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.
| Concept | What it means | Decision rule |
|---|---|---|
| Layer | Cached filesystem change in an image | Copy lockfiles before source to preserve dependency cache |
| Multi-stage build | Build and runtime use separate stages | Leave compilers and dev dependencies out of runtime |
| PID 1 | Container process receiving lifecycle signals | Use exec-form CMD and graceful shutdown |
Professional workflow
Build and verify Node.js programs from the terminal in small, observable steps.
- Define the container artifact boundary: inputs, outputs, invariants, ownership, and expected failures.
- Design the data or message contract before choosing implementation details.
- Implement the smallest correct path with dependencies passed explicitly.
- Add validation, failure translation, cleanup, and concurrency behavior.
- Verify the boundary with realistic data and at least one adversarial case.
- Measure or observe the behavior before optimizing or extracting abstractions.
Keep the feedback loop short
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.
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
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
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