Docker Multistage Build Use Cases
The Multistage Build feature has made it possible to write extremely concise Dockerfiles.
I was so impressed that I decided to summarize the delightful points for each use case.
Note that it's been a while since I wrote this memo, so this article has become quite outdated. The Multistage Build feature has been available since the 2017/05 release, so it's been almost a year...….
Additionally, there's an article about Multistage Build on the official GCP blog, which includes measurements and is a very interesting read. Please refer to it beforehand, as it explains the first use case in detail.
Use Case 1: Reducing Image Size
This is an indispensable merit when it comes to Multistage Build. By copying the build results (and necessary libraries), you can exclude programs and unnecessary layers required only for building from the image.
This benefit is common to many containers, but it's particularly powerful for Go language programs. This is because Go programs can run with just a single binary copy, making it possible to create extremely lightweight images.
You can now create images of several MB in size, which was previously impossible.
before
FROM alpine:latest as builder
...
// build as command
CMD command
Several hundred MB〜
after
FROM ... as builder
...
// build as command
FROM ...
COPY --from=build command command
CMD command
Several MB〜
Use Case 2: Excluding Programs Used Only in Development Environments
There are programs that are valuable in development environments, such as hot reload tools and debuggers, but are not needed in release environments.
Without Multistage Build, you would need to prepare two Dockerfiles (Dockerfile.dev
and Dockerfile.prod
, etc.). Since the build preparation is the same for both files, you would need to modify both, which would decrease maintainability.
By using Multistage Build, you can exclude programs used only in development environments from the image with a single Dockerfile. The modifications for using docker-compose are also minimal.
Personally, I think this is the most convenient way to use Multistage Build.
before
Dockerfile.dev
FROM ...
// build preparation
// build
// hot reload tool installation
CMD hot-reload command
Dockerfile.prod
FROM ...
// build preparation
// build
CMD command
after
FROM ... as builder
// build preparation
// build
// hot reload tool installation
CMD hot-reload command
FROM ...
COPY --from=build command command
CMD command
Use Case 3: Refactoring
This is a byproduct of the above two use cases, but it makes the dependencies between each stage clear.
For example, consider building two programs and using their results to run a program (such as building plugins or creating local certificates). Previously, to reduce image size, you would build everything in a single layer of the Dockerfile and finally execute it.
With Multistage Build, you can separate the dependencies into each stage, making the Dockerfile more understandable.
before
FROM ...
RUN apk pre-cmd1 pre-cmd2 cmd3 ... && \
build cmd1 ... && \
cd ... && \
build cmd2 ... && \
...
CMD cmd1 cmd2
after
FROM ... as cmd1
RUN apk pre-cmd1 ... && \
build cmd1 ...
FROM ... as cmd2
RUN apk pre-cmd2 ... && \
build cmd2 ...
FROM ...
COPY --from=build cmd1 cmd1
COPY --from=build cmd2 cmd2
CMD cmd1 cmd2
Conclusion
Multistage Build is a very convenient feature that can simplify complex Dockerfiles. Let's aim to create lightweight and easy-to-understand Dockerfiles by effectively utilizing this feature.