In this lesson, you will learn how to structure your Dockerfile instructions to maximize layer reuse and significantly shrink your final production image footprint.
The Anatomy of a Build Layer
Every time you run an instruction like RUN, COPY, or ADD in a Dockerfile, Docker creates a new read-only layer. These layers are stacked sequentially; if a layer changes (or any layer before it changes), all subsequent layers must be rebuilt from scratch. In a CI/CD pipeline, this means if you update a line of code at the top of your Dockerfile, your runner might waste time re-downloading dependencies you haven't touched.
Optimizing for Cacheability
To optimize, you should order your instructions from "least frequently changed" to "most frequently changed."
For example, never put COPY . . at the top of your file. If you do, a single character change in a CSS file will invalidate the cache for your entire dependency installation step. Instead, copy only your dependency manifest files first:
# 1. Copy only manifest files
COPY package.json package-lock.json ./
# 2. Install dependencies (cached until manifest changes)
RUN npm install
# 3. Copy application code (frequent changes occur here)
COPY . .
Minimizing Image Size via Multi-Stage Builds
Beyond caching, we must minimize image size to ensure faster pulls and reduced storage costs. Use multi-stage builds to separate your "build environment" from your "runtime environment." By using a builder image to compile code and then copying only the final artifacts into a slim base image (like alpine or distroless), you strip away unnecessary compilers, header files, and caches that the production container doesn't need.
# Stage 1: Build
FROM node:18 AS builder
WORKDIR /app
COPY . .
RUN npm install && npm run build
# Stage 2: Runtime
FROM node:18-alpine
COPY --from=builder /app/dist /app/dist
CMD ["node", "dist/server.js"]
By decoupling the heavy build dependencies from the final artifact, you ensure your pipeline runs faster and your production clusters are not burdened with bloated, insecure image layers.
Key Takeaway: Order your Dockerfile instructions from least-to-most volatile to maximize cache hits, and always use multi-stage builds to discard unnecessary build-time artifacts.