CONFIG.SYS
  • ALL_POSTS.BAT
  • ABOUT.EXE

Docker multi stage builds - Tue, Jan 11, 2022

Using multi stage builds to reduce image size

Using multi state builds to reduce image size

Multi stage builds are around since quiet a while in docker. In short they offer a way to reduce the size of a runtime image by allowing the usage of different images for building, then using only artifacts needed at runtime while discarding the rest (e.g. compiler).
An example would be building a binary using an sdk image, and after building copying only the binary to the runtime image.
This post shows an example how big these reductions can be.

Build without multi stages

In my recent cloud project, the k8s runtime uses Kyverno to enforce some security related policies. For example “containers must be required to run as non-root users” .
In order to test the conformance of the k8s resources as early as possible, I wanted to do these checks as part of a CI cycle . Thus gaining feedback much early as oppose to deployment to the cluster.
Therefore I needed the Kyvernso CLI in a docker image. The Dockerfile plain approach would look something like this:

FROM golang:alpine

RUN apk add git make gcc libc-dev

RUN git clone --depth 1 https://github.com/kyverno/kyverno
RUN cd kyverno && make cli

ENTRYPOINT ["/go/kyverno/cmd/cli/kubectl-kyverno/kyverno"]
CMD ["--help"]

The base for the image is an alpine golang image augmented with the needed build tools, and the cli is build using make. Building the image is straight forward and the resulting size is about ~3GB:

podman build --tag "kyverno-no-multi-stage-build" .
...
podman images kyverno-no-multi-stage-build
REPOSITORY                              TAG         IMAGE ID      CREATED        SIZE
localhost/kyverno-no-multi-stage-build  latest      26c8c65a8400  7 minutes ago  2.91 GB

How come the size is that big ? First of all the base image alone is around 300 MB and adding the required build libs adds an additional 140 MB. In addition kyverno’s build does produce quiet a lot of artifacts having a size of 2.43 GB. The detailed layer size is shown below:

podman image tree kyverno-no-multi-stage-build
Image ID: 26c8c65a8400
Tags:     [localhost/kyverno-no-multi-stage-build:latest]
Size:     2.909GB
Image Layers
├── ID: 8d3ac3489996 Size: 5.866MB
├── ID: 0230d85b843e Size:   769kB
├── ID: 1afb07c068f3 Size:  2.56kB
├── ID: 337b50a59b37 Size:   318MB
├── ID: 369ac6dc732b Size: 3.072kB Top Layer of: [docker.io/library/golang:alpine]
├── ID: af50ae034e40 Size: 140.8MB
├── ID: 5854b398eff0 Size: 13.35MB
└── ID: 3c07e807a13e Size: 2.431GB Top Layer of: [localhost/kyverno-no-multi-stage-build:latest]

While this image can be used perfectly, cleaning up build artifacts, only keeping those needed would of course reduce the size considerably. A cleaned up version would still be around 500 MB.

Build with multi stages

Creating an equivalent image using multiple stages is done by building the cli using the golang image and copying only the executable to the runtime image, a very small alpine:

FROM golang:alpine as builder

RUN apk add git make gcc libc-dev

RUN git clone --depth 1 https://github.com/kyverno/kyverno
RUN cd kyverno && make cli

FROM alpine

WORKDIR /workdir

COPY --from=builder /go/kyverno/cmd/cli/kubectl-kyverno/kyverno /usr/bin

ENTRYPOINT ["kyverno"]
CMD ["--help"]

The key points here are the two FROM statements. The first one runs all following commands in an image containing all that is needed for building. After building however we switch to the alpine image using a second FROM statement. And finally we only copy the binaries needed to the target image. We can see that the size of the image is much smaller:

podman build --tag "kyverno-multi-stage-build" .
...
podman images kyverno-multi-stage-build
REPOSITORY                           TAG         IMAGE ID      CREATED         SIZE
localhost/kyverno-multi-stage-build  latest      86d0d669f0b1  19 seconds ago  128 MB

And the layer structure shows that the kyverno cli actually uses most of the size:

podman image tree kyverno-multi-stage-build
Image ID: 86d0d669f0b1
Tags:     [localhost/kyverno-multi-stage-build:latest]
Size:     128.5MB
Image Layers
├── ID: 8d3ac3489996 Size: 5.866MB
├── ID: 67a04a8023ad Size: 2.909MB
├── ID: a4fdf72de125 Size: 30.71MB
├── ID: 904d07a6808e Size: 4.608kB
├── ID: 3ec6f7d80887 Size: 8.869MB Top Layer of: [docker.io/library/python:alpine]
└── ID: bca841cc4570 Size: 80.09MB Top Layer of: [localhost/kyverno-multi-stage-build:latest]

Conclusion

We have shown that image sizes can be reduced by using “Multi stage builds”. Size reduction and the effectiveness of this pattern mostly depend on the size of the build tools that need to be used for building.

Back to Home


21st century version | © Thomas Reuhl 2025 | Disclaimer | Built on Hugo

Linkedin GitHub