DevOpsil

Reducing Docker Image Build Times With BuildKit Cache Mounts

Aareez AsifAareez Asif4 min read

Reducing Docker Image Build Times with BuildKit Cache Mounts

Quick Reference: BuildKit Cache Mounts

If your Docker builds are slow, cache mounts are probably the single highest-leverage fix available. I've seen Node.js image builds drop from 4 minutes to 45 seconds just by adding two lines to a Dockerfile. Here's everything you need to know, fast.


Enable BuildKit First

Before anything else, make sure BuildKit is active:

# One-time environment variable
export DOCKER_BUILDKIT=1

# Or set it permanently in Docker daemon config
# /etc/docker/daemon.json
{
  "features": { "buildkit": true }
}

# Or use the buildx command directly (always uses BuildKit)
docker buildx build .

The Core Syntax

RUN --mount=type=cache,target=<cache-directory> <your-command>

That's it. The target is where the package manager stores its cache. BuildKit persists this directory between builds — it's not part of the image layer.


Language-Specific Cache Mount Recipes

Node.js / npm

FROM node:20-alpine

WORKDIR /app
COPY package*.json ./

RUN --mount=type=cache,target=/root/.npm \
    npm ci --prefer-offline

COPY . .
RUN npm run build

Node.js / Yarn

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

Node.js / pnpm

RUN --mount=type=cache,target=/root/.local/share/pnpm/store \
    pnpm install --frozen-lockfile

Python / pip

FROM python:3.12-slim

WORKDIR /app
COPY requirements.txt .

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

COPY . .

Go Modules

FROM golang:1.22-alpine

WORKDIR /app
COPY go.mod go.sum ./

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

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

Go tip: Always mount both /go/pkg/mod AND /root/.cache/go-build. The second one caches compiled intermediates — skipping it leaves 40-60% of the speedup on the table.

Rust / Cargo

FROM rust:1.78-slim

WORKDIR /app
COPY Cargo.toml Cargo.lock ./

RUN --mount=type=cache,target=/usr/local/cargo/registry \
    --mount=type=cache,target=/app/target \
    cargo build --release

apt / Debian packages

RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
    --mount=type=cache,target=/var/lib/apt,sharing=locked \
    apt-get update && apt-get install -y --no-install-recommends \
    curl \
    git \
    && rm -rf /var/lib/apt/lists/*

Key Options Reference

OptionValuesWhen to Use
targetpathRequired. The directory to cache
idstringIsolate caches between stages or projects
sharingshared, locked, privateUse locked for package managers that don't handle concurrency
uid / gidintWhen running as non-root

Scoping Caches with id

Prevent cache collisions between different projects or Dockerfile stages:

RUN --mount=type=cache,id=myapp-npm,target=/root/.npm \
    npm ci

Multi-Stage Build Pattern

Cache mounts work per-stage. Name them explicitly:

FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,id=app-npm,target=/root/.npm \
    npm ci --omit=dev

FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,id=app-npm,target=/root/.npm \
    npm ci
COPY . .
RUN npm run build

FROM node:20-alpine AS final
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
CMD ["node", "dist/index.js"]

Cache Management Commands

# View current BuildKit cache usage
docker buildx du

# Prune build cache (be careful in CI)
docker buildx prune

# Prune only cache older than 48 hours
docker buildx prune --filter until=48h

# Keep cache but free a specific amount
docker buildx prune --keep-storage=5GB

CI/CD Considerations

Cache mounts are local to the build daemon. In ephemeral CI environments (GitHub Actions, GitLab CI), combine cache mounts with registry caching:

docker buildx build \
  --cache-from type=registry,ref=ghcr.io/myorg/myapp:buildcache \
  --cache-to type=registry,ref=ghcr.io/myorg/myapp:buildcache,mode=max \
  -t myapp:latest \
  --push .

Cache mounts still help here — they accelerate the build during a run when multiple RUN steps share the same cache target.


Common Mistakes

  • Forgetting the mount on the build step — declare it on go mod download but forget it on go build. You'll get cache misses on the actual compilation.
  • Wrong cache directory — check where your package manager actually writes cache. Run pip cache dir or npm config get cache if unsure.
  • Using sharing=shared with package managers — parallel builds can corrupt the cache. Use locked for anything that writes during install.
  • Expecting cache mounts in the final image — they don't exist there. That's a feature, not a bug.

The 30-Second Checklist

  • BuildKit enabled
  • COPY lock files before RUN install (layer cache still matters)
  • Cache mount added to every RUN that installs dependencies
  • Correct target path for your package manager
  • id set if you have multiple projects or stages
  • sharing=locked for package managers with write locks

Cache mounts are one of those optimizations with essentially zero downside. Add them once, forget about slow rebuilds.

Share:

Was this article helpful?

Aareez Asif
Aareez Asif

Senior Kubernetes Architect

10+ years orchestrating containers in production. Battle-tested opinions on everything from pod scheduling to service mesh. I've seen clusters burn and helped rebuild them better.

Related Articles

DockerQuick RefBeginnerNeeds Review

Docker CLI Cheat Sheet

Essential Docker CLI commands organized by task — build images, run containers, manage volumes and networks, compose services, and debug.

Sarah Chen·
3 min read

Discussion