Docker Best Practices
A condensed summary of the 25 most important Docker practices for Node.js teams - drawn from every page in this section.
How to Use This List
- Add CI gate: fail build on critical CVEs in the production image
- Review Dockerfile changes with the same rigor as application code
- Pair with CI/CD Best Practices for scan + promote flow
-
Multi-stage builds always: Build TypeScript in
build; runnode dist/main.jsinprod- Multi-Stage Builds. -
npm ci --omit=devin runtime stage: Reproducible production dependency graph only. -
Never
npm installin Docker: Lockfile drift breaks prod parity. -
Comprehensive
.dockerignore: Excludenode_modules, tests,.git,.env- Image Slimming. -
Default base
node:24-bookworm-slim: glibc compatibility for native modules. -
Evaluate Alpine only with CI matrix: musl breaks
sharp,prisma,bcrypt- Distroless & Alpine Trade-offs. -
Pin base image digests:
FROM node:24-bookworm-slim@sha256:...prevents surprise rebuilds. -
Run as non-root:
USER nodeafterchown -R node:node /app- Non-Root Containers. -
Bind
0.0.0.0and usePORTenv: Required for K8s, ECS, Cloud Run. -
One process per container: Separate API and worker images.
-
/healthfor liveness: No database calls; fast 200 - Health & Readiness Probes. -
/readyfor readiness: Check primary DB with short timeout. -
Startup probe for slow boots: Migrations and cache warm before liveness.
-
Fail readiness on SIGTERM drain: Stop new traffic before shutdown - Graceful Shutdown.
-
No secrets in layers: Inject via orchestrator secrets at runtime.
-
No
.envinCOPY: Use.dockerignoreand CI build args only for non-secret metadata. -
Compile native modules inside Linux build stage: Never copy host
node_modules. -
Scan images in CI: Trivy, Grype, or ECR native scanning before deploy.
-
Tag images with git SHA:
:mainis not a deploy tag; use immutable SHA tags. -
Deploy the same artifact tested in CI: Build once, promote digest - CI/CD Best Practices.
-
NODE_ENV=productionin runtime: Enables production code paths in frameworks. -
Log to stdout/stderr: No file logging inside containers.
-
Set resource requests from load tests: Memory limit should exceed RSS under p99 load - HPA & Resource Limits.
-
Document local
docker composeparity: Same env var names as K8s manifests. -
Review image size quarterly:
docker historyand dependency audit when size grows 2x.
FAQs
What is the single highest-impact Docker practice?
Multi-stage build with npm ci --omit=dev in the final stage. It cuts size, attack surface, and dev-tool leakage in one change.
Should we use distroless?
After bookworm-slim multi-stage works reliably. Distroless is a security hardening step, not day-one requirement.
How often should we rebuild base images?
Weekly automated Renovate PRs for digest bumps, plus immediate rebuild on critical Node security advisories.
Do we need Docker if we use Kubernetes?
Yes. Kubernetes runs containers; someone must build and scan the image. CI does the build; developers debug locally.
Where do TypeScript types go?
DevDependencies in build stage only. Never ship typescript or @types/* to production unless you have a documented reason.
Related
- Docker Basics - first container
- Multi-Stage Builds - build vs runtime
- Health & Readiness Probes - probe design
- Kubernetes Deployment - run images on K8s
- Platform Deploy Best Practices - orchestrator checklist
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.