Multi-Stage Dockerfiles
Multi-Stage Dockerfiles
One Dockerfile per service, each ending in a slim runtime stage:
FROM node:20-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-slim
ENV NODE_ENV=production
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY package*.json ./
RUN npm ci --omit=dev
USER node
CMD ["node", "dist/server.js"]
Copy package*.json before the source so dependency installs cache until manifests change. Add a .dockerignore (node_modules, .git, local env files) to keep the context small and secrets out of layers. Never bake credentials into RUN — layers persist in image history.
Compose Configuration That Ships
Compose Configuration That Ships
services:
api:
build: ./api
env_file: [.env]
stop_grace_period: 30s
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://localhost:3000/health').then(r=>{if(!r.ok)process.exit(1)})"]
interval: 15s
retries: 3
deploy:
resources:
limits: { cpus: "1.0", memory: 512M }
env_file keeps config out of the repo; .env.example documents required keys. stop_grace_period must exceed your app's shutdown drain, or Compose kills in-flight requests. Validate before shipping with docker compose config, then bring it up with docker compose up -d --build.
Deploy and Verify
Deploy and Verify
Deploy is a loop you must observe, not a command you fire and forget:
docker compose up -d --build
docker compose ps # health, not just "running"
docker compose logs -f api # startup errors surface here
docker compose exec api node # inspect the live process
Pitfalls: tagging everything latest, so a rollback pulls unknown bits — tag with the git SHA; secrets passed as build args, which leak into history — use runtime env or Docker secrets; ignoring SIGTERM, so every deploy drops connections — handle it and match stop_grace_period; and skipping CPU/memory limits, letting one bad service starve the host. In CI, run docker compose config to catch drift before it reaches a server.
Quiz
1. Why copy package.json before the rest of the source in a Dockerfile?
2. What does stop_grace_period control?
3. Which practice makes a rollback reproducible?
Flashcards
Question
Multi-stage build
Click to reveal answer
Answer
Compile in a builder stage, copy artifacts into a slim runtime stage — small images, no toolchain shipped.
Question
Layer-cache rule
Click to reveal answer
Answer
Order Dockerfile steps from least- to most-frequently changing; COPY manifests before source.
Question
Graceful shutdown in Compose
Click to reveal answer
Answer
Handle SIGTERM and set stop_grace_period longer than your drain time.
Question
docker compose config
Click to reveal answer
Answer
Renders and validates the final config — run it in CI to catch YAML/env mistakes.
Revision Notes
Key Takeaways
- 1. Multi-stage builds plus .dockerignore keep images small and secret-free
- 2. Health checks, env_file, and resource limits make the stack production-shaped
- 3. Deploy = up, then verify via ps/logs/exec before declaring success
Interview Tips
- • Walk through your layer ordering and name the cache benefit of each COPY
- • Explain the shutdown sequence: SIGTERM, drain, grace window, SIGKILL — and how you sized it
Cheat Sheet
Multi-stage + .dockerignore; manifests before source for cache; compose: env_file, healthcheck, stop_grace_period, limits. Deploy by SHA tag; verify ps/logs/exec; validate with compose config.