Multi-stage Dockerfile for Node.js (2026)

2026-06-28 · 4 min read

SHORT ANSWER Use two stages: a build stage that installs dependencies and builds, and a slim runtime stage that copies only what's needed and runs as a non-root user. This cuts image size and shrinks the attack surface. Copy-paste template below.

The template

# Build stage — has dev deps + toolchain
FROM node:22-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build --if-present

# Runtime stage — slim, only what's needed, non-root
FROM node:22-slim
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY package*.json ./
USER node
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD node -e "fetch('http://localhost:3000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
CMD ["node", "dist/index.js"]

Why each part matters

Two stages

The build stage carries your full toolchain and dev dependencies. The runtime stage starts fresh from a slim base and copies in only the built artifact and production node_modules — so compilers, caches, and dev deps never ship.

Non-root user

The official Node images include a node user. Running as it (USER node) means a compromised process isn't root inside the container — a cheap, large security win.

HEALTHCHECK

Lets your orchestrator know when the container is actually serving, not just started. The example hits a /health endpoint with Node's built-in fetch — no extra tools.

Common mistakes

Generate this for your stack → SysBuild's free Dockerfile generator does multi-stage, non-root, and healthchecks for Node, Python, Go and Rust — in your browser, no signup.

Going further

For a complete, ready-to-run project — Dockerfile, docker-compose with Postgres, CI, and a health endpoint already wired — the SysBuild Pro Pack ships five such stacks (including node-express-postgres and nextjs-standalone) for $29 one-time.

More articles