Dockerizing a Nest App

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.

4 min read Level 2/5 #nestjs#docker#deployment
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/.npm so repeat builds are fast.
  • Run as the unprivileged node user — never as root.
  • Pin the Node minor version (node:22-slim, not node:latest).
  • For the smallest image, use gcr.io/distroless/nodejs22 in the runtime stage — about 50MB total.
Deployment & Going Further →