KhueApps
Home/DevOps/Fix Docker builds that reuse cache unexpectedly or ignore --no-cache

Fix Docker builds that reuse cache unexpectedly or ignore --no-cache

Last updated: October 06, 2025

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

  1. Show detailed build logs

    • docker build --progress=plain .
    • Look for [CACHED] markers per step.
  2. 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.
  3. Refresh the base image

    • Always add --pull when you expect updated FROM images.
  4. 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.
  5. 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.
  6. Temporarily disable cache mounts

    • Comment out RUN --mount=type=cache to verify that steps really execute.
  7. Nuke caches as a last resort

    • docker builder prune -af
    • docker buildx prune -af

Pitfalls and concrete fixes

SymptomLikely causeFix
FROM not updatedNo --pullAdd --pull to build command
COPY . always cached.dockerignore excludes changesAdjust .dockerignore
ARG change ignoredARG not used in stepReference ARG in the instructions
Fast RUN with --no-cachecache mount presentRemove or change cache mount
Pruning ineffectiveWrong builderPrune 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.

Series: Docker

DevOps