Multi-stage build is a new feature and requires Docker 17.05 or higher daemon and client. For those struggling to optimize Dockerfiles and make them easy to read and maintain, multi-stage builds are very useful.
Before the multi-stage build
One of the most challenging things when building an image is to reduce the image size. Each instruction in the Dockerfile will add a layer to the image. Before going to the next layer, you need to remember to remove all unnecessary artifacts. To write a truly efficient Dockerfile, it is traditionally necessary to use shell techniques 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.
In fact, it is very common to have a Dockerfile for the development environment (contains all the content needed to build the application), and a streamlined Dockerfile for the production environment (contains only the application and the content required to run the application) at the same time . This is called "builder mode". Maintaining two Dockerfiles is not ideal.
Here's an example Dockerfile.build
file and builders meet the above modes Dockerfile
:
Dockerfile.build
:
FROM golang:1.7.3
WORKDIR /go/src/github.com/alexellis/href-counter/
COPY app.go .
RUN go get -d -v golang.org/x/net/html \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
Note that this example also uses the operator Bash &&
two RUN
artificial compressed together in order to avoid creating additional layers in the mirror. This is prone to failure and difficult to maintain. For example, it is easy to insert another command and forget to use the \
character to continue the line.
Dockerfile
:
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY app .
CMD ["./app"]
build.sh
:
#!/bin/sh
echo Building alexellis2/href-counter:build
docker build --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy \
-t alexellis2/href-counter:build . -f Dockerfile.build
docker container create --name extract alexellis2/href-counter:build
docker container cp extract:/go/src/github.com/alexellis/href-counter/app ./app
docker container rm -f extract
echo Building alexellis2/href-counter:latest
docker build --no-cache -t alexellis2/href-counter:latest .
rm ./app
When you run the build.sh
script, it needs to build the first image, create a container from which to copy the work, then build a second mirror. The two mirrors take up space on your system, and still there on your local disk app
workpiece.
Multi-stage construction greatly simplifies this situation!
Use multi-stage build
For multi-stage build, you can use multiple Dockerfile in the FROM
statement. Each FROM
instruction can use a different group of mirror, and they are beginning to build a new stage. You can selectively copy artifacts from one stage to another, discarding everything you don't want in the final image. To illustrate how this works, let's adjust the Dockerfile in the previous section using a multi-stage build.
Dockerfile
:
FROM golang:1.7.3
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]
You only need a Dockerfile. You also don't need a separate build script. Just run docker build
.
$ docker build -t alexellis2/href-counter:latest .
The end result is the same tiny production mirror as before, and significantly reduced complexity. You don't need to create any intermediate mirrors, and you don't need to extract any artifacts to the local system.
How does it work? The second FROM
instruction is used alpine:latest
the mirror as a basis to begin a new build phase. COPY --from=0
Only the build artifacts from the previous stage are copied to this new stage. The Go SDK and any intermediate artifacts will be left behind and will not be saved in the final image.
Name the build phase
By default, the phases are not named, and they can be referenced by their integers. FROM
The first integer of the instruction starts from 0. However, you can add one AS <NAME>
to the FROM
directive named stage. The following examples and by naming stage COPY
using an improved example of the name of the previous instruction. This means that even if the instructions in the Dockerfile are reordered later, COPY
they will not be broken.
FROM golang:1.7.3 AS builder
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]
Stop at a specific build stage
When building the image, it is not necessary to build the entire Dockerfile including each stage. You can specify the target build phase. The following command assumes that you are using before Dockerfile
, but called builder
stop phase:
$ docker build --target builder -t alexellis2/href-counter:latest .
A few scenarios where this can be very powerful are:
- Debug a specific build phase
- All use a tool or debugging symbols enabled
调试(debug)
phase and a lean生产(production)
phase - Use a
测试(testing)
stage where your application will be filled with test data, but when building a product, use a different stage that uses real data.
Use external mirrors as "stages"
When using a multi-stage build, you are not restricted to copying from previously created stages in the Dockerfile. You can use the COPY --from
instructions from a separate copy of the image can be used are available on the local image name, the local registry, or Docker label or ID. The Docker client pulls the image and copies artifacts from it when necessary. The syntax is:
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
Treat the previous stage as the new stage
In use FROM
instruction, you can refer to the contents of the previous stage. E.g:
FROM alpine:latest as builder
RUN apk --no-cache add build-base
FROM builder as build1
COPY source1.cpp source.cpp
RUN g++ -o /binary source.cpp
FROM builder as build2
COPY source2.cpp source.cpp
RUN g++ -o /binary source.cpp
Author: Docker's official website
translator: Technical Zemin
Publisher: Technical Verses
links: English text