A Small, Fast Production Image
Dockerizing a Nest App
A two-stage Dockerfile builds with dev deps and ships only what production needs — fast cold starts, smaller attack surface.
What you'll learn
- Write a multi-stage Dockerfile for a Nest app
- Use node:slim or alpine as the runtime base
- Add a .dockerignore that excludes the right files
A good Nest container does three things: builds fast, runs small, and contains nothing it does not need. A multi-stage Dockerfile gets you there without ceremony.
The Dockerfile
# syntax=docker/dockerfile:1.7
FROM node:22-slim AS build
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY tsconfig*.json nest-cli.json ./
COPY src ./src
RUN npm run build && npm prune --omit=dev
FROM node:22-slim AS runtime
ENV NODE_ENV=production
WORKDIR /app
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY package*.json ./
USER node
EXPOSE 3000
CMD ["node", "dist/main"] The build stage installs all deps and compiles TypeScript. The runtime
stage copies only dist and the pruned node_modules — typically a
fraction of the build stage’s size.
.dockerignore
node_modules
dist
.git
.env
.env.*
coverage
test
*.log
.DS_Store Without this file, COPY . . pulls in your local node_modules and
busts the layer cache on every change. Always include one.
Compose For Local Dev
services:
api:
build: .
ports: ['3000:3000']
environment:
DATABASE_URL: postgres://app:app@db:5432/app
REDIS_URL: redis://redis:6379
depends_on: [db, redis]
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: app
redis:
image: redis:7-alpine A Few Tips
- Use BuildKit cache mounts for
/root/.npmso repeat builds are fast. - Run as the unprivileged
nodeuser — never as root. - Pin the Node minor version (
node:22-slim, notnode:latest). - For the smallest image, use
gcr.io/distroless/nodejs22in the runtime stage — about 50MB total.