SyntaxStudy
Sign Up
Docker Layer Caching and Build Optimisation
Docker Beginner 1 min read

Layer Caching and Build Optimisation

Docker's layer cache is invalidated for an instruction and all subsequent instructions whenever the instruction itself changes or — for COPY and ADD — the source files change. This means instruction order is a performance-critical decision: instructions that change infrequently should come first. For Node.js applications, copy package.json and package-lock.json and run npm ci before copying the application source. This way, the expensive dependency installation layer is cached as long as the lock file does not change. Changing a single line of application code does not trigger a re-install. The .dockerignore file prevents unwanted files from entering the build context and potentially invalidating cache. Including node_modules/ is essential: local modules built on macOS are binary-incompatible with the Linux container, and their presence in the context would invalidate the npm install layer whenever any module file changes.
Example
# INEFFICIENT: deps reinstall on every code change
# COPY . .
# RUN npm ci

# EFFICIENT: deps cached separately from code
FROM node:20-alpine

WORKDIR /app

# Layer 1: OS packages (rare change)
RUN apk add --no-cache dumb-init

# Layer 2: package files (changes when deps update)
COPY package.json package-lock.json ./

# Layer 3: npm install (cached until layer 2 changes)
RUN npm ci --omit=dev

# Layer 4: app source (changes on every commit)
COPY src/ ./src/

EXPOSE 3000
ENV NODE_ENV=production
ENTRYPOINT ["dumb-init","--"]
CMD ["node","src/server.js"]

# .dockerignore contents:
# node_modules/
# .git/
# .env*
# *.test.js
# coverage/
# docs/