Docker Multi-Stage Builds Explained
Docker Multi-Stage Builds Explained
Multi-stage builds are one of Docker's most powerful features for producing lean, production-ready images. Instead of shipping your build toolchain alongside your app, you compile in one stage and copy only the final artifact into a minimal runtime image.
The Problem with Single-Stage Builds
A typical single-stage Dockerfile installs compilers, package managers, dev dependencies, and source code — all of which end up in the final image. A simple Node.js app can balloon to 1 GB+ when it only needs 50 MB to run.
Single-stage — everything ships
FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
CMD ["node", "dist/index.js"]
Final image: ~1.1 GB
How Multi-Stage Builds Work
You define multiple FROM statements in one Dockerfile. Each FROM starts a new stage. You can copy files between stages with COPY --from=.
Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
Stage 2: Production
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
CMD ["node", "dist/index.js"]
Final image: ~150 MB
Advanced Patterns
Named Stages for Clarity
Give each stage a meaningful name with AS. This makes COPY --from= self-documenting and lets you target specific stages during development:
docker build --target builder -t myapp:dev .
Separate Test Stage
Insert a test stage between build and production. If tests fail, the build fails before producing the runtime image:
FROM builder AS tester
RUN npm run test
FROM node:20-alpine AS runner
COPY --from=builder /app/dist ./dist
Static Assets with Nginx
For frontend apps, build with Node and serve with Nginx:
FROM node:20-alpine AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
Final image: ~25 MB
Best Practices
package.json copies before source copies so dependency installs are cached..dockerignore — exclude node_modules, .git, and test fixtures from the build context.node:20.11-alpine) rather than latest for reproducible builds.Conclusion
Multi-stage builds let you keep your Dockerfiles readable while producing images that are 5-10x smaller than naive single-stage builds. The build toolchain stays in the build stage, and your production image ships only what it needs to run.