tezvyn:

Multi-stage Docker builds for lean production images?

AI-drafted, machine-checkedSource: interviewadvanced
WHAT IT TESTS

Docker build optimization and separation of build and runtime.

OUTLINE

build stage compiles/installs, runtime stage copies artifacts only, discards build tools.

SINGLE-STAGE PROBLEM

A naive single-stage Dockerfile installs all dependencies, runs the build, and includes everything in the final image. This ships devDependencies (testing frameworks, linters, TypeScript), build tools (webpack, Babel), and potentially source maps or source code. An attacker with access to the image can read your source and run tests. The image is also bloated.

MULTI-STAGE APPROACH

A multi-stage build uses multiple FROM instructions. The first stage (builder) installs dependencies, runs the build, and produces artifacts. The second stage (runtime) uses a fresh base image, copies only the final application and runtime dependencies, discards everything else. Build tools, devDependencies, and source never reach the final image.

STAGE 1: BUILDER

FROM node:20-alpine AS builder WORKDIR /build COPY package*.json ./ RUN npm ci COPY . . RUN npm run build

This stage installs all dependencies (including dev) and runs the build script, producing an output directory like dist/ or build/.

STAGE 2: RUNTIME

FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY --from=builder /build/dist ./dist EXPOSE 3000 ENV NODE_ENV=production CMD ["node", "dist/server.js"]

This stage installs only production dependencies and copies the compiled output from the builder. No source, no devDependencies.

SIZE AND SECURITY BENEFITS

A typical multi-stage build reduces image size by 50-70%. The builder stage is discarded; its layers don't appear in the final image. All build tools, testing frameworks, and source are invisible in the runtime image. An attacker cannot read source or abuse dev tools. Attack surface shrinks dramatically.

LAYER CACHING

Multi-stage builds also improve rebuild speed. If only source changes, the dependency layer in the builder is cached. The runtime stage's dependencies layer is always fresh and minimal, reducing cache misses.

WHEN MULTI-STAGE IS CRITICAL

For TypeScript applications, you must compile to JavaScript before running. The compiled output is artifacts; source is build-time only. Multi-stage separates these clearly. For any app with devDependencies, multi-stage should be standard practice.

Read the original → docs.docker.com

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.