vsupalov

What Multi-Stage Builds Can Be Good For

Multi-stage builds are simple in principle, but the benefits they bring can be surprising. Let’s look at a few useful things you can do with Docker multi-stage builds.

Reduce Redundant Build Effort

Are you rebuilding your frontend assets every time a new backend package is installed?

In the spirit of Docker layers, multi-stage builds can be a good advanced tool to reduce the amount of duplicate work needed. Especially for production builds, where you just need the final artifacts.

If a part of your build workflow only depends on some subsets, you can process them in an own stage, and copy the results over to a final image. This way, the hard work only needs to be repeated when those dependencies change, and not if there’s any change to the project.

Note: You can get similar benefits by using BuildKit cache mounts.

Make Intermediate Image Layers Shareable

Multi-stage builds can help to share intermediate image layers with your team. You’ll need to tag them right, and make sure to push them. It can pay off though!

Pullable intermediate images can help to make the first developer experience more pleasant. Especially if it’s a small download instead of a long from-scratch building time.

Keep Final Images Small And Dockerfiles Readable

In the past, you had the choice: readable Dockerfiles or large images. Keeping your Docker images clean of unneeded parts usually meant multi-line RUN instructions, with lots of && everywhere.

It was an effort to install required packages, perform a complete build and remove all unneeded traces in the same line. Nope, the Docker cache did not help to make those lines faster most of the time.

With multi-stage builds, you can keep all dependencies which are only needed during the build in an intermediate build stage.

Your final image does not need to have your dev tooling, or compiler parts or a complicated node environment in it! Install all you want into an intermediate image, and only copy the final results into your final image. You can even skip all those cleanup steps in your Dockerfile. If you don’t push your intermediate images, its size on disk doesn’t matter much. Less hassle, less size, easier readability.

Keep Your Secrets Safe

Multi-stage builds were a popular tool to keep secrets out of final images. They still are! But there are better ways to handle build-time secrets and SSH credentials by now if you want to give BuildKit a try.

Simplify Your Image Stages Internally

You can have a single “starting” image stage, where you install common dependencies, and reuse them for different final image stages - be it for different architectures or different images.

However, this is where my personal spidey-sense starts tingling. You see, if your Dockerfiles start getting too fancy, and you might begin considering an templating tool for your Dockerfiles, it might be time to…

Notice You Might Have Deeper Lying Problems

Are you using Docker for the right tasks? It’s a versatile tool, and it doesn’t shine for all use-cases. Sometimes, people try to do too much with multi-stage builds or their Dockerfiles.

Problems which can be addressed with Docker, but are better solved outside of it.

If your Dockerfiles are really long, you have huge images and very long build times, chances are that multi-stage builds are only a part of the solution you are looking for.

Organically grown environments where nobody has an overview anymore, managing unreliable external dependencies, trying to get around proper dependency pinning…

Maybe it’s time to start building your own internal packages, and address deeper lying issues with your workflows. Sound fundamentals can be more impactful if addressed individually, instead of trying to squeeze everything into a single Docker image build tree. But that’s a topic for another time.