By Sebastian Sigl

Containers enable you to package your application in a portable way that can run in many environments. The most popular container platform is Docker.

This tutorial will explain how to use the Docker build cache to your advantage.

Docker Build Cache

Building images should be fast, efficient, and reliable. The concept of Docker images comes with immutable layers. Every command you execute results in a new layer that contains the changes compared to the previous layer.

All previously built layers are cached and can be reused. But, if your installation depends on external resources, the Docker cache can cause issues.

How to Leverage the Docker Build Cache

To understand Docker build-cache issues, let’s build a simple custom nginx Docker application. Before you build the image, create a Dockerfile that updates libraries and adds a custom startpage:

FROM nginx:1.21.6

# Update all packages
RUN apt-get update && apt-get -y upgrade

# Use a custom startpage
RUN echo '<html><bod>My Custom Startpage</body></html>' > /usr/share/nginx/html/index.html

You can now build the Docker image:

$  docker build -t my-custom-nginx .

=> [1/3] FROM docker.io/library/nginx:1.21.6@sha256:e12...  5.8s
=> [2/3] RUN apt-get update && apt-get -y upgrade           3.6s
=> [3/3] RUN echo '<html><bod>My Custom Startpage...        0.2s

=> exporting to image                                       0.1s
=> exporting layers                                         0.1s
=> writing image                                            0.0s
=> naming to docker.io/library/my-custom-nginx

[+] Building 11.3s (7/7) FINISHED

In this example, I removed some output for readability. If you build the image the first time, you see that it takes quite some time, in my case 11.3s.

One long executing step is apt-get update && apt-get -y upgrade depending on how many dependencies are updated and how fast your internet speed is. It checks for package updates on the operation system and installs them if available.

Now, you execute it again, and you benefit from the Docker build cache:

$ docker build -t my-custom-nginx .

=> [1/3] FROM docker.io/library/nginx:1.21.6@sha256:e1211ac1…   0.0s
=> CACHED [2/3] RUN apt-get update && apt-get -y upgrade        0.0s
=> CACHED [3/3] RUN echo '<html><bod>My Custom Startpage...     0.0s

=> exporting to image                                           0.0s
=> exporting layers                                             0.0s
=> writing image                                                0.0s
=> naming to docker.io/library/my-custom-nginx

Building 1.1s (7/7) FINISHED

This time, the image build is very fast because it can reuse all previously built images. When you customize your startpage in the Dockerfile, you see how the caching behavior is affected:

FROM nginx:1.21.6

# Update all packages
RUN apt-get update && apt-get -y upgrade

# Use a custom startpage
RUN echo '<html><bod>New Startpage</body></html>' > /usr/share/nginx/html/index.html

Now, build the image again:

$ docker build -t my-custom-nginx .

=> [1/3] FROM docker.io/library/nginx:1.21.6@sha256:e1211ac1…   0.0s
=> CACHED [2/3] RUN apt-get update && apt-get -y upgrade        0.0s
=> [3/3] RUN echo '<html><bod>My Custom Startpage...            0.2s

=> exporting to image                                           0.0s
=> exporting layers                                             0.0s
=> writing image                                                0.0s
=> naming to docker.io/library/my-custom-nginx

Building 2.1s (7/7) FINISHED

This time it only rebuilt the last layer because it recognized that the RUN command had changed. But, it reused the intense 2nd build step and did not update operation system dependencies.

The caching behavior is intelligent. Once 1 step needs to rebuild, every subsequent step is built again. Therefore, it’s good to put frequently changing parts at the end of a Dockerfile to reuse previous build layers.

Still, maybe you want to force a rebuild of a cached layer to force a package update. Forcing a rebuild can be necessary because you want to keep your application safe and use the newest updates when available.

How to Use the Docker Build --no-cache Option

There can be different reasons for disabling the build-cache. You can rebuild the image from the base image without using cached layers by using the --no-cache option.

$ docker build -t my-custom-nginx .

=> CACHED [1/3] FROM docker.io/library/nginx:1.21.6@sha256:...  0.0s
=> [2/3] RUN apt-get update && apt-get -y upgrade               3.5s
=> [3/3] RUN echo '<html><bod>My Custom Startpage...            0.2s

=> exporting to image                                           0.1s
=> exporting layers                                             0.0s
=> writing image                                                0.0s
=> naming to docker.io/library/my-custom-nginx

Building 5.5s (7/7) FINISHED

New layers were constructed and used. The docker build runs both commands this time, which comes with an all-or-nothing approach. Either you provide the --no-cache option that executes all commands, or you will cache as much as possible.

How to Use Docker Arguments for Cache-Busting

Another option allows providing a little starting point in the Dockerfile. You need to edit your Dockerfile like this:

FROM nginx:1.21.6

# Update all packages
RUN apt-get update && apt-get -y upgrade

# Custom cache invalidation
ARG CACHEBUST=1

# Use a custom startpage
RUN echo '<html><bod>New Startpage</body></html>' > /usr/share/nginx/html/index.html

You add a CACHEBUST argument to your Dockerfile at the location you want to enforce a rebuild. Now, you can build the Docker image and provide an always different value that causes all following commands to rerun:

$ docker build -t my-custom-nginx --build-arg CACHEBUST=$(date +%s) .

=> [1/3] FROM docker.io/library/nginx:1.21.6@sha256:e1211ac1...    0.0s
=> CACHED [2/3] RUN apt-get update && apt-get -y upgrade           0.0s
=> [3/3] RUN echo '<html><bod>My Custom Startpage...               0.3s

=> exporting to image                                              0.0s
=> exporting layers                                                0.0s
=> writing image                                                   0.0s
=> naming to docker.io/library/my-custom-nginx

Building 1.0s (7/7) FINISHED

By providing --build-arg CACHEBUST=$(date +%s), you set the parameter to an always different value that causes all following layers to rebuild.

Summary

Docker’s build-cache is a handy feature. It speeds up Docker builds due to reusing previously created layers.

You can use the --no-cache option to disable caching or use a custom Docker build argument to enforce rebuilding from a certain step.

Understanding the Docker build cache is powerful and will make you more efficient in building your Docker container.

I hope you enjoyed this article.

If you liked it and feel the need to give me a round of applause or just want to get in touch, follow me on Twitter.

I work at eBay Kleinanzeigen, one of the world’s biggest classified companies. By the way, we are hiring!

References