BuildKit, cache and multi-platform images

Buildx builders, cache mounts that skip dependency downloads, registry-backed caches for CI, and building for other architectures.

BuildKit and builders

BuildKit is the build engine behind modern Docker. The docker driver builds inside the daemon and cannot export cache or cross-compile; the docker-container driver runs a build container with the full feature set, which is what you want for multi-platform work.

docker buildx ls
docker buildx create --name builder --driver docker-container --use
docker buildx inspect --bootstrap        # confirms the driver and platforms

# a Dockerfile can demand a newer frontend for the latest syntax
cat > Dockerfile <<'EOF'
# syntax=docker/dockerfile:1
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm npm ci --omit=dev
COPY . .
CMD ["node", "server.js"]
EOF

docker buildx build -t app:dev --load .   # --load puts it in the local image store
docker buildx du                          # cache usage of the current builder
  • With the docker-container driver the result stays in the builder cache until you ask for it: use --load for the local daemon or --push for a registry.
  • BuildKit builds independent stages in parallel and only runs the steps the final image needs.
  • docker buildx prune reclaims builder cache; the classic docker system prune does not touch it.

Cache mounts and cache backends

MountPurposeEnds up in the image?
type=cachePersistent scratch space for package managersNo
type=bindRead source or config from the build contextNo
type=tmpfsScratch data held in memoryNo
type=secretA credential available only during that RUNNo
COPYReal file content in a layerYes — permanently
# python
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt

# apt: the lists directory is reused between builds
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

# go modules
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=bind,source=go.sum,target=go.sum \
    go mod download
# reuse layers across machines and CI runs
docker buildx build \
  --cache-from type=registry,ref=registry.example.com/team/app:buildcache \
  --cache-to   type=registry,ref=registry.example.com/team/app:buildcache,mode=max \
  --tag registry.example.com/team/app:1.4.2 --push .

# GitHub Actions cache, no registry needed
docker buildx build --cache-from type=gha --cache-to type=gha,mode=max --load .

# local directories, useful on one machine
docker buildx build --cache-from type=local,src=/tmp/cache --cache-to type=local,dest=/tmp/cache .
💡
A cache mount is a build-time optimisation, not storage: nothing written to it is in the image, so you never need to clean it and you must not rely on it being empty. Keep the instruction that populates it above COPY . . so a source edit does not invalidate it.

Building for more than one architecture

# one command, one tag, both architectures, straight to the registry
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  --tag registry.example.com/team/app:1.4.2 \
  --provenance=true --sbom=true \
  --push .

docker buildx imagetools inspect registry.example.com/team/app:1.4.2

# multi-platform cannot use --load: the local store holds one platform per tag
docker buildx build --platform linux/arm64 -t app:arm64 --load .

# native builders in CI are far faster than emulation
docker buildx create --name arm --driver docker-container --node arm --platform linux/arm64
  • --platform with more than one value requires --push (or --output); BuildKit produces a manifest list, which a local image store cannot represent.
  • The second architecture is built under QEMU emulation unless you have a native node, and it can be several times slower — CI minutes add up quickly.
  • --provenance and --sbom attach attestations to the manifest, which scanners and admission controllers can then verify.
  • Only the stages the final image depends on are executed per platform, so a multi-stage build keeps the cross-build cost manageable.

FAQ

Why is 'RUN --mount=type=cache' rejected as an unknown flag?
The Dockerfile is being parsed by an older frontend, or you are on the legacy builder. Add # syntax=docker/dockerfile:1 as the first line and build with BuildKit (default since Docker 23, and DOCKER_BUILDKIT=1 before that).
Are cache mounts shared between parallel builds of the same project?
Yes, and concurrent writers can conflict. Add sharing=locked to serialise access, which is what package managers like apt need, or use sharing=private for isolation.

Registries, tags and image distribution Docker in CI and publishing images

Last refreshed 2026-09-18.