Multi-stage builds feature allow you to add multiple FROM instructions in the Dockerfile.
The FROM instructions are basicaly the same as the regular single FROM instructions that we have used previously.
Each FROM instruction define a build stage that has its base image and a set of instructions to build images layers.
A FROM instruction should have a name which can be used by other FROM instructions as a reference.
The FROM instruction that references another one, will be able to copy artifacts created within that staging build.
A staging build can be considered as a temporary build that we can use in the next staging builds to copy only the artifacts that we need and discards everything else.
The goal is to have an optimized final stage (in term of disk space) that in theory will be the one that we will use for the image we want to create.
Let's use this Dokerfile (not a very useful one but it's simple enough to demonstrate the multi-stage builds feature):
$ vi Dockerfile
#stage 0
FROM alpine:latest AS stage0
RUN apk add --no-cache curl tar
RUN curl -fSL -o /tmp/apache-zookeeper-3.6.1-bin.tar.gz https://archive.apache.org/dist/zookeeper/zookeeper-3.6.1/apache-zookeeper-3.6.1-bin.tar.gz
RUN tar -xzf /tmp/apache-zookeeper-3.6.1-bin.tar.gz -C /tmp/
#final stage
FROM alpine:latest
COPY --from=stage0 /tmp/apache-zookeeper-3.6.1-bin /opt/
Let's build the Dockerfile:
$ DOCKER_BUILDKIT=0 docker build -t zookeeper-multi-stage:3.6.1 .
Sending build context to Docker daemon 2.048kB
Step 1/6 : FROM alpine:latest AS stage0
latest: Pulling from library/alpine
df20fa9351a1: Pull complete
Digest: sha256:185518070891758909c9f839cf4ca393ee977ac378609f700f60a771a2dfe321
Status: Downloaded newer image for alpine:latest
---> a24bb4013296
Step 2/6 : RUN apk add --no-cache curl tar
---> Running in 97a752789c85
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/community/x86_64/APKINDEX.tar.gz
(1/6) Installing ca-certificates (20191127-r4)
(2/6) Installing nghttp2-libs (1.41.0-r0)
(3/6) Installing libcurl (7.69.1-r0)
(4/6) Installing curl (7.69.1-r0)
(5/6) Installing libacl (2.2.53-r0)
(6/6) Installing tar (1.32-r1)
Executing busybox-1.31.1-r16.trigger
Executing ca-certificates-20191127-r4.trigger
OK: 8 MiB in 20 packages
Removing intermediate container 97a752789c85
---> 5d10ef835956
Step 3/6 : RUN curl -fSL -o /tmp/apache-zookeeper-3.6.1-bin.tar.gz https://archive.apache.org/dist/zookeeper/zookeeper-3.6.1/apache-zookeeper-3.6.1-bin.tar.gz
---> Running in 636240e89a86
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 11.8M 100 11.8M 0 0 7515k 0 0:00:01 0:00:01 --:--:-- 7510k
Removing intermediate container 636240e89a86
---> 5341fb2f8fe0
Step 4/6 : RUN tar -xzf /tmp/apache-zookeeper-3.6.1-bin.tar.gz -C /tmp/
---> Running in 27a216de9588
Removing intermediate container 27a216de9588
---> 5425d0ccdc19
Step 5/6 : FROM alpine:latest
---> a24bb4013296
Step 6/6 : COPY --from=stage0 /tmp/apache-zookeeper-3.6.1-bin /opt/
---> c54f88c3c573
Successfully built c54f88c3c573
Successfully tagged zookeeper-multi-stage:3.6.1
Let's have a look at the image history:
$ docker image history zookeeper-multi-stage:3.6.1 --no-trunc
IMAGE CREATED CREATED BY SIZE COMMENT
sha256:c54f88c3c573930fb26ea047d5953a842a702bb39289618f3f3cfc013edf337d About a minute ago /bin/sh -c #(nop) COPY dir:420ba2bd6abb5a0706062b95eb13c8e9c1cb7a7d9c19f1e88eab87c425d65877 in /opt/ 35.3MB
sha256:a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e 2 months ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 2 months ago /bin/sh -c #(nop) ADD file:c92c248239f8c7b9b3c067650954815f391b7bcb09023f984972c082ace2a8d0 in / 5.57MB
As you can see, the image does contain only the layers of the base image (alpine) and the layer of the COPY instruction.
Notes:
-
Assigning a name to a stage build is recommended but not mandatory.
Instead of using the name of the stage build you can reference it by its number.
The first FROM instruction in the Dockerfile has the number 0 and the following will be numbered 1,2,3, and so on.
To reference the first build stage, you do:
COPY --from=0 ...
#stage 0
FROM alpine:latest
RUN apk add --no-cache curl tar
RUN curl -fSL -o /tmp/apache-zookeeper-3.6.1-bin.tar.gz https://archive.apache.org/dist/zookeeper/zookeeper-3.6.1/apache-zookeeper-3.6.1-bin.tar.gz
RUN tar -xzf /tmp/apache-zookeeper-3.6.1-bin.tar.gz -C /tmp/
#final stage
FROM alpine:latest
COPY --from=0 /tmp/apache-zookeeper-3.6.1-bin /opt/
-
It's also possible to copy artifacts from external images (kind of using the image as a stage):
COPY --from=zookeeper:3.6.1 /apache-zookeeper-3.6.1-bin /opt/zookeper
-
You can also use a stage build as a base image for the FROM instruction (
FROM stage0 AS stage1
):
#stage 0
FROM alpine:latest AS stage0
RUN apk add --no-cache curl tar
#stage 1
FROM stage0 AS stage1
RUN curl -fSL -o /tmp/apache-zookeeper-3.6.1-bin.tar.gz https://archive.apache.org/dist/zookeeper/zookeeper-3.6.1/apache-zookeeper-3.6.1-bin.tar.gz
RUN tar -xzf /tmp/apache-zookeeper-3.6.1-bin.tar.gz -C /tmp/
#final stage
FROM alpine:latest
COPY --from=stage1 /tmp/apache-zookeeper-3.6.1-bin /opt/