Overview
Docker build caching is powerful but can surprise you: layers appear as cached when you expected fresh work, or --no-cache seems ignored. This guide shows how to diagnose, force clean builds, and structure Dockerfiles to avoid confusion.
Quickstart: Force a truly fresh build
Use these commands to eliminate most cache sources:
- Use BuildKit (recommended):
- Linux/macOS: export DOCKER_BUILDKIT=1
- Windows PowerShell: $Env:DOCKER_BUILDKIT = "1"
- Build with no cache and refresh the base image metadata:
docker build --no-cache --pull -t demo:nocache .
- If using buildx (common with BuildKit):
# Show plain logs to see which steps are cached
docker buildx build --progress=plain --no-cache --pull -t demo:nocache .
- If caches are still interfering, prune build caches. Warning: removes caches for all builds.
# Classic builder caches
docker builder prune -af
# Buildx caches (when using docker buildx)
docker buildx prune -af
Minimal working example (why cache can look "ignored")
Files:
Dockerfile
# syntax=docker/dockerfile:1.6
FROM alpine:3.19
ARG MESSAGE=hello
RUN echo "Installing curl" && apk add --no-cache curl >/dev/null
# Only app.txt content affects this COPY layer's cache key
COPY app.txt /app.txt
# This step runs a command and writes a timestamp
# The cache mount speeds downloads but does not skip the step
RUN --mount=type=cache,target=/root/.cache/curl \
sh -c 'echo "Run at: $(date +%s) $MESSAGE" >> /stamp.txt && \
curl -s https://example.com >/dev/null || true'
app.txt
v1
Build and observe:
# First build: runs all steps
docker build -t demo:test .
# Second build: COPY and RUN may show CACHED (unchanged context)
docker build -t demo:test .
# Force rerun of all steps, ignoring build cache
docker build --no-cache -t demo:test .
# See the stamp value change when the RUN executes
docker run --rm demo:test tail -n1 /stamp.txt
Notes:
- With --no-cache, the RUN step executes even if it looks fast. The cache mount only speeds I/O inside the step; it does not skip execution.
- If the base image changed upstream, --pull ensures the FROM line revalidates against the registry.
Why "no-cache" can appear not respected
Common causes and fixes:
Base image not refreshed
- Symptom: FROM appears cached; upstream base has newer digest.
- Fix: add --pull to the build command.
BuildKit cache mounts
- Symptom: RUN steps seem instantaneous with --no-cache.
- Reason: the step still runs, but reads from a persistent cache mount.
- Fix: remove the --mount=type=cache temporarily to verify, or change the cache key/path.
External cache sources with buildx
- Symptom: Steps show CACHED even after pruning classic caches.
- Reason: buildx may use remote or inline caches via --cache-from or prior builds.
- Fix: use buildx with --no-cache, and prune with docker buildx prune -af.
.dockerignore excludes the files you changed
- Symptom: COPY . looks cached though files changed locally.
- Reason: excluded files never enter the context; COPY inputs are unchanged.
- Fix: adjust .dockerignore to include the files that should invalidate the cache.
build-args not referenced where you expect
- Symptom: Passing --build-arg VAR=foo doesn’t invalidate a step.
- Reason: Only instructions that reference ARG VAR participate in cache keys.
- Fix: Move ARG before the steps that must react to it, and reference it in those instructions.
Multi-stage confusion
- Symptom: Changing code in the final stage does not bust earlier stages.
- Reason: Early stages cache independently.
- Fix: Ensure the change affects the stage with the cached step, or use --target to build specific stages.
Wrong builder instance
- Symptom: Pruning has no effect; builds keep using cache.
- Reason: You pruned the classic builder, but your build uses buildx (or another driver).
- Fix: Check docker buildx ls and prune the active builder.
Step-by-step diagnosis
Show detailed build logs
- docker build --progress=plain .
- Look for [CACHED] markers per step.
Confirm the builder in use
- If logs show buildx or BuildKit formatting, you’re on BuildKit.
- docker buildx ls to identify the active builder; prune that one if needed.
Refresh the base image
- Always add --pull when you expect updated FROM images.
Verify the build context
- Run docker build --no-cache --progress=plain . and confirm that COPY steps list only the expected files.
- Fix .dockerignore so changed files enter the context.
Check ARG/ENV scope
- Place ARG instructions before the steps that must react to them.
- Move rarely changing instructions (apt-get update, toolchain install) earlier; frequently changing COPY later.
Temporarily disable cache mounts
- Comment out RUN --mount=type=cache to verify that steps really execute.
Nuke caches as a last resort
- docker builder prune -af
- docker buildx prune -af
Pitfalls and concrete fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| FROM not updated | No --pull | Add --pull to build command |
| COPY . always cached | .dockerignore excludes changes | Adjust .dockerignore |
| ARG change ignored | ARG not used in step | Reference ARG in the instructions |
| Fast RUN with --no-cache | cache mount present | Remove or change cache mount |
| Pruning ineffective | Wrong builder | Prune the active buildx instance |
Performance notes (without breaking correctness)
- Separate dependency and app layers
- COPY only manifest files (e.g., package.json, go.mod) first, install deps, then COPY the rest. This maximizes cache hits where safe.
- Keep the context small
- Use .dockerignore to exclude node_modules, build artifacts, and test data.
- Use BuildKit cache mounts deliberately
- Cache compiler/package-manager directories to speed up installs, but remember they don’t skip execution.
- Multi-stage builds
- Compile in a builder stage; copy only the final artifacts to a thin runtime stage.
- Deterministic steps
- Avoid time-dependent commands in RUN (date) unless you intend to always bust cache.
Tiny FAQ
Does --no-cache delete existing caches?
- No. It only avoids using them for that build. Use docker builder prune -af or docker buildx prune -af to delete caches.
What’s the difference between --no-cache and --pull?
- --no-cache ignores previous build layers. --pull revalidates the base image in FROM against the registry.
How do I disable cache for only one stage?
- With buildx, use --no-cache-filter <stage-name> to turn off cache for selected stages.
Why do logs show CACHED when I changed a file?
- The file may be excluded by .dockerignore, or your COPY does not include that path. Ensure the changed file is in the build context.
Can cache mounts make --no-cache ineffective?
- No. They can make steps fast, but the step still executes when --no-cache is set.