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.