Supercharge Docker Builds with BuildKit Mount Cache: From 10 Minutes to 30 Seconds

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

The Nightmare Called “Waiting for Docker Build…”

We’ve all been there—waiting dozens of minutes for a Docker build just to fix a single log line. Watching npm install or pip install crawl along while re-downloading hundreds of megabytes of old data is incredibly frustrating.

Normally, we optimize Dockerfiles by separating the package.json copy step to leverage Layer Caching. However, this approach has a fatal flaw: if you add even one tiny library, Docker treats the entire layer as changed and wipes the cache. As a result, the system has to re-download everything from scratch.

BuildKit and its Mount Cache feature were created to solve this problem once and for all. It allows you to persist package manager cache folders (npm, pip, go mod) across multiple builds, even when configuration files change. In my own project, build time dropped from 8 minutes to under 1 minute after applying this technique.

Enabling BuildKit – High Performance Mode

BuildKit is the next-generation build engine integrated into Docker. If you’ve been using Docker Desktop since version 2.4.0, it’s enabled by default. For those on Linux or older versions, we need to explicitly tell Docker to use this engine.

To quickly enable it for your current session, run the following command:

export DOCKER_BUILDKIT=1

To make it permanent, add the configuration to your /etc/docker/daemon.json file:

{
  "features": { "buildkit": true }
}

Don’t forget to restart Docker after editing the file. When building, if you see the log interface displaying parallel stages and looking more professional, you’ve succeeded.

How to Configure Mount Cache for Each Language

The power lies in the --mount=type=cache flag within the RUN command. It creates a temporary directory on the host machine and mounts it into the container during the build. This directory is preserved across different builds.

1. For Node.js (npm/yarn)

With Node.js, the node_modules directory and npm’s global cache are the biggest time sinks. Instead of fresh downloads, we can mount the npm cache directory like this:

# Old way: RUN npm install
# Optimized with BuildKit:
RUN --mount=type=cache,target=/root/.npm \
    npm install

If your project uses Yarn, change the target path:

RUN --mount=type=cache,target=/usr/local/share/.cache/yarn \
    yarn install

Now, when you add a new package to package.json, npm will only download that specific package. Existing libraries will be pulled directly from the mount cache, saving hundreds of megabytes of bandwidth.

2. For Python (pip)

Every pip install -r requirements.txt run can be a long wait. You can persist the pip cache with the following configuration:

RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

Note: If you run Docker with a non-root user, adjust the target path to that user’s home directory to avoid permission errors.

3. For Go (Golang)

Go has an excellent caching mechanism, but by default, it gets wiped after each image build. Go requires two types of cache: GOCACHE (for compilation results) and GOMODCACHE (for dependencies).

RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    go build -o app main.go

On the second build, compilation speed will be surprisingly fast because Go only processes the code that actually changed.

Tips for Inspecting and Managing Cache

When applying this technique, I usually watch the build logs closely to ensure everything is working as expected. If you see the CACHED label appearing at installation steps or execution time dropping from minutes to seconds, you’re on the right track.

Sometimes complex mount point configurations can be confusing. I often use the JSON Formatter at toolcraft.app to reformat manifest files or Docker JSON logs. This helps me scrutinize mount points and layers without needing heavy extensions.

To clean up memory after continuous building, use the following command:

docker builder prune

This command deletes old, unused caches, freeing up disk space on your server.

Important Considerations for CI/CD Environments

Mount Cache is powerful, but it isn’t a “silver bullet.” Keep these three points in mind:

  • Security: Never store secrets or sensitive information in cache directories, as they can persist on the build host for a long time.
  • CI Systems: On GitHub Actions, you must use docker/build-push-action with cache-from and cache-to of type gha. Otherwise, every time CI runs on a new runner, Mount Cache will have no data to use.
  • Storage: Cache can quickly balloon to dozens of gigabytes if your project has many dependencies. Set up periodic cleanup scripts.

Optimizing Docker builds doesn’t just reduce developer stress; it also significantly cuts CI/CD server operating costs. Try enabling BuildKit and configuring Mount Cache today—you’ll definitely be satisfied with the results.

Share: