Cache Mounts and Build Secrets
Two BuildKit-only RUN --mount modes fix the two worst build-time habits. --mount=type=cache gives a RUN step a persistent directory — pip's wheel cache, apt's package cache — that survives across builds without ever becoming a layer, so dependency installs stop re-downloading the world. --mount=type=secret exposes a file, the Postgres password, to one RUN step at build time and to nothing else.
The secret never lands in a layer, never appears in docker history, and never becomes an ENV — which is exactly the fix for the ARG-baked-secret footgun from Chapter 4 topic 25. Both mounts depend on the # syntax line and on BuildKit being the active builder (topic 27); on a current Docker, they already are.
--mount=type=cache — Persistent Build Caches
RUN --mount=type=cache,target=/root/.cache/pip pip install … mounts a directory that BuildKit keeps between builds. The downloaded wheels persist outside the image, so the next build reuses them instead of re-fetching, and none of it becomes a shipped layer. The cache lives in BuildKit's own storage, attached to the step only while it runs and detached the instant it finishes.
# syntax=docker/dockerfile:1
FROM python:3.12 AS builder
WORKDIR /app
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
pip wheel --wheel-dir /app/wheels -r requirements.txt
The /root/.cache/pip directory exists only during this step and persists in BuildKit between builds; it is not part of the image's filesystem. A second build with an unchanged requirements.txt finds the wheels already downloaded and skips the network entirely.
apt and Package Caches
The same mount on /var/lib/apt/lists and /var/cache/apt lets apt-get reuse package indexes across builds. This replaces the old "always rm -rf /var/lib/apt/lists in the same RUN" dance, because the cache now lives in the mount, not the layer — there is nothing to clean up in the image, since nothing was ever written to it.
--mount=type=secret — Build-Time Secrets
RUN --mount=type=secret,id=db_password … exposes the secret as a file — by default /run/secrets/db_password — for that one step only. You pass it from outside with docker build --secret id=db_password,src=./db_password.txt, and the value is gone the instant the step finishes: no layer, no history, no ENV.
# syntax=docker/dockerfile:1
FROM python:3.12 AS builder
WORKDIR /app
COPY . .
RUN --mount=type=secret,id=db_password \
DB_PASSWORD="$(cat /run/secrets/db_password)" python manage.py migrate
You build it with docker buildx build --secret id=db_password,src=./db_password.txt .. The password is readable at /run/secrets/db_password while the migration runs and nowhere afterward — the mount is torn down with the step, leaving no trace in the layer or in docker history.
Why This Replaces the ARG Footgun
Chapter 4 topic 25 showed a password passed via ARG surviving in docker history and the layer cache for anyone to read. The secret mount makes the value available only inside the running RUN step and writes nothing persistent — which is the correct fix, not a workaround. An ARG is a build-time variable that Docker records; a secret mount is a file that exists only for one step and is never recorded at all.
--mount=type=cache--mount=type=secretRUN step at build time. The value lands in no layer and never appears in docker history — torn down the instant the step ends.The Driftwood Build, Secured
Driftwood's builder stage needs the database password only to run a migration step at build time. It reads it via --mount=type=secret,id=db_password, uses a --mount=type=cache for pip so repeat builds skip re-downloading wheels, and the final image's docker history shows neither the password nor the cache. The password is supplied as a build secret, exactly as the chapter promised.
Runtime secrets — the password the running container needs to actually connect to Postgres — are a separate mechanism handled in Chapter 6 and Chapter 10. A build secret solves the build-time leak; it does not feed a long-lived process.
Build ARG — a build-time variable Docker records. Its value is visible in docker history, persists in the layer cache, and is trivially extractable from the image. Use it for non-sensitive build-time configuration: a version number, a feature flag, a mirror URL. Never put a credential in one.
Build secret (--mount=type=secret) — a file mounted for a single RUN step that leaves nothing in any layer or in history. It is the only correct way to give a credential to the build. Use it for anything you would be unhappy to publish — the database password, an API token, a private registry credential.
- Passing the database password via
--build-arg DB_PASSWORD=…and trusting it is gone —docker historyshows the value and it sits in the build cache; this is the exact Chapter 4 footgun, unfixed. - Echoing a mounted secret into a file or
ENVinside theRUNstep "to use it later" — that write becomes a layer and re-leaks the secret the mount was protecting. - Putting a cache mount's contents on the critical path for correctness — a cache mount is best-effort and can be empty or pruned, so the build must still succeed (just slower) when the cache is cold.
- Combining a cache mount with an in-layer
rmof the same cache directory — thermdoes nothing useful because the cache was never in the layer, and you may be deleting the mount you wanted to keep warm. - Forgetting the
# syntax=docker/dockerfile:1line and writing--mount=type=secret— it fails to parse, because these mounts are BuildKit frontend features (topic 27).
- Deliver every build-time credential through
--mount=type=secret, never throughARGorENV, so nothing sensitive reaches a layer ordocker history. - Add
--mount=type=cacheto pip and apt install steps, so dependency downloads persist across builds without bloating the image. - Keep cache mounts strictly an optimization — write the
RUNso it still produces a correct image when the cache is empty. - Pass secrets from a file at build time (
--secret id=…,src=…) and keep that source file out of the build context via.dockerignore, so it never enters the image by either path.
RUN --mount modes
Kaniko its own caching and a different secret-handling approach for in-cluster builds
Podman · Buildah supports --secret build secrets with compatible syntax
Buildpacks · ko manage dependency caching internally, no exposed RUN --mount
Knowledge Check
What does --mount=type=cache persist, and why does it never become a layer?
- A directory BuildKit keeps between builds — pip wheels, apt indexes — that lives outside the image, so it ships in no layer
- A layer that BuildKit automatically detects and deletes from the image after the build finishes
- The application's runtime data directory, mounted into the build so user uploads and database rows survive container restarts and image rebuilds
- The build secret value, so later builds can reuse the same stored credential
How does --mount=type=secret expose a value, and what does it leave behind?
- As a file readable by one
RUNstep, torn down when the step ends — leaving nothing in any layer or indocker history - As an environment variable injected at the top of the build and available to every subsequent
RUNandCOPYstep until the build finishes - As a file copied into a persistent layer so later
RUNsteps can read it - As a value recorded in
docker historyfor later audit purposes
Why is a build secret the correct fix for the ARG-in-history leak?
- An
ARGis recorded in history and the cache; a secret mount writes nothing persistent, so the value is never exposed - A build secret runs under BuildKit while an
ARGcan only run under the legacy builder - The build secret is encrypted inside the layer with a per-build key and decrypted on pull, while the
ARGis stored there in plaintext for anyone to read - A build secret is automatically available to the running container at runtime too
Why do these mounts require BuildKit and the # syntax line?
- They are BuildKit frontend features; without the
# syntaxline an older daemon can't parse--mount, and the legacy builder has no such instruction - The
# syntaxline pins the base image digest that the cache and secret mounts depend on, so the mounted paths resolve to the same files on every rebuild across machines - BuildKit needs the line to encrypt the cache and secret directories on disk
- Setting
DOCKER_BUILDKIT=0in the environment is what enables the mount instructions
You got correct