Docker Build Slow and Not Using Cache: 5 Fixes
Docker builds ignoring cache and rebuilding from scratch every time? These 5 fixes — Dockerfile layer ordering, BuildKit cache mounts, .dockerignore issues, and multi-stage optimizations — cut build times by 60-80%.
Docker cache invalidation is subtle. One wrong layer order, one missing .dockerignore entry, or one timestamp issue makes every build start from scratch. Here are the five most common causes and how to fix each.
Why Docker Cache Breaks
Docker cache is sequential and layer-based. When any layer changes, all subsequent layers are invalidated and rebuilt. The key insight: order matters more than content.
Fix 1: Wrong Layer Order (Most Common)
The problem:
# BAD — copies entire source before installing dependencies
FROM node:20-alpine
WORKDIR /app
COPY . . # Invalidated by ANY file change
RUN npm install # Reinstalls all packages every timeEvery time any file changes (including source code, README, .env), the COPY . . layer invalidates the npm install cache.
The fix:
# GOOD — install dependencies first, copy source last
FROM node:20-alpine
WORKDIR /app
# These layers only rebuild when package.json changes
COPY package.json package-lock.json ./
RUN npm ci --only=production
# This layer rebuilds on source changes, but npm install stays cached
COPY . .Python version:
FROM python:3.12-slim
WORKDIR /app
# Copy requirements first — cached until requirements.txt changes
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy source last
COPY . .Go version:
FROM golang:1.22-alpine
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download # Cached until go.mod/go.sum changes
COPY . .
RUN go build -o /app/server ./cmd/serverFix 2: Missing or Wrong .dockerignore
If .dockerignore is missing or incomplete, COPY . . includes files that change frequently (logs, node_modules, .git), constantly invalidating the cache.
Create .dockerignore:
# Version control
.git
.gitignore
# Dependencies (don't copy — rebuild from package files)
node_modules/
vendor/
__pycache__/
*.pyc
.venv/
# Build artifacts
dist/
build/
*.o
*.so
# Environment and secrets
.env
.env.*
*.pem
*.key
# IDE and OS files
.vscode/
.idea/
*.DS_Store
Thumbs.db
# Test files (not needed in production image)
**/*_test.go
**/test/
**/tests/
coverage.out
# Docs
README.md
CHANGELOG.md
docs/
Verify your .dockerignore is working:
# Build with DOCKER_BUILDKIT=1 and check what's being sent
DOCKER_BUILDKIT=1 docker build . --progress=plain 2>&1 | head -20
# Look for "transferring context" — the size should be small (not hundreds of MB)Fix 3: Enable BuildKit and Use Cache Mounts
BuildKit (the modern Docker build engine) provides cache mounts that persist across builds — even on CI.
# Enable BuildKit
export DOCKER_BUILDKIT=1
# Or use docker buildx (always uses BuildKit)
docker buildx build .Cache mounts in Dockerfile:
# syntax=docker/dockerfile:1
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
# --mount=type=cache persists pip cache between builds
# Even on CI with a fresh container, cache is preserved on the host
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
COPY . .For apt packages:
RUN --mount=type=cache,target=/var/cache/apt \
apt-get update && apt-get install -y curl wgetFor npm:
RUN --mount=type=cache,target=/root/.npm \
npm ci --prefer-offlineCache mounts are especially powerful in CI — add them to your GitHub Actions runner and package installs become nearly instant after the first run.
Fix 4: Multi-Stage Build for Faster Final Images
Multi-stage builds keep your final image small and avoid rebuilding heavyweight build tools:
# syntax=docker/dockerfile:1
# Stage 1: Build
FROM golang:1.22-alpine AS builder
WORKDIR /build
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
COPY . .
RUN --mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 go build -ldflags="-w -s" -o server ./cmd/server
# Stage 2: Final image — only the binary
FROM gcr.io/distroless/static-debian12
COPY --from=builder /build/server /server
ENTRYPOINT ["/server"]Result: build image is 500MB+, final image is 10-20MB. CI downloads the small final image.
Fix 5: Cache in CI (GitHub Actions)
GitHub Actions throws away everything after each run. Without explicit caching, Docker layer cache is lost.
name: Build and Push
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to ECR
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: 123456789.dkr.ecr.ap-south-1.amazonaws.com/myapp:${{ github.sha }}
# Cache from/to GitHub Actions cache
cache-from: type=gha
cache-to: type=gha,mode=maxtype=gha uses GitHub's Actions cache storage. mode=max caches all layers including intermediate build stages. First run is slow; subsequent runs use cache.
Alternative: cache in ECR:
cache-from: type=registry,ref=123456789.dkr.ecr.ap-south-1.amazonaws.com/myapp:cache
cache-to: type=registry,ref=123456789.dkr.ecr.ap-south-1.amazonaws.com/myapp:cache,mode=maxQuick Diagnosis
# Check if cache is being used
docker build . --progress=plain 2>&1 | grep -E "CACHED|RUN"
# Lines starting with "CACHED" are cache hits
# Lines starting with "RUN" are cache misses
# Check context size being sent to Docker daemon
docker build . 2>&1 | grep "Sending build context"
# Should be KB, not MB
# Check layer history
docker history myapp:latest --no-trunc
# Force rebuild without cache (to establish baseline)
docker build --no-cache .Summary
| Problem | Fix |
|---|---|
| Dependencies reinstall every build | Move COPY package.json before COPY . . |
| Large context sent to daemon | Add .dockerignore |
| Slow package installs | Use --mount=type=cache |
| Big final images | Multi-stage builds |
| CI cache lost between runs | Add cache-from/cache-to in docker/build-push-action |
Most Docker build speed problems are solved by fixing layer ordering and adding .dockerignore. The rest (cache mounts, CI caching) are optimizations that multiply on top.
More Docker optimization? Read our Docker multi-stage build patterns and Docker security scanning with Trivy.
Today I Fixed
Short real fixes from production — posted daily
Stay ahead of the curve
Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.
Related Articles
AWS ECR Push Denied: no basic auth credentials — Fix
Getting 'no basic auth credentials' or 'denied: Your authorization token has expired' when pushing to AWS ECR? Here are the exact commands to fix authentication for Docker, GitHub Actions, and Kubernetes.
Docker Build Fails in CI But Works Locally — Fix
Your Docker build works perfectly on your machine but fails in GitHub Actions, GitLab CI, or Jenkins. Here's every reason this happens and exactly how to fix it.
Docker Build Taking Too Long — Cache and Speed Fixes (2026)
Docker builds taking 10+ minutes every time? Here's how to fix layer caching, use BuildKit properly, and cut build times by 80% with multi-stage builds and cache mounts.