Phase 6 · Testing, Delivery & ProductionModule 44~70 min read

Docker, Images & Local Infrastructure

Package Node.js into minimal reproducible images, run as non-root, compose dependencies, manage signals, and scan build artifacts.

What you'll learn

A container image should be reproducible, minimal, non-root, signal-aware, and independent of mutable local state. Use multi-stage builds and Compose for explicit development dependencies.

By the end of this lesson, you'll be able to:

  • Build a multi-stage Node image
  • Run as a non-root user
  • Handle PID 1 signals correctly
  • Compose local infrastructure with health checks

Core mental model

Node.js becomes easier when you separate the JavaScript language from the runtime and the operating-system capabilities it exposes. Use this table as a decision guide.

ConceptWhat it meansDecision rule
LayerCached filesystem change in an imageCopy lockfiles before source to preserve dependency cache
Multi-stage buildBuild and runtime use separate stagesLeave compilers and dev dependencies out of runtime
PID 1Container process receiving lifecycle signalsUse exec-form CMD and graceful shutdown

Professional workflow

Build and verify Node.js programs from the terminal in small, observable steps.

  1. Define the container artifact boundary: inputs, outputs, invariants, ownership, and expected failures.
  2. Design the data or message contract before choosing implementation details.
  3. Implement the smallest correct path with dependencies passed explicitly.
  4. Add validation, failure translation, cleanup, and concurrency behavior.
  5. Verify the boundary with realistic data and at least one adversarial case.
  6. Measure or observe the behavior before optimizing or extracting abstractions.

Keep the feedback loop short

Run the smallest useful command after every meaningful change. Read the complete error message before editing again, and keep inputs and outputs visible while you learn.

Guided code lab

Build a small non-root runtime

A locked install and build happen separately; the runtime contains only production artifacts and uses exec-form startup.

Dockerfile
FROM node:24-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build && npm prune --omit=dev

FROM node:24-alpine AS runtime
ENV NODE_ENV=production
WORKDIR /app
COPY --from=build --chown=node:node /app/node_modules ./node_modules
COPY --from=build --chown=node:node /app/dist ./dist
COPY --from=build --chown=node:node /app/package.json ./
USER node
CMD [`node`, `dist/server.js`]

Production practice

Contract

The image pins its base, locks dependencies, exposes no baked secrets, runs non-root, defines startup, health, resource, and shutdown behavior.

Verification

Build from clean cache, scan image and SBOM, run read-only where possible, send SIGTERM under load, and start with Compose dependencies unavailable.

Operations

Use immutable digests, resource limits, writable temp mounts, health probes, secret injection, and regular base-image rebuilds.

Common failure mode

Using shell-form CMD or npm wrappers can prevent termination signals reaching Node promptly, breaking graceful shutdown.

Independent workshop

Containerize the typed API plus PostgreSQL and Redis development services.

Your finished workshop must include:

  • Multi-stage Dockerfile
  • .dockerignore
  • Non-root runtime
  • Compose health dependencies
  • Signal test
  • Image scan report

Definition of done

Run the happy path and at least two edge cases, keep responsibilities separated, and add a short README explaining how to run the program.

Recap & quick check

Key takeaways

  • Images are immutable artifacts
  • Lockfiles make builds repeatable
  • Runtime stays minimal
  • PID 1 handles signals
  • Containers do not embed secrets

Quick check

1. Why use multiple stages?

2. Why exec-form CMD?

3. Where should secrets enter?

Next: CI/CD & Release Automation