build: refresh text and style for multi-stage desc

Signed-off-by: David Karlsson <david.karlsson@docker.com>
This commit is contained in:
David Karlsson 2023-05-04 21:01:24 +02:00
parent de46ad7b1c
commit 6c20a6a31d
1 changed files with 41 additions and 36 deletions

View File

@ -14,26 +14,31 @@ Dockerfiles while keeping them easy to read and maintain.
> >
> Special thanks to [Alex Ellis](https://twitter.com/alexellisuk){:target="blank" rel="noopener" class=""} > Special thanks to [Alex Ellis](https://twitter.com/alexellisuk){:target="blank" rel="noopener" class=""}
> for granting permission to use his blog post [Builder pattern vs. Multi-stage builds in Docker](https://blog.alexellis.io/mutli-stage-docker-builds/){:target="blank" rel="noopener" class=""} > for granting permission to use his blog post [Builder pattern vs. Multi-stage builds in Docker](https://blog.alexellis.io/mutli-stage-docker-builds/){:target="blank" rel="noopener" class=""}
> as the basis of the examples below. > as the basis of the examples on this page.
## Before multi-stage builds ## Before multi-stage builds
One of the most challenging things about building images is keeping the image One problem you may face as you build and publish images, is that the size of
size down. Each `RUN`, `COPY`, and `ADD` instruction in the Dockerfile adds a layer to the image, and you those images can sometimes grow quite large. Traditionally, before multi-stage
need to remember to clean up any artifacts you don't need before moving on to builds were a thing, keeping the size of images down would require you to
the next layer. To write a really efficient Dockerfile, you have traditionally manually clean up resources from the image, so as to keep it small.
needed to employ shell tricks and other logic to keep the layers as small as
possible and to ensure that each layer has the artifacts it needs from the
previous layer and nothing else.
It was actually very common to have one Dockerfile to use for development (which In the past, it was common practice to have one Dockerfile for development,
contained everything needed to build your application), and a slimmed-down one and another, slimmed-down one to use for production.
to use for production, which only contained your application and exactly what The development version contained everything needed to build your application.
was needed to run it. This has been referred to as the "builder The production version only contained your application
pattern". Maintaining two Dockerfiles is not ideal. and the dependencies needed to run it.
Here's an example of a `build.Dockerfile` and `Dockerfile` which adhere to the To write a truly efficient Dockerfile, you had to come up with shell tricks and
builder pattern above: arcane solutions to keep the layers as small as possible.
All to ensure that each layer contained only the artifacts it needed,
and nothing else.
This has been referred to as the _builder pattern_.
The following examples show two Dockerfiles that adhere to this pattern:
- `build.Dockerfile`, for development builds
- `Dockerfile`, for slimmed-down production builds
**`build.Dockerfile`**: **`build.Dockerfile`**:
@ -46,10 +51,11 @@ RUN go get -d -v golang.org/x/net/html \
&& CGO_ENABLED=0 go build -a -installsuffix cgo -o app . && CGO_ENABLED=0 go build -a -installsuffix cgo -o app .
``` ```
Notice that this example also artificially compresses two `RUN` commands together Notice how this example artificially compresses two `RUN` commands together
using the Bash `&&` operator, to avoid creating an additional layer in the image. using the Bash `&&` operator. This is done to avoid creating an additional
This is failure-prone and hard to maintain. It's easy to insert another command layer in the image. Writing Dockerfiles like this is failure-prone and hard to
and forget to continue the line using the `\` character, for example. maintain. It's easy to insert another command and forget to continue the line
using the `\` character, for example.
**`Dockerfile`**: **`Dockerfile`**:
@ -62,7 +68,11 @@ COPY app ./
CMD ["./app"] CMD ["./app"]
``` ```
**`build.sh`**: The following example is a utility script that:
1. Builds the first image.
2. Creates a container from it to copy the artifact out.
3. Builds the second image.
```bash ```bash
#!/bin/sh #!/bin/sh
@ -78,12 +88,10 @@ docker build --no-cache -t alexellis2/href-counter:latest .
rm ./app rm ./app
``` ```
When you run the `build.sh` script, it needs to build the first image, create Both images take up room on your system and you still end up with the `app`
a container from it to copy the artifact out, then build the second
image. Both images take up room on your system and you still have the `app`
artifact on your local disk as well. artifact on your local disk as well.
Multi-stage builds vastly simplify this situation! Multi-stage builds simplifies this situation!
## Use multi-stage builds ## Use multi-stage builds
@ -91,7 +99,7 @@ With multi-stage builds, you use multiple `FROM` statements in your Dockerfile.
Each `FROM` instruction can use a different base, and each of them begins a new Each `FROM` instruction can use a different base, and each of them begins a new
stage of the build. You can selectively copy artifacts from one stage to stage of the build. You can selectively copy artifacts from one stage to
another, leaving behind everything you don't want in the final image. To show another, leaving behind everything you don't want in the final image. To show
how this works, let's adapt the `Dockerfile` from the previous section to use how this works, you can adapt the `Dockerfile` from the previous section to use
multi-stage builds. multi-stage builds.
```dockerfile ```dockerfile
@ -110,8 +118,9 @@ COPY --from=0 /go/src/github.com/alexellis/href-counter/app ./
CMD ["./app"] CMD ["./app"]
``` ```
You only need the single Dockerfile. You don't need a separate build script, You only need the single Dockerfile.
either. Just run `docker build`. No need for a separate build script.
Just run `docker build`.
```console ```console
$ docker build -t alexellis2/href-counter:latest . $ docker build -t alexellis2/href-counter:latest .
@ -128,7 +137,7 @@ intermediate artifacts are left behind, and not saved in the final image.
## Name your build stages ## Name your build stages
By default, the stages are not named, and you refer to them by their integer By default, the stages aren't named, and you refer to them by their integer
number, starting with 0 for the first `FROM` instruction. However, you can number, starting with 0 for the first `FROM` instruction. However, you can
name your stages, by adding an `AS <NAME>` to the `FROM` instruction. This name your stages, by adding an `AS <NAME>` to the `FROM` instruction. This
example improves the previous one by naming the stages and using the name in example improves the previous one by naming the stages and using the name in
@ -162,7 +171,7 @@ the stage named `builder`:
$ docker build --target builder -t alexellis2/href-counter:latest . $ docker build --target builder -t alexellis2/href-counter:latest .
``` ```
A few scenarios where this might be very powerful are: A few scenarios where this might be useful are:
- Debugging a specific build stage - Debugging a specific build stage
- Using a `debug` stage with all debugging symbols or tools enabled, and a - Using a `debug` stage with all debugging symbols or tools enabled, and a
@ -172,7 +181,7 @@ A few scenarios where this might be very powerful are:
## Use an external image as a "stage" ## Use an external image as a "stage"
When using multi-stage builds, you are not limited to copying from stages you When using multi-stage builds, you aren't limited to copying from stages you
created earlier in your Dockerfile. You can use the `COPY --from` instruction to created earlier in your Dockerfile. You can use the `COPY --from` instruction to
copy from a separate image, either using the local image name, a tag available copy from a separate image, either using the local image name, a tag available
locally or on a Docker registry, or a tag ID. The Docker client pulls the image locally or on a Docker registry, or a tag ID. The Docker client pulls the image
@ -202,10 +211,6 @@ COPY source2.cpp source.cpp
RUN g++ -o /binary source.cpp RUN g++ -o /binary source.cpp
``` ```
## Version compatibility
Multi-stage build syntax was introduced in Docker Engine 17.05.
## Differences between legacy builder and BuildKit ## Differences between legacy builder and BuildKit
The legacy Docker Engine builder processes all stages of a Dockerfile leading The legacy Docker Engine builder processes all stages of a Dockerfile leading
@ -279,5 +284,5 @@ Removing intermediate container bbc025b93175
Successfully built 09fc3770a9c4 Successfully built 09fc3770a9c4
``` ```
`stage1` gets executed when BuildKit is disabled, even if `stage2` does not The legacy builder processes `stage1`,
depend on it. even if `stage2` doesn't depend on it.