Faster, Safer Docker Builds with BuildKit Cache and Secret Mounts
BuildKit's cache mounts and secret mounts eliminate redundant dependency downloads and keep credentials out of image layers — here's how to use them in real Dockerfiles and CI pipelines.
16 Sept 2026, 06:47 UTC

The Problem: Slow Rebuilds and Leaked Credentials
Every Docker user knows the pain: you change one line of application code, and the next docker build re-downloads all your npm, pip, or go dependencies because the package-manager cache lives inside a layer that gets invalidated. Worse, teams still bake API tokens or SSH keys into images by writing them in RUN commands or ARG values, only to discover them later in docker history or a registry scan.
BuildKit, the default builder since Docker 23.0, solves both problems with two mount types that exist only at build time: cache mounts for reusable package caches and secret mounts for sensitive values that never touch the filesystem of the final image.
How Cache Mounts Keep Dependency Downloads Off the Critical Path
A cache mount binds a host-managed directory into the build container at a path you choose. The directory persists across builds but is not snapshotted into any layer. When the build finishes, the cache stays behind for the next run.
Add the BuildKit syntax directive at the top of your Dockerfile, then wrap the install command with --mount=type=cache:
# syntax=docker/dockerfile:1.4
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci --prefer-offline --no-audit
COPY . .
CMD ["node", "server.js"]
The target path must exist inside the container; npm creates /root/.npm automatically, but for tools that don't, add RUN mkdir -p /cache/path before the mount. The sharing attribute controls concurrency: locked (default) serializes writers, shared allows parallel reads, private gives each build its own copy.
On the second build, npm ci finds a warm cache and finishes in seconds instead of minutes. No layer stores the downloaded tarballs, so image size stays small.
Secret Mounts: Credentials That Vanish After the Build
Secret mounts expose a file or stdin stream at /run/secrets/<id> during the RUN command only. The file is mounted with tmpfs and never written to the container's rootfs or the build cache.
# syntax=docker/dockerfile:1.4
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt ./
RUN --mount=type=secret,id=pypi-token \
pip install --index-url https://user:${PYPI_TOKEN}@pypi.example.com/simple -r requirements.txt
ENV PYPI_TOKEN=""
COPY . .
CMD ["python", "app.py"]
Pass the secret at build time:
echo "$PYPI_TOKEN" | docker buildx build --secret id=pypi-token,src=- -t myapp .
The src=- reads from stdin; you can also use src=/path/to/file or src=$ENV_VAR. Inside the RUN command, read the secret from the mounted file instead of echoing it into the environment, which would leak it into build logs.
Worked Example: Multi-Stage Go Build with Module Cache and Private Repo Token
This Dockerfile demonstrates both mounts in a realistic pipeline. The first stage downloads Go modules into a cache mount; the second stage uses a secret mount for a GitHub token to fetch a private module.
# syntax=docker/dockerfile:1.4
FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=secret,id=github-token \
GOPRIVATE=github.com/myorg/* \
go mod download
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=secret,id=github-token \
CGO_ENABLED=0 go build -o /app ./cmd/server
FROM alpine:3.20
COPY --from=builder /app /app
ENTRYPOINT ["/app"]
Build command:
docker buildx build \
--secret id=github-token,src=$HOME/.github/token \
--platform=linux/amd64,linux/arm64 \
-t myorg/server:latest .
Run docker image history myorg/server:latest — you'll see no layers for the module cache or the token. The final image contains only the static binary and the Alpine base.
Trade-offs and Operational Gotchas
- Cache growth is unbounded. BuildKit does not garbage-collect cache mounts automatically. Schedule
docker builder prune --filter until=72hor set--max-cache-sizeon the BuildKit daemon to avoid filling the host disk. - Secrets can still leak via logs. If a command writes the secret to stdout/stderr (e.g.,
RUN echo $SECRET), it appears indocker build --progress=plainoutput. Always read secrets from the mounted file. - Mount target must exist. A missing directory fails silently — the mount appears empty. Create it explicitly in the Dockerfile.
- Syntax version matters. Cache mounts require
# syntax=docker/dockerfile:1.2+; secret mounts need1.4+. Older CI runners defaulting to the legacy builder will ignore these directives.
Verify It Works in Your Pipeline
- Confirm BuildKit is active:
docker versionshould listbuildkitunder Client/Server components. - Run the build twice. The second run should skip dependency downloads. Check
docker builder duto see cache size. - Inspect the final image with
docker image history <image>— secret and cache mounts leave no layers. - For secret mounts, add a temporary
RUN cat /run/secrets/<id>during development to verify the file appears, then remove before merging.
Adopting these two mounts is a low-risk, high-reward change: faster local iteration, smaller images, and credentials that never ship to production. Start with one npm ci or go mod download in your next PR and measure the difference.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.