Dockerfile Builder for Node.js

Generate a production-ready multi-stage Dockerfile for Node.js apps

Build a production-ready multi-stage Dockerfile for Node.js. Configure Node version, package manager, port, build step and a /health HEALTHCHECK, with a non-root user for Express, NestJS or Next.js. It runs free in your browser on Gera Tools, with nothing uploaded.

Last updated Source: Gera Tools

Why use a multi-stage build?

A multi-stage build compiles your app and installs dev dependencies in a builder stage, then copies only the runtime artifacts and production dependencies into a slim final image. This keeps the shipped image small and free of build tooling and source.

A builder for a production-ready, multi-stage Dockerfile for Node.js apps — Express, NestJS, Fastify or Next.js. Choose your Node version, package manager and port, and the tool emits an image that compiles in a builder stage and ships only the runtime in a slim final stage, running as a non-root user.

How it works

The generated Dockerfile uses three stages. The builder stage installs all dependencies (npm ci, yarn install --frozen-lockfile or pnpm install --frozen-lockfile), copies your source and runs the build script if you have one. A separate deps stage installs production-only dependencies so dev tooling never reaches the final image. The runner stage copies the production node_modules and the compiled dist/ output, sets NODE_ENV=production, switches to the built-in non-root node user, exposes your port and defines the start command.

What the generated output looks like

For an Express app using npm, Node 22, port 3000:

FROM node:22-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

FROM node:22-slim AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-slim AS runner
ENV NODE_ENV=production
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
USER node
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD node -e "fetch('http://localhost:3000/health').then(r=>process.exit(r.ok?0:1))"
CMD ["node", "dist/server.js"]

The deps stage installs only production dependencies (--omit=dev) so build tools like TypeScript, ts-node, and test libraries never enter the final image. The builder stage gets the full dependency set including devDependencies.

Health checks and security

When enabled, the HEALTHCHECK instruction calls your /health endpoint with Node’s built-in fetch (available in Node 18+), exiting non-zero on failure so the orchestrator can restart the container. Running as USER node is a baseline hardening step: it ensures a compromised process does not have root inside the container. The official Node images ship with a built-in node user for exactly this purpose.

Per-framework adjustments

NestJS — the build outputs to dist/ by default and the start command is node dist/main.js. Enable the build step.

Next.js standalone — in next.config.js set output: 'standalone'. The build then emits .next/standalone, which includes a self-contained server.js and a minimal node_modules. Copy .next/standalone, .next/static, and public into the runner stage and start with node server.js. The deps stage is not needed because standalone bundles everything.

Fastify — same structure as Express. If using Fastify’s Avvio plugin system, make sure NODE_ENV=production is set so plugins are not loaded in dev-only mode.

The .dockerignore file

Always add this file next to the Dockerfile:

node_modules
.git
.env
dist
.next
npm-debug.log
*.test.ts

Without it, Docker copies the local node_modules directory into the build context, which makes the context multi-gigabyte and can override the freshly-installed dependencies inside the image.