Docker build cache isn’t working, causing painfully slow rebuilds because the builder thinks it needs to re-execute every instruction when it doesn’t.
Common Causes and Fixes
-
Instruction Order and Cache Invalidation: Docker caches layers based on the instruction and its arguments. If an instruction that shouldn’t change (like
RUN apt-get update && apt-get install -y ...) is placed after an instruction that does change frequently (likeCOPY . .), the entire cache from that point onward is invalidated.-
Diagnosis: Examine your
Dockerfile. Look for instructions that are frequently re-executed. -
Fix: Reorder your
Dockerfileto place frequently changing instructions (likeCOPY) later, and stable instructions (like package installations) earlier.# BAD: COPY invalidates RUN apt-get COPY . . RUN apt-get update && apt-get install -y some-package # GOOD: apt-get runs from cache if nothing changes before it RUN apt-get update && apt-get install -y some-package COPY . . -
Why it works: Docker builds layers sequentially. If a layer’s instruction and its arguments haven’t changed since the last build, Docker reuses the cached layer. By moving
COPYlater, it only invalidates the cache for theCOPYinstruction and subsequent ones, not for earlier, stable operations like package installation.
-
-
ADDwith Remote URLs: UsingADDto fetch files from a remote URL can cause cache misses if the URL’s content changes, even if the URL itself remains the same. Docker doesn’t cache the content of the URL independently; it caches based on the instruction.-
Diagnosis: Check if you’re using
ADDwith URLs. -
Fix: Use
curlorwgetwithin aRUNinstruction instead ofADDfor remote files.# BAD: ADD can invalidate cache on remote content change ADD https://example.com/file.tar.gz /tmp/ # GOOD: RUN instruction allows for explicit caching control and better debugging RUN curl -sSL https://example.com/file.tar.gz | tar xz -C /tmp/ -
Why it works:
RUNinstructions are executed by a shell, and Docker caches the layer based on the exact command string. By usingcurland piping totar, you make the operation a single, reproducible step that Docker can cache effectively.
-
-
Changed Build Context: The build context (the directory sent to the Docker daemon) includes all files and subdirectories. If any file within the build context changes, and an instruction like
COPY . .orADD . .is present, it can invalidate the cache for thatCOPY/ADDlayer and all subsequent layers.-
Diagnosis: Observe if small, unrelated file changes trigger long rebuilds.
-
Fix: Use
.dockerignoreto exclude unnecessary files and directories from the build context. Be specific about what youCOPYorADD.# GOOD: Only copy necessary files COPY app/ /app/ # .dockerignore .git node_modules *.log -
Why it works: Reducing the build context size and excluding files that don’t affect the build means fewer potential cache invalidations. Explicitly copying specific directories or files, rather than the entire context (
.), makes the dependency clearer to Docker’s cache.
-
-
File Timestamps: Docker’s cache invalidation for
COPYandADDinstructions considers file content and timestamps. If you’re copying files that are generated or modified during the build process (e.g., usingnpm installand thenCOPY package*.json .), and the timestamps are inconsistent, it can lead to cache misses.-
Diagnosis: Look for
COPYorADDoperations on files that are also modified by subsequentRUNcommands. -
Fix: Ensure files copied into the image have consistent, predictable timestamps, or perform operations in an order that minimizes timestamp dependencies. For generated files, consider building them within the container.
# GOOD: Copying package files first, then installing, then copying app code COPY package*.json ./ RUN npm install COPY . . -
Why it works: By copying the
package.jsonandpackage-lock.jsonfirst and runningnpm install, you create a layer for dependencies. Only if these specific files change will thenpm installlayer be invalidated. Then, copying the rest of the application code invalidates subsequent layers. This is more granular than copying everything at once.
-
-
BuildKit Not Enabled or Configured Incorrectly: Docker’s newer builder, BuildKit, offers more advanced caching mechanisms, including remote caching and better cache invalidation strategies. If you’re not using it or it’s misconfigured, you won’t benefit from these improvements.
-
Diagnosis: Check your Docker version (
docker version). EnsureDOCKER_BUILDKIT=1is set in your environment orbuildx.buildkit=truein~/.docker/config.json. -
Fix: Enable BuildKit.
- Environment Variable:
export DOCKER_BUILDKIT=1 docker build . - Docker Daemon Configuration (Docker Desktop): Go to Settings -> Features in development -> Enable BuildKit (experimental).
docker-compose.yml:version: '3.8' services: myapp: build: context: . dockerfile: Dockerfile args: BUILDKIT_INLINE_CACHE: 1 # Optional, for faster cache pulls # ...
- Environment Variable:
-
Why it works: BuildKit is a more intelligent builder. It understands build graphs better, can perform more operations in parallel, and has features like secret management and improved caching that go beyond the legacy builder.
-
-
docker buildvs.docker buildx build: Whiledocker buildx builduses BuildKit by default and offers more features, sometimes issues can arise if you’re mixing usage or have specific buildx configurations that interfere with caching.-
Diagnosis: If you’re using
buildxand experiencing cache issues, check your builder instances (docker buildx ls) and their configurations. -
Fix: Ensure you’re using a consistent builder and that its cache settings are as expected. For example, to ensure you’re using the default builder:
docker buildx use default docker buildx build --platform linux/amd64 -t myimage . -
Why it works:
buildxmanages multiple builders, and each can have its own cache configuration (e.g., local vs. remote). Explicitly selecting and configuring your builder ensures you’re leveraging the caching mechanism you intend to.
-
After fixing these, the next error you’ll likely encounter is a "no space left on device" error if your cache has grown too large and you haven’t pruned it.