Docker BuildKit Cache Mounts: How to Speed Up Builds
A BuildKit cache mount (RUN --mount=type=cache) keeps package-manager and compiler directories across builds, separate from image layer cache. Even when a layer rebuilds, unchanged packages can stay in the mount.
This article covers cache mounts only from the official Dockerfile and Optimize cache docs—not a Docker layer primer.
Where do you use type=cache?
One-line answer: Attach --mount=type=cache,target=<package-cache-path> to RUN steps that install or compile (npm, Go, pip, apt).
# syntax=docker/dockerfile:1
FROM node:latest
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci
COPY . .
Go:
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
go build -o /app/hello
Apt (needs exclusive access):
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get --no-install-recommends install -y gcc
Cache contents persist between builder invocations without invalidating the instruction cache. Builds must still succeed with an empty or overwritten cache; GC may reclaim space. Mount contents are not committed into the final image.
What are id and sharing?
One-line answer: id names distinct cache buckets; sharing is shared (default), private, or locked.
| Option | Meaning |
|---|---|
id | Cache identity; defaults to target. Use separate ids per language/stage |
target | Mount path inside the container (required) |
sharing=shared | Concurrent writers (default); fine for many npm/Go caches |
sharing=locked | Second writer waits; required for apt-style lock files |
sharing=private | New mount when multiple writers appear |
ro / readonly | Read-only |
uid / gid / mode | Ownership/mode for a new cache directory |
Do not mix npm and Go into one bucket just because targets differ—set explicit ids. Parallel multi-stage apt installs need sharing=locked (or private) to avoid lock contention.
What should you watch in CI?
One-line answer: Ephemeral runners may drop local BuildKit mounts—pair Dockerfile cache mounts with --cache-from / --cache-to, and keep apt on locked.
Checklist:
- New VMs each job — local
type=cachemay vanish with the builder. Export layer cache to registry/GHA while still using cache mounts for package downloads inside the Dockerfile. - Parallel jobs, same id — serialize apt with
sharing=lockedor give each job its ownid. - Empty-cache correctness — docs require the build to work when GC or another build clears the mount.
- No secrets in cache mounts — use
type=secretfor tokens. - Pin syntax —
# syntax=docker/dockerfile:1for stable--mountsupport.
Locally, BuildKit must be enabled (DOCKER_BUILDKIT=1 or buildx).
Wrap-up
Speed package installs with ① RUN --mount=type=cache → ② pick id/sharing (apt=locked) → ③ add external cache in CI. See RUN —mount=type=cache and Optimize cache usage.