Marcio Cunha

Slow pipeline? How to speed up Docker builds with cache

Learn how to use BuildKit and cache mounts in Docker to eliminate long wait times in continuous integration pipelines and speed up software delivery.

Marcio Cunha•4 min
Also available in:PortuguêsEspañol
Summary
  • Docker BuildKit transforms image creation by processing build steps in parallel intelligently.
  • Cache mounts prevent the repetitive downloading of dependencies on every minor source code update.
  • CI environments require external cache persistence so different servers can leverage previous history.
  • Configuring the Dockerfile with mount parameters requires only simple adjustments to build instructions.
  • Gaining speed in pipelines reduces operational costs and improves the team's delivery agility.

Why does your continuous integration pipeline take so long to run?

Anyone working with software development has experienced the frustration of pushing a small adjustment to a repository and waiting long minutes just for the continuous integration system to download libraries that already existed on their local machine. This constant delay happens because the traditional container building mechanism treats each step as an isolated block, forgetting what happened right after and discarding the temporary package directory. In practice, this means that with every new code push, the disconnected server starts the work from scratch, downloading heavy dependencies like npm packages, Ruby gems, or Python modules that haven't changed at all.

To make matters worse, cloud build servers are usually disposable, meaning they are born clean, execute the process, and disappear right after, taking any trace of useful history with them. When we scale this operation to dozens of developers pushing code throughout the day, the wasted time accumulates into precious hours of productivity thrown away. To solve this chronic inefficiency, the modern container ecosystem's build tool introduced advanced parallelism and selective persistence features that radically change how we handle dependencies. Before moving on to solutions, it is worth reviewing how to optimize the underlying operating system as discussed in the guide on how to speed up builds and containers on the Linux Kernel.

The secret engine behind speed: understanding BuildKit

BuildKit is Docker's modern compilation engine that replaced the legacy builder, bringing an execution model based on directed acyclic graphs. Simply put, instead of executing each command line by line rigidly, BuildKit analyzes the entire instruction file from end to end and figures out which steps can run simultaneously, ignoring blocks that haven't undergone any changes. This intelligent architecture allows the engine to leverage intermediate layers much more precisely, avoiding processing waste on repetitive tasks.

However, even with the parallelism gain, there is a persistent bottleneck that traditional layer caching cannot solve on its own: the internal folder where package managers store files downloaded from the internet. When the installation command runs, it downloads files to a temporary folder inside the container, and upon completion of that specific instruction, the layer is frozen and that temporary folder disappears to free up space. This is precisely where cache mounters come in, allowing specific directories to survive between different build runs without polluting the final image output destined for production.

How cache mounts work in practice

Cache mounts act as a direct tunnel connecting your build server's disk to a specific folder inside the container during the exact moment your installation command is running. To enable this feature in your instruction file, you need to enable modern BuildKit syntax at the top of the file and use the advanced mount command on the installation line. In practice, this ensures that the package manager checks what is already stored in the local cache before trying to download anything from the network, saving bandwidth and wait time.

To understand the operational impact of this strategy in high-demand environments, it is worth checking out the detailed approach on ephemeral Kubernetes runners architecture for continuous integration builds, where state and cache management makes all the difference. Below, see a practical example of how to structure a configuration file using cache mounting for Node.js projects, ensuring local package folders are preserved:

syntax=docker/dockerfile:1
FROM node:18-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci
COPY . .
RUN npm run build

Challenges of external cache in continuous integration environments

Although cache mounters work perfectly on a developer's machine where the disk remains the same every day, the story changes completely when the process runs on ephemeral continuous integration servers. In these automated environments, each task can be dispatched to a different virtual machine in the cloud, meaning the cache created a minute earlier might simply not exist on the machine running the next batch of code. To overcome this obstacle, we need to configure external cache storage using remote container registries or compatible storage services.

When we configure BuildKit to export and import cache remotely, the server sends accumulated temporary files directly to a dedicated repository right after compilation ends. The next time the pipeline is triggered on any other machine in the infrastructure, the system downloads this remote cache before starting work, guaranteeing the same speed as a local machine. This strategy eliminates developer idle time and drastically reduces cloud computing resource costs, making the development workflow much more sustainable and predictable. To explore these architectural concepts further in production, see our related technical analysis on How to scale GitLab runners in Kubernetes under high demand.

Final thoughts on container speed and efficiency

Accelerating container creation is no longer an aesthetic luxury; it has become a fundamental requirement to keep engineering teams productive and happy. Combining the BuildKit engine with the intelligent use of cache mounts and remote storage eliminates the biggest operational bottlenecks of modern pipelines, transforming exhaustive waits into instantaneous processes. By applying these techniques in your daily routine, you not only save machine time and cloud infrastructure money, but you also remove frustrating barriers that delay the continuous delivery of value to your application's end users.