Ship and operate

Build a production image

Use a multi-stage build to separate compilation tools from the smaller runtime filesystem delivered to production.

8 minute lesson

~~~

A build stage may need TypeScript, test tools, and a compiler. The running application normally needs only production dependencies and compiled output.

Each FROM begins a stage. COPY --from selects the artifacts that cross into the runtime image. This reduces size and prevents build-only files from becoming part of the deployed filesystem.

FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm test && npm run build

FROM node:22-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
CMD ["node", "dist/server.js"]

The stage boundary is an explicit allowlist. Only paths named by COPY --from enter the runtime stage. Inspect the final image rather than assuming the boundary worked: list its filesystem, review docker history, and run the same smoke request you used in development.

Multi-stage builds can also reveal portability mistakes. Native modules compiled against one operating system or CPU architecture must match the runtime stage. Keep build and runtime families compatible, build for the production platform, and test that exact image. A smaller runtime image reduces unused tools and attack surface, but it still needs vulnerability scanning, a non-root user, and a repeatable way to rebuild when the base image changes.

Adapt the pattern to a TypeScript version of the Notes API and inspect the final image history.

Lesson completed

Take this course offline

Get every free book and course as PDF and EPUB files.

Get the download library →