How it works
The explanation, with examples you can run.
Mounts for a single step
A RUN --mount attaches something to one step without storing it in the layer. The first line, # syntax=docker/dockerfile:1, opts into the current Dockerfile frontend, which supports all of these.
# syntax=docker/dockerfile:1FROM node:22-alpineWORKDIR /appCOPY package.json package-lock.json ./# A persistent npm cache, a throwaway /tmp, and a secret: none of them land in the layerRUN --mount=type=cache,target=/root/.npm \ --mount=type=tmpfs,target=/tmp \ --mount=type=secret,id=npmrc,target=/root/.npmrc \ npm ci --omit=devCOPY . .CMD ["node", "server.js"]docker build --secret id=npmrc,src=$HOME/.npmrc -t hello:1 .- Cache mounts keep package-manager or compiler caches between builds, so a changed lockfile reinstalls from a warm cache instead of the network, and the cache stays out of the image.
- tmpfs mounts give a step a scratch directory that vanishes afterwards. Check what your steps leave behind: in the plain Dockerfile from the Dockerfile topic, the
npm cilayer is 2.72 MB for a 28 kBnode_modules, because npm (Node 22) writes about 2.6 MB of compile cache to/tmp/node-compile-cache. With the mounts above, the same layer is 36.9 kB. - Secret mounts expose a file (or an environment variable) only while that step runs. Never use
ARGorENVfor tokens: they persist in the image and its history. - SSH mounts (
--mount=type=ssh) forward your SSH agent to clone private repositories without copying keys.
Multi-platform images
An image name can point to an image index with one manifest per platform. To build one, list the platforms. Docker Desktop and Engine 29+ with the containerd image store can hold the result locally; older setups need a docker-container builder or --push.
There are three ways to produce the foreign variants: emulation with QEMU (simple but slow, and it must be installed), several native builder nodes, or cross-compilation. Cross-compiling is fastest where the toolchain supports it:
# syntax=docker/dockerfile:1FROM --platform=$BUILDPLATFORM golang:1.27-alpine AS buildARG TARGETOS TARGETARCHWORKDIR /srcCOPY . .RUN --mount=type=cache,target=/root/.cache/go-build \ CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /out/hello . FROM gcr.io/distroless/static-debian13:nonrootCOPY --from=build /out/hello /helloENTRYPOINT ["/hello"]docker buildx build --platform linux/amd64,linux/arm64 -t hello-go:multi .docker image ls --tree# IMAGE DISK USAGE CONTENT SIZE# hello-go:multi 17.8MB 6.53MB# ├─ linux/amd64 3.38MB 3.38MB# └─ linux/arm64 14.4MB 3.15MBBUILDPLATFORM, TARGETOS and TARGETARCH are filled in automatically. The Go compiler runs natively on the arm64 Mac for both variants; only the final stages, which just copy a file, are per platform. Without --platform=$BUILDPLATFORM, this same build failed on a Desktop with no amd64 emulation, because the amd64 build stage could not execute the Go toolchain at all.
Builds in CI
Every CI runner starts with an empty cache. Export it and import it on the next run, for example to the registry (--cache-to type=registry,ref=...,mode=max and --cache-from) or to the GitHub Actions cache (type=gha). mode=max stores the intermediate stages too, which is what makes multi-stage builds fast in CI.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.