$devtoolkit.sh/examples/config/dockerfile-node

Dockerfile for a Node.js Application

A well-structured Node.js Dockerfile uses a multi-stage build to keep the final image small, installs only production dependencies, and runs as a non-root user for security. This example copies package files first to exploit Docker layer caching — rebuilds only reinstall packages when package.json changes, not on every code change. The Dockerfile generator validates syntax and suggests best practices like specifying exact base image versions. Always pin your Node.js version to avoid unexpected behavior from floating tags.

Example
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# Runtime stage
FROM node:20-alpine
WORKDIR /app
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --from=builder /app/node_modules ./node_modules
COPY . .
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "server.js"]
[ open in Dockerfile Generator → ]

FAQ

Why use a multi-stage Docker build for Node.js?
Multi-stage builds let you install build tools and devDependencies in an intermediate stage, then copy only the production artifacts to the final image. This results in a significantly smaller image with fewer attack surfaces.
Should I use npm ci or npm install in Docker?
Use npm ci in Docker. It installs exactly what is in package-lock.json for reproducible builds and fails if the lock file is out of sync, catching version drift before it reaches production.
Why run the container as a non-root user?
Running as root inside a container is a security risk. If an attacker escapes the container, they land as root on the host. A non-root user limits the blast radius of any container escape vulnerability.

Related Examples

/examples/config/dockerfile-nodev1.0.0