A Tiny, Fast, Reproducible Image
Docker for Node
A minimal Dockerfile for a Node app, with multi-stage builds for size and security.
What you'll learn
- Write a Dockerfile
- Use multi-stage builds
- Run as a non-root user
A Docker image bundles Node + your code + your deps. Run anywhere that can run containers — same image dev → CI → prod.
A Working Dockerfile
# 1) Build stage: install all deps, build the app
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 2) Runtime stage: only what we need to run
FROM node:22-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
# install prod deps only
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
# copy built output
COPY --from=build /app/dist ./dist
# non-root user (already exists in node images)
USER node
EXPOSE 3000
CMD ["node", "dist/index.js"] What’s Going On
node:22-alpine— small (~50MB) base image- Multi-stage — dev deps (TypeScript, vitest, etc.) don’t ship to production
USER node— don’t run as root (security best practice)npm ci— installs exactly what’s inpackage-lock.jsonCOPY package*.json ./thennpm ci— done BEFORECOPY . .so Docker can cache thenpm cilayer when only source files change
Build + Run
docker build -t my-app .
docker run -p 3000:3000 -e DATABASE_URL=... my-app .dockerignore
Just like .gitignore — exclude node_modules, .git, .env,
build artifacts:
node_modules
.git
.env*
.DS_Store
dist
coverage Otherwise COPY . . ships your local node_modules (wrong
platform binaries) into the image. Slow build, broken runtime.
Distroless / Slim
For even smaller, more locked-down images:
node:22-slim— Debian-based, no shell shenanigans (~80MB)gcr.io/distroless/nodejs22— minimal, no shell at all (great for production, painful to debug)
Alpine has occasional musl-vs-glibc issues with native deps — slim/distroless avoid them.
Signal Handling
If your container can’t be killed cleanly, you forgot to handle
SIGTERM:
process.on("SIGTERM", () => server.close(() => process.exit(0))); Without it, container orchestrators (Kubernetes, ECS) wait 10s,
then SIGKILL. Connections drop. Users notice.