Docker — build cache invalidated on every build
Fix repeated cache invalidation by ordering Dockerfile layers and avoiding changes that invalidate early layers.
Symptoms
- Every build re-downloads dependencies
- COPY . . early in the Dockerfile invalidates everything
- The build takes the same long time regardless of changes
- A cache miss appears right after a source edit
Possible causes
- A COPY or ADD of volatile files precedes dependency installation
- COPY . . copies changing files before the expensive step
- Build arguments or labels change between builds
- Files such as lockfiles change frequently and appear early
Diagnosis steps
Read the build output for the invalidated step
docker build --progress=plain . 2>&1 | grep -i "CACHED\|CACHE"Interpretation
The step that lost its cache is the first change that invalidated later layers; fixing its inputs restores caching.
Reorder to copy manifests first
COPY package.json pnpm-lock.yaml ./ && RUN pnpm installInterpretation
Installing dependencies before copying source keeps the heavy layer cached while source changes.
Check what changes between builds
git diff --name-only HEAD~1Interpretation
Identifying volatile files copied early guides what to exclude or defer to later layers.
Root cause
A layer that changes frequently runs before an expensive layer, so any change to its inputs invalidates the cached dependency install and forces a full rebuild.
Fix
Order layers from least to most frequently changing, copy dependency manifests and install before copying source, and use a .dockerignore to keep volatile files out of early layers.
Verify the fix
Rebuild after a source-only change and confirm the dependency layers report CACHED rather than re-running.
Symptom walkthrough
Builds take the same long time every run because the cache is never reused, and output shows steps re-executing instead of hitting cache. This is a layer-ordering problem: if a frequently changing file is copied before the expensive dependency install, every source edit invalidates the install and everything after it. Build arguments and changing labels can also bust the cache unexpectedly.
Fix in practice
Order the Dockerfile from least to most volatile: copy the dependency manifest and install first, then copy source. Add a .dockerignore so files like local caches and logs do not enter early layers, and keep build arguments stable. This keeps the heavy install cached across ordinary code changes.
Verification
Edit a source file and rebuild, then confirm the dependency layers report CACHED instead of re-running. Reused cache on a trivial change confirms the ordering is right.
Diagnostic commands
Check the behavior with commands like these:
docker build --progress=plain . 2>&1 | grep -i "CACHED\|CACHE"
COPY package.json pnpm-lock.yaml ./ && RUN pnpm install
git diff --name-only HEAD~1