Reducing Docker Image Build Times With BuildKit Cache Mounts
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 <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 \
npm ci --prefer-offline
COPY . .
RUN npm run build
Node.js / Yarn
RUN \
yarn install --frozen-lockfile
Node.js / pnpm
RUN \
pnpm install --frozen-lockfile
Python / pip
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN \
pip install -r requirements.txt
COPY . .
Go Modules
FROM golang:1.22-alpine
WORKDIR /app
COPY go.mod go.sum ./
RUN \
go mod download
COPY . .
RUN \
go build -o /bin/app ./...
Go tip: Always mount both
/go/pkg/modAND/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 \
cargo build --release
apt / Debian packages
RUN \
apt-get update && apt-get install -y --no-install-recommends \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
Key Options Reference
| Option | Values | When to Use |
|---|---|---|
target | path | Required. The directory to cache |
id | string | Isolate caches between stages or projects |
sharing | shared, locked, private | Use locked for package managers that don't handle concurrency |
uid / gid | int | When running as non-root |
Scoping Caches with id
Prevent cache collisions between different projects or Dockerfile stages:
RUN \
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 \
npm ci --omit=dev
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN \
npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS final
WORKDIR /app
COPY /app/node_modules ./node_modules
COPY /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 downloadbut forget it ongo build. You'll get cache misses on the actual compilation. - Wrong cache directory — check where your package manager actually writes cache. Run
pip cache dirornpm config get cacheif unsure. - Using
sharing=sharedwith package managers — parallel builds can corrupt the cache. Uselockedfor 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
-
COPYlock files beforeRUN install(layer cache still matters) - Cache mount added to every
RUNthat installs dependencies - Correct
targetpath for your package manager -
idset if you have multiple projects or stages -
sharing=lockedfor package managers with write locks
Cache mounts are one of those optimizations with essentially zero downside. Add them once, forget about slow rebuilds.
Was this article helpful?
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
Docker "Permission Denied" On Mounted Volumes: Fix Unix Socket And File Ownership Errors
If you've spent more than five minutes with Docker volumes, you've seen it. That infuriating wall of red text: Or worse, the cryptic Unix socket variant: Y...
Fix Docker Container DNS Resolution Failures
Resolve DNS lookup failures inside Docker containers — fix resolv.conf, custom networks, and upstream DNS configuration issues.
Fix Docker 'No Space Left on Device' Error
Reclaim disk space when Docker fills your drive — clean up dangling images, stopped containers, unused volumes, and build cache.
Docker Multi-Stage Builds: Shrink Images by 80% for Production
Cut Docker image sizes by up to 80% using multi-stage builds — practical patterns for Go, Node.js, and Python apps in production.
Docker CLI Cheat Sheet
Essential Docker CLI commands organized by task — build images, run containers, manage volumes and networks, compose services, and debug.
Docker Multi-Stage Builds for Production-Ready Minimal Images
Shrink Docker images from 1.2GB to 45MB using multi-stage builds. Production Dockerfiles for Node.js, Go, and Python with real size comparisons.