Docker BuildKit and buildx: Cache Mounts, Secrets and Multi-Platform Images
What is BuildKit #
BuildKit is the engine that builds your images. It builds independent stages in parallel, skips stages nobody needs, transfers only the files that changed and adds features that the old builder did not have: cache mounts, secret mounts, SSH forwarding and multi-platform builds. It is the default in current Docker, and Rancher Desktop uses it for both docker build and nerdctl build.
docker buildx is the CLI front end for the extra features. Features such as RUN --mount need nothing special in the command, only in the Dockerfile.
Cache mounts: reuse downloads between builds #
Package managers download the same files on every build when a dependency changes. A cache mount gives a RUN instruction a persistent directory that is not saved in the image:
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]Change one line of requirements.txt and pip finds the rest of the packages already downloaded in the cache. The same idea for other tools:
# Node.js
RUN --mount=type=cache,target=/root/.npm npm ci
# Go
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
go build -o /out/server .
# apt (Debian): keep the lists and archives in the cache
RUN rm -f /etc/apt/apt.conf.d/docker-clean
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
Secret mounts: never bake a secret into a layer #
A token passed with ARG or ENV, or copied with COPY, stays in the image history forever, even if you delete it in a later layer. A secret mount makes the secret available to one RUN as a file and leaves nothing behind:
# syntax=docker/dockerfile:1
FROM node:22-slim
WORKDIR /app
COPY package*.json ./
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm ci
COPY . .$ docker build --secret id=npmrc,src=$HOME/.npmrc -t myapp .
Check that nothing leaked:
$ docker history --no-trunc myapp | grep -i npmrc # nothing
For private Git repositories use an SSH mount: RUN --mount=type=ssh git clone git@github.com:org/private.git and build with docker build --ssh default ..
Multi-platform images #
Your Apple Silicon laptop builds arm64 images, but your EKS nodes or Fargate tasks may run amd64. Running the wrong architecture fails with exec format error. Build the platform you deploy to, or both:
$ docker buildx build --platform linux/amd64 -t myapp:1.0 .
$ docker buildx build --platform linux/amd64,linux/arm64 -t registry.example.com/myapp:1.0 --push .
There are three ways to produce the other architecture:
-
QEMU emulation: no changes to the Dockerfile, but much slower, especially for compilation. Install the emulators once if your system does not have them:
docker run --privileged --rm tonistiigi/binfmt --install all. -
Several native builders: one
amd64node and onearm64node joined in a single builder, the fastest option for CI:$ docker buildx create --use --name mybuild ssh://user@amd64-host $ docker buildx create --append --name mybuild ssh://user@arm64-host -
Cross-compilation in a multi-stage Dockerfile, which needs no emulation at all:
FROM --platform=$BUILDPLATFORM golang:1.24 AS build ARG TARGETOS ARG TARGETARCH WORKDIR /src COPY . . RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o /out/server . FROM gcr.io/distroless/static-debian12:nonroot COPY --from=build /out/server /server ENTRYPOINT ["/server"]The build stage runs natively on your machine (
$BUILDPLATFORM) and only the output targets another platform.
Cache in CI #
Local caches vanish on a clean CI runner. Export the build cache to a registry so the next build can import it:
$ docker buildx build \
--cache-from type=registry,ref=registry.example.com/myapp:buildcache \
--cache-to type=registry,ref=registry.example.com/myapp:buildcache,mode=max \
-t registry.example.com/myapp:1.0 --push .
Check your Dockerfile #
$ docker build --check . # lints the Dockerfile with the built-in rules
Next steps #
Make sure what you ship is safe: Docker image security and scanning.