Some touch-ups in language get-started

- wrap lines in the java and golang getting started
- make the "test on the host" step in the Java getting started "optional". This
  allows users to try the tutorial without requiring them to set up a functional
  Java environment on their host. (i'm planning on making similar changes in
  the other language guides).
- use `docker <object> <action>` in some examples (but mention the corresponding
  shorthands); this may help understand the relation between the `<action>` and
  what kind of `<object>` we're acting on (tag an _image_, remove an _image_).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2021-08-25 14:29:34 +02:00
parent 0371f3cb28
commit d7e9ac275f
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
2 changed files with 198 additions and 68 deletions

View File

@ -10,12 +10,18 @@ redirect_from:
## Prerequisites
* Some understanding of Go and its toolchain. This is not a tutorial on Go. If you are new to the language, the [Go website](https://golang.org/){: target="_blank" rel="noopener" class="_"} is a good starting point, so go (pun intended) check it out.
* Some awareness of basic Docker concepts. If unsure, work through the orientation and setup in Get started [Part 1](../../get-started/index.md).
* Some understanding of Go and its toolchain. This is not a tutorial on Go. If
you are new to the language, the [Go website](https://golang.org/){: target="_blank" rel="noopener" class="_"}
is a good starting point, so go (pun intended) check it out.
* Some awareness of basic Docker concepts. If unsure, work through the orientation
and setup in Get started [Part 1](../../get-started/index.md).
## Overview
Now that we have a good overview of containers and the Docker platform, lets take a look at building our first image. An image includes everything you need to run an application the code or binary, runtime, dependencies, and any other file system objects required.
Now that we have a good overview of containers and the Docker platform, lets
take a look at building our first image. An image includes everything you need
to run an application the code or binary, runtime, dependencies, and any other
file system objects required.
To complete this tutorial, you need the following:
@ -25,15 +31,18 @@ To complete this tutorial, you need the following:
## Meet the example application
To avoid losing focus on Docker's features, the sample application is a minimal HTTP server that has only three features:
To avoid losing focus on Docker's features, the sample application is a minimal
HTTP server that has only three features:
* It responds with a text message containing a heart symbol ("<3") on requests to `/`.
* It responds with `{"Status" : "OK"}` JSON to the health check request on requests to `/ping`.
* The port it listens on is configurable using the environment variable `HTTP_PORT`. The default value is `8080`.
Thus, it somewhat mimics enough basic properties of a REST microservice to be useful for our learning of Docker.
Thus, it somewhat mimics enough basic properties of a REST microservice to be
useful for our learning of Docker.
The source code for the application is in the [olliefr/docker-gs-ping](https://github.com/olliefr/docker-gs-ping){: target="_blank" rel="noopener" class="_"} GitHub repo. Please feel free to clone or fork it.
The source code for the application is in the [github.com/olliefr/docker-gs-ping](https://github.com/olliefr/docker-gs-ping){: target="_blank" rel="noopener" class="_"}
GitHub repository. Please feel free to clone or fork it.
For our present study, we clone it to our local machine:
@ -41,7 +50,8 @@ For our present study, we clone it to our local machine:
$ git clone https://github.com/olliefr/docker-gs-ping
```
The application's `main.go` file is fairly straightforward, if you are familiar with Go:
The application's `main.go` file is fairly straightforward, if you are familiar
with Go:
{% raw %}
```go
@ -82,13 +92,16 @@ func main() {
## Smoke test the application
Lets start our application and make sure its running properly. Open your terminal and navigate to the directory into which you cloned the project's repo. From now on, we'll refer to this directory as the **working directory**.
Lets start our application and make sure its running properly. Open your
terminal and navigate to the directory into which you cloned the project's repo.
From now on, we'll refer to this directory as the **project directory**.
```console
$ go run main.go
```
This should compile and start the server as a foreground application, outputting the banner, as illustrated in the next figure.
This should compile and start the server as a foreground application, outputting
the banner, as illustrated in the next figure.
```
____ __
@ -102,16 +115,20 @@ ____________________________________O/_______
⇨ http server started on [::]:8080
```
Let's run a quick _smoke test_ on the application. **In a new terminal**, run a request using `curl`. Alternatively, you can use your favourite web browser as well.
Let's run a quick _smoke test_ on the application. **In a new terminal**, run a
request using `curl`. Alternatively, you can use your favourite web browser as
well.
```console
$ curl http://localhost:8080/
Hello, Docker! <3
```
So, the application responds with a greeting, just as the first business requirement says it should. Great.
So, the application responds with a greeting, just as the first business
requirement says it should. Great.
Having established that the server is running and is accessible, let's proceed to "dockerizing" it.
Having established that the server is running and is accessible, let's proceed
to "dockerizing" it.
## Create a Dockerfile for the application
@ -126,7 +143,11 @@ we would like to use for our application.
FROM golang:1.16-alpine
```
Docker images can be inherited from other images. Therefore, instead of creating our own base image, well use the official Go image that already has all the tools and packages to compile and run a Go application. You can think of this in the same way you would think about class inheritance in object oriented programming or functional composition in functional programming.
Docker images can be inherited from other images. Therefore, instead of creating
our own base image, well use the official Go image that already has all the tools
and packages to compile and run a Go application. You can think of this in the
same way you would think about class inheritance in object oriented programming
or functional composition in functional programming.
When we have used that `FROM` command, we told Docker to include in our image all the functionality from the `golang:1.16-alpine` image. All of our consequent commands would build on top of that "base" image.
@ -134,50 +155,75 @@ When we have used that `FROM` command, we told Docker to include in our image al
>
> If you want to learn more about creating your own base images, see [creating base images](https://docs.docker.com/develop/develop-images/baseimages/) section of the guide.
To make things easier when running the rest of our commands, lets create a directory _inside_ the image that we are building. This also instructs Docker to use this directory as the default _destination_ for all subsequent commands. This way we do not have to type out full file paths but can use relative paths based on this directory.
To make things easier when running the rest of our commands, lets create a
directory _inside_ the image that we are building. This also instructs Docker
to use this directory as the default _destination_ for all subsequent commands.
This way we do not have to type out full file paths but can use relative paths
based on this directory.
```dockerfile
WORKDIR /app
```
Usually the very first thing you do once youve downloaded a project written in Go is to install the modules necessary to compile it.
Usually the very first thing you do once youve downloaded a project written in
Go is to install the modules necessary to compile it.
But before we can run `go mod download` inside our image, we need to get our `go.mod` and `go.sum` files copied into it. We use the `COPY` command to do this.
But before we can run `go mod download` inside our image, we need to get our
`go.mod` and `go.sum` files copied into it. We use the `COPY` command to do this.
In its simplest form, the `COPY` command takes two parameters. The first parameter tells Docker what file you would like to copy into the image. The second parameter tells Docker where you want that file to be copied to.
In its simplest form, the `COPY` command takes two parameters. The first
parameter tells Docker what files you want to copy into the image. The last
parameter tells Docker where you want that file to be copied to.
Well copy the `go.mod` and `go.sum` file into our working directory `/app` which, owing to our use of `WORKDIR`, is the current directory (`.`) inside the image.
Well copy the `go.mod` and `go.sum` file into our project directory `/app` which,
owing to our use of `WORKDIR`, is the current directory (`.`) inside the image.
```dockerfile
COPY go.mod ./
COPY go.sum ./
```
Now that we have the module files inside the Docker image that we are building, we can use the `RUN` command to execute the command `go mod download` there as well. This works exactly the same as if we were running `go` locally on our machine, but this time these Go modules will be installed into the app directory inside our image.
Now that we have the module files inside the Docker image that we are building,
we can use the `RUN` command to execute the command `go mod download` there as
well. This works exactly the same as if we were running `go` locally on our
machine, but this time these Go modules will be installed into a directory
inside the image.
```dockerfile
RUN go mod download
```
At this point, we have an image that is based on Go environment version 1.16 (or a later minor version, since we had specified `1.16` as our tag in the `FROM` command) and we have installed our dependencies.
At this point, we have an image that is based on Go environment version 1.16
(or a later minor version, since we had specified `1.16` as our tag in the
`FROM` command) and we have installed our dependencies.
The next thing we need to do is to copy our source code into the image. Well use the `COPY` command just like we did with our module files before.
The next thing we need to do is to copy our source code into the image. Well
use the `COPY` command just like we did with our module files before.
```dockerfile
COPY *.go ./
```
This `COPY` command uses a wildcard to copy all files with `.go` extension located in the current directory on the host (the directory where the `Dockerfile` is located) into the current directory inside the image.
This `COPY` command uses a wildcard to copy all files with `.go` extension
located in the current directory on the host (the directory where the `Dockerfile`
is located) into the current directory inside the image.
Now, we would like to compile our application. To that end, we use the familiar `RUN` command:
Now, we would like to compile our application. To that end, we use the familiar
`RUN` command:
```dockerfile
RUN go build -o /docker-gs-ping
```
This should be familiar. The result of that command will be a static application binary named `docker-gs-ping` and located in the root of the filesystem of the image that we are building. We could have put the binary into any other place we desire inside that image, the root directory has no special meaning in this regard. It's just convenient to use it to keep the file paths short for improved readability.
This should be familiar. The result of that command will be a static application
binary named `docker-gs-ping` and located in the root of the filesystem of the
image that we are building. We could have put the binary into any other place we
desire inside that image, the root directory has no special meaning in this
regard. It's just convenient to use it to keep the file paths short for improved
readability.
Now, all that is left to do is to tell Docker what command to execute when our image is used to start a container.
Now, all that is left to do is to tell Docker what command to execute when our
image is used to start a container.
We do this with the `CMD` command:
@ -207,7 +253,11 @@ EXPOSE 8080
CMD [ "/docker-gs-ping" ]
```
The `Dockerfile` may also contain _comments_. They always begin with a `#` symbol and make no difference to Docker. The comments are there for the convenience of humans tasked to maintain the `Dockerfile`:
The `Dockerfile` may also contain _comments_. They always begin with a `#` symbol,
and must be at the beginning of a line. Comments are there for your convenience
to allow documenting your `Dockerfile`. Dockerfile _directives_, such as the
`syntax` directive we added, must always be at the very top of the `Dockerfile`,
so when adding comments, make sure they are after those directives:
```dockerfile
# syntax=docker/dockerfile:1
@ -229,9 +279,14 @@ RUN go mod download
## Build the image
Now that we've created our `Dockerfile`, lets build an image from it. The `docker build` command creates Docker images from the `Dockerfile` and a "context". A build _context_ is the set of files located in the specified path or URL. The Docker build process can access any of the files located in the context.
Now that we've created our `Dockerfile`, lets build an image from it. The
`docker build` command creates Docker images from the `Dockerfile` and a "context".
A build _context_ is the set of files located in the specified path or URL. The
Docker build process can access any of the files located in the context.
The build command optionally takes a `--tag` flag. This flag is used to label the image with a string value, which is easy for humans to read and recognise. If you do not pass a `--tag`, Docker will use `latest` as the default value.
The build command optionally takes a `--tag` flag. This flag is used to label
the image with a string value, which is easy for humans to read and recognise.
If you do not pass a `--tag`, Docker will use `latest` as the default value.
Let's build our first Docker image!
@ -262,42 +317,61 @@ $ docker build --tag docker-gs-ping .
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
```
Your exact output will vary, but provided there aren't any errors, you should see the `FINISHED` line in the build output. This means Docker has successfully built our image and assigned a `docker-gs-ping` tag to it.
Your exact output will vary, but provided there aren't any errors, you should
see the `FINISHED` line in the build output. This means Docker has successfully
built our image and assigned a `docker-gs-ping` tag to it.
## View local images
To see the list of images we have on our local machine, we have two options. One is to use the CLI and the other is to use [Docker Desktop](../../desktop/index.md). Since we are currently working in the terminal, lets take a look at listing images with the CLI.
To see the list of images we have on our local machine, we have two options. One
is to use the CLI and the other is to use [Docker Desktop](../../desktop/index.md).
Since we are currently working in the terminal, lets take a look at listing
images with the CLI.
To list images, simply run the `images` command:
To list images, run the `docker image ls`command (or the `docker images` shorthand):
```console
$ docker images
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-gs-ping latest 336a3f164d0f 39 minutes ago 540MB
postgres 13.2 c5ec7353d87d 7 weeks ago 314MB
```
Your exact output may vary, but you should see `docker-gs-ping` image with the `latest` tag.
Your exact output may vary, but you should see `docker-gs-ping` image with the
`latest` tag.
## Tag images
An image name is made up of slash-separated name components. Name components may contain lowercase letters, digits and separators. A separator is defined as a period, one or two underscores, or one or more dashes. A name component may not start or end with a separator.
An image name is made up of slash-separated name components. Name components may
contain lowercase letters, digits and separators. A separator is defined as a
period, one or two underscores, or one or more dashes. A name component may not
start or end with a separator.
An image is made up of a manifest and a list of layers. In simple terms, a “tag” points to a combination of these artifacts. You can have multiple tags for the image and, in fact, most images have multiple tags. Lets create a second tag for the image we had built and take a look at its layers.
An image is made up of a manifest and a list of layers. In simple terms, a “tag”
points to a combination of these artifacts. You can have multiple tags for the
image and, in fact, most images have multiple tags. Lets create a second tag
for the image we had built and take a look at its layers.
To create a new tag for our image, run the following command.
Use the `docker image tag` (or `docker tag` shorthand) command to create a new
tag for our image. This command takes two arguments; the first argument is the
"source" image, and the second is the new tag to create. The following command
creates a new `docker-gs-ping:v1.0` tag for the `docker-gs-ping:latest` we built
above:
```console
$ docker tag docker-gs-ping:latest docker-gs-ping:v1.0
$ docker image tag docker-gs-ping:latest docker-gs-ping:v1.0
```
The Docker `tag` command creates a new tag for the image. It does not create a new image. The tag points to the same image and is just another way to reference the image.
The Docker `tag` command creates a new tag for the image. It does not create a
new image. The tag points to the same image and is just another way to reference
the image.
Now run the `docker images` command to see the updated list of local images:
Now run the `docker image ls` command again to see the updated list of local
images:
```console
$ docker images
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-gs-ping latest 336a3f164d0f 43 minutes ago 540MB
@ -305,34 +379,49 @@ docker-gs-ping v1.0 336a3f164d0f 43 minutes ago 540MB
postgres 13.2 c5ec7353d87d 7 weeks ago 314MB
```
You can see that we have two images that start with `docker-gs-ping`. We know they are the same image because if you look at the `IMAGE ID` column, you can see that the values are the same for the two images. This value is a unique identifier Docker uses internally to identify the image.
You can see that we have two images that start with `docker-gs-ping`. We know
they are the same image because if you look at the `IMAGE ID` column, you can
see that the values are the same for the two images. This value is a unique
identifier Docker uses internally to identify the image.
Lets remove the tag that we had just created. To do this, well use the `rmi` command, which stands for "remove image":
Lets remove the tag that we had just created. To do this, well use the
`docker image rm` command, or the shorthand `docker rmi` (which stands for
"remove image"):
```console
$ docker rmi docker-gs-ping:v1.0
$ docker image rm docker-gs-ping:v1.0
Untagged: docker-gs-ping:v1.0
```
Notice that the response from Docker tells us that the image has not been removed but only "untagged". Verify this by running the images command:
```console
$ docker images
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-gs-ping latest 336a3f164d0f 45 minutes ago 540MB
postgres 13.2 c5ec7353d87d 7 weeks ago 314MB
```
The tag `v1.0` has been removed but we still have the `docker-gs-ping:latest` tag available on our machine, so the image is there.
The tag `v1.0` has been removed but we still have the `docker-gs-ping:latest`
tag available on our machine, so the image is there.
## Multi-stage builds
You may have noticed that our `docker-gs-ping` image stands at 540MB, which you may think is a lot. You may also be wondering whether our dockerized application still needs the full suite of Go tools, including the compiler, after the application binary had been compiled.
You may have noticed that our `docker-gs-ping` image stands at 540MB, which you
may think is a lot. You may also be wondering whether our dockerized application
still needs the full suite of Go tools, including the compiler, after the
application binary had been compiled.
These are legit concerns. Both can be solved by using _multi-stage builds_. The following example is provided with little explanation because this would derail us from our current concerns, but please feel free to explore on your own later. The main idea is that we use one image to produce some artefacts, which are then placed into another, much smaller image, containing only the parts necessary for running the artefacts that we'd built.
These are legit concerns. Both can be solved by using _multi-stage builds_. The
following example is provided with little explanation because this would derail
us from our current concerns, but please feel free to explore on your own later.
The main idea is that we use one image to produce some artefacts, which are then
placed into another, much smaller image, containing only the parts necessary for
running the artefacts that we'd built.
The `Dockerfile.multistage` in the sample application's repo has the following content:
The `Dockerfile.multistage` in the sample application's repo has the following
content:
{% raw %}
```dockerfile
@ -370,34 +459,50 @@ ENTRYPOINT ["/docker-gs-ping"]
```
{% endraw %}
Since we have two dockerfiles now, we have to tell Docker that we want to build using our new Dockerfile. We also tag the new image with `multistage` but this word has no special meaning, we only do so that we could compare this new image to the one we've built previously, that is the one we tagged with `latest`:
Since we have two dockerfiles now, we have to tell Docker that we want to build
using our new Dockerfile. We also tag the new image with `multistage` but this
word has no special meaning, we only do so that we could compare this new image
to the one we've built previously, that is the one we tagged with `latest`:
```console
$ docker build -t docker-gs-ping:multistage -f Dockerfile.multistage .
```
Comparing the sizes of `docker-gs-ping:multistage` and `docker-gs-ping:latest` we see an order-of-magnitude difference!
Comparing the sizes of `docker-gs-ping:multistage` and `docker-gs-ping:latest`
we see an order-of-magnitude difference!
```console
$ docker images
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-gs-ping multistage e3fdde09f172 About a minute ago 27.1MB
docker-gs-ping latest 336a3f164d0f About an hour ago 540MB
```
This is due to the fact that the ["distroless" base image](https://github.com/GoogleContainerTools/distroless) that we have used to deploy our Go application is very barebones and is meant for lean deployments of static binaries.
This is due to the fact that the ["distroless" base image](https://github.com/GoogleContainerTools/distroless){:target="_blank" rel="noopener" class="_"}
that we have used to deploy our Go application is very barebones and is meant
for lean deployments of static binaries.
For more information on multi-stage builds, please feel free to check out [other parts](../../develop/develop-images/multistage-build.md) of Docker documentation. This is, however, not essential for our progress here, so we'll leave it at that.
For more information on multi-stage builds, please feel free to check out
[other parts](../../develop/develop-images/multistage-build.md) of the Docker
documentation. This is, however, not essential for our progress here, so we'll
leave it at that.
## Next steps
In this module, we took a look at setting up our example Go application that we will use for much of the rest of the tutorial. We also created a `Dockerfile` that we used to build our Docker image. Then, we took a look at tagging our images and removing images and tags. In the next module, well take a look at how to:
In this module, we took a look at setting up our example Go application that we
will use for much of the rest of the tutorial. We also created a `Dockerfile`
that we used to build our Docker image. Then, we took a look at tagging our
images and removing images and tags. In the next module, well take a look at
how to:
[Run your image as a container](run-containers.md){: .button .outline-btn}
## Feedback
Help us improve this topic by providing your feedback. Let us know what you think by creating an issue in the [Docker Docs ](https://github.com/docker/docker.github.io/issues/new?title=[Golang%20docs%20feedback]){:target="_blank" rel="noopener" class="_"} GitHub repository. Alternatively, [create a PR](https://github.com/docker/docker.github.io/pulls){:target="_blank" rel="noopener" class="_"} to suggest updates.
Help us improve this topic by providing your feedback. Let us know what you
think by creating an issue in the [Docker Docs](https://github.com/docker/docker.github.io/issues/new?title=[Golang%20docs%20feedback]){:target="_blank" rel="noopener" class="_"}
GitHub repository. Alternatively, [create a PR](https://github.com/docker/docker.github.io/pulls){:target="_blank" rel="noopener" class="_"}
to suggest updates.
<br />

View File

@ -18,7 +18,6 @@ Now that we have a good overview of containers and the Docker platform, lets
To complete this tutorial, you need the following:
- Java OpenJDK version 15 or later. [Download and install Java](https://jdk.java.net/){: target="_blank" rel="noopener" class="_"}
- Docker running locally. Follow the instructions to [download and install Docker](../../get-docker.md)
- A Git client
- An IDE or a text editor to edit files. We recommend using [IntelliJ Community Edition](https://www.jetbrains.com/idea/download/){: target="_blank" rel="noopener" class="_"}.
@ -33,7 +32,17 @@ $ git clone https://github.com/spring-projects/spring-petclinic.git
$ cd spring-petclinic
```
## Test the application
## Test the application without Docker (optional)
In this step, we will test the application locally without Docker, before we
continue with building and running the application with Docker. This section
requires you to have Java OpenJDK version 15 or later installed on your machine.
[Download and install Java](https://jdk.java.net/){: target="_blank" rel="noopener" class="_"}
If you prefer to not install Java on your machine, you can skip this step, and
continue straight to the next section, in which we explain how to build and run
the application in Docker, which does not require you to have Java installed on
your machine.
Lets start our application and make sure it is running properly. Maven will manage all the project processes (compiling, tests, packaging, etc). The **Spring Pets Clinic** project we cloned earlier contains an embedded version of Maven. Therefore, we don't need to install Maven separately on your local machine.
@ -54,9 +63,15 @@ o.s.s.petclinic.PetClinicApplication : Started
PetClinicApplication in 11.743 seconds (JVM running for 12.364)
```
## Create a Dockerfile for Java
Great! We verified that the application works. At this stage, you've completed
testing the server script locally.
Now that our application is running properly, lets take a look at creating a Dockerfile.
Press `CTRL-c` from within the terminal session where the server is running to stop it.
We will now continue to build and run the application in Docker.
## Create a Dockerfile for Java
{% include guides/create-dockerfile.md %}
@ -69,28 +84,38 @@ we would like to use for our application.
FROM openjdk:16-alpine3.13
```
Docker images can be inherited from other images. Therefore, instead of creating our own base image, well use the official Maven image with Java JDK that already has all the tools and packages that we need to run a Java application.
Docker images can be inherited from other images. For this guide, we use the
official `openjdk` image from Docker Hub with Java JDK that already has all the
tools and packages that we need to run a Java application.
> **Note**
>
> To learn more about creating your own base images, see [Creating base images](https://docs.docker.com/develop/develop-images/baseimages/).
To make things easier when running the rest of our commands, lets create a working directory. This instructs Docker to use this path as the default location for all subsequent commands. By doing this, we do not have to type out full file paths but can use relative paths based on the working directory.
To make things easier when running the rest of our commands, lets set the image's
working directory. This instructs Docker to use this path as the default location
for all subsequent commands. By doing this, we do not have to type out full file
paths but can use relative paths based on the working directory.
```dockerfile
WORKDIR /app
```
Usually, the very first thing you do once youve downloaded a project written in Java which is using Maven for project management is to install dependencies.
Usually, the very first thing you do once youve downloaded a project written in
Java which is using Maven for project management is to install dependencies.
Before we can run `mvnw dependency`, we need to get the Maven wrapper and our `pom.xml` file into our image. Well use the `COPY` command to do this. The `COPY` command takes two parameters. The first parameter tells Docker what file(s) you would like to copy into the image. The second parameter tells Docker where you want that file(s) to be copied to. Well copy all those files and directories into our working directory - `/app`.
Before we can run `mvnw dependency`, we need to get the Maven wrapper and our
`pom.xml` file into our image. Well use the `COPY` command to do this. The
`COPY` command takes two parameters. The first parameter tells Docker what
file(s) you would like to copy into the image. The second parameter tells Docker
where you want that file(s) to be copied to. Well copy all those files and
directories into our working directory - `/app`.
```dockerfile
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
```
Once we have our `pom.xml` file inside the image, we can use the `RUN` command to execute the command `mvnw dependency:go-offline`. This works exactly the same way as if we were running `mvnw` (or mvn) dependency locally on our machine, but this time the dependencies will be installed into the image.
Once we have our `pom.xml` file inside the image, we can use the `RUN` command
to execute the command `mvnw dependency:go-offline`. This works exactly the same
way as if we were running `mvnw` (or `mvn`) dependency locally on our machine,
but this time the dependencies will be installed into the image.
```dockerfile
RUN ./mvnw dependency:go-offline