Marcio Cunha

Slow Docker builds? How to use remote cache in CI/CD

Tired of waiting minutes for Docker builds in your pipeline? Learn how to speed up continuous integration using BuildKit and remote registries.

Marcio Cunha•4 min
Also available in:PortuguêsEspañol
Summary
  • BuildKit replaces the standard Docker builder with native parallelism and remote layer caching support.
  • CI/CD pipelines without remote caching must download and compile all dependencies from scratch on every commit.
  • Modern container registries can store metadata and intermediate layers for rapid reuse.
  • Proper configuration of export and import parameters reduces build times by over eighty percent.
  • Keeping images lean and ordering instruction files optimizes cache hit rates in distributed environments.

Why does your Docker pipeline feel so slow?

Whenever we push new code to the repository, the continuous integration server (the automated system that tests and packages software) usually starts a task from scratch. In practice, this means it creates a completely clean environment, downloads all source codes, installs heavy libraries, and builds the container image as if it were the first time. This repetitive process consumes precious minutes from the team and creates massive queues on build servers. To make matters worse, if you have ever faced the frustration of seeing a process fail due to a timeout after ten minutes of waiting, you know exactly how big this operational bottleneck is in daily development.

Many teams try to solve this by purchasing more expensive and powerful CI/CD servers, but the problem is rarely a lack of raw processor power. The real villain is the system's digital amnesia: with every commit, it throws away all previous work. If you already deal with local servers and infrastructure tools, you might know that managing disk space and cleaning orphaned files in Docker is essential, but in the cloud server the challenge is the opposite. We need to save what has already been done to avoid repeating useless effort.

Understanding BuildKit and layer caching

Traditional Docker builds images linearly, executing each instruction line sequentially. Whenever a line changes, all subsequent lines must be reprocessed. This is where BuildKit comes in, Docker's modern build engine that brings real parallelism, better dependency management, and, most importantly, native support for remote caches. In practice, BuildKit can slice the process into small pieces and check which parts are already stored in the cloud before trying to create them again.

Unlike local cache that gets trapped on a specific machine's hard drive and disappears as soon as the task finishes, remote cache lives in a place accessible to any build server. When a developer makes a simple tweak to the system text, the pipeline does not need to recompile the language base or download giant libraries again. It simply looks up the corresponding layer in the remote registry (the repository where we keep ready images), snaps the modification on top, and finishes the job in a few seconds, ensuring much faster deliveries.

How to configure BuildKit to use remote registries

To take advantage of this technology in your CI/CD environment, we need to instruct the build engine to send and fetch intermediate layers directly from your image registry, such as Docker Hub, GitHub Container Registry, or GitLab Registry. In the terminal or inside your pipeline configuration file, we activate BuildKit by defining a simple environment variable and then adjusting the cache export and import parameters. In practice, we tell the system: look for existing cache here and save the new cache over there.

Below we have a classic example of a command used in pipeline configuration files, such as GitHub Actions or GitLab CI, to accomplish this feat automatically:

name: Build Docker Image
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Set up Docker BuildKit
        uses: docker/setup-buildx-action@v3
      - name: Build and push with remote cache
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: my-registry.com/my-app:latest
          cache-from: type=registry,ref=my-registry.com/my-app:cache
          cache-to: type=registry,ref=my-registry.com/my-app:cache,mode=max

In this configuration block, the cache-from parameter retrieves existing history, while cache-to sends the new layers generated during execution. Using the max mode ensures that the system saves the largest possible number of intermediate layers, maximizing the chances of reuse in future runs. Just like when we guarantee service stability by configuring automated infrastructure routines, automating the cache eliminates the human factor and manual errors from the process.

Best practices to maximize cache hit rates

Configuring the tool is only the first step; the way you write the Docker instruction file (the Dockerfile) determines whether the cache will be used or ignored. The build engine evaluates each line based on the command text and copied files. If you place the command that copies the entire project source code in the first few lines, any tiny change in a text file will instantly invalidate the cache for all library installations below it.

The golden rule is to organize instructions from least frequent to most frequent. First, pull the base image, install heavy operating system packages, and copy only project dependency files (like Node.js or Python package files). Only after that should you copy the rest of the application code. This way, if you only change the system's business logic, Docker skips the entire time-consuming library installation step and focuses only on what changed, saving time and network bandwidth on the integration server.

Conclusion

Slowness in Docker build processes is not an inevitable evil that we must live with in modern software development. By combining the power of BuildKit with the persistence of a remote cache in container registries, we transform a slow and frustrating pipeline into an extremely agile workflow. This simple change frees up valuable time from the technical team, reduces costs with continuous integration servers, and drastically accelerates the delivery cycle of new features to end users.

Adopting this strategy requires only initial planning in the structure of build files and correct configuration of the CI/CD environment. It is worth reviewing your current pipelines, applying cache import and export parameters, and observing the immediate productivity gain. With optimized time, your team gains more focus on what really matters: writing quality code and solving real business problems.