NestJS Docker Setup

Docker packages your NestJS application and all its dependencies into a container — a self-contained unit that runs identically on any machine. "It works on my laptop" stops being an excuse when your app ships inside a Docker container. Docker also makes it simple to run a database alongside your app during development without installing anything globally.

What Docker Provides

Without Docker:
  Developer A: Node 18, PostgreSQL 14, runs fine
  Developer B: Node 20, PostgreSQL 16, different behavior
  Production:  Node 16, PostgreSQL 15, crashes

With Docker:
  Everyone runs the same container image
  Identical environment everywhere: dev, staging, production
  No "works on my machine" problems

Creating a Dockerfile

A Dockerfile describes how to build an image of your application. Use a multi-stage build to keep the production image small:

# Stage 1: Build the TypeScript code
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Run only the compiled output
FROM node:20-alpine AS production
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev     # install only production dependencies
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/main"]

Multi-Stage Build Diagram

Stage 1 (builder):
  ├── All node_modules (dev + prod) ~500MB
  ├── src/ (TypeScript source)
  └── dist/ (compiled JavaScript)

Stage 2 (production image):
  ├── node_modules (production only) ~100MB
  └── dist/ (compiled JavaScript)

Final image: ~150MB instead of ~600MB
No TypeScript compiler, no test libraries, no dev tools in production

Building and Running the Docker Image

# Build the image
docker build -t my-nestjs-app .

# Run the container
docker run -p 3000:3000 \
  -e DB_HOST=host.docker.internal \
  -e DB_PORT=5432 \
  -e DB_USER=postgres \
  -e DB_PASS=secret \
  -e JWT_SECRET=mysupersecret \
  my-nestjs-app

# App is now accessible at http://localhost:3000

Docker Compose for Development

Docker Compose runs multiple containers together — your NestJS app plus its database — with one command:

# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - '3000:3000'
    environment:
      DB_HOST: db
      DB_PORT: 5432
      DB_USER: postgres
      DB_PASS: secret
      DB_NAME: myapp
      JWT_SECRET: supersecretkey
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - ./src:/app/src   # hot-reload in development

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: myapp
    ports:
      - '5432:5432'
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ['CMD', 'pg_isready', '-U', 'postgres']
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  postgres_data:

Docker Compose Commands

docker compose up         # start all services
docker compose up -d      # start in background (detached)
docker compose down       # stop and remove containers
docker compose down -v    # stop and remove containers + volumes (deletes DB data)
docker compose logs -f    # stream logs from all services
docker compose ps         # show running services

.dockerignore File

The .dockerignore file prevents unnecessary files from being copied into the Docker build context, making builds faster:

node_modules
dist
.env
.env.*
*.spec.ts
test/
.git
README.md

Environment Variables in Docker

Pass environment variables to containers without hardcoding them in docker-compose.yml. Create a .env file and reference it:

# docker-compose.yml
services:
  app:
    env_file:
      - .env     # Docker reads from this file automatically

The .env file stays out of Docker images (excluded by .dockerignore) and out of version control (excluded by .gitignore). Each environment — development, staging, production — has its own .env file with the appropriate credentials.

Production vs Development Compose Files

docker-compose.yml          # base config (shared settings)
docker-compose.dev.yml      # development overrides (hot-reload, debug port)
docker-compose.prod.yml     # production overrides (no volumes, restart policy)

# Run development environment:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up

# Run production environment:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up

Docker eliminates environment inconsistency from your entire development and deployment pipeline. Every developer, every CI server, and every production node runs the same container image. Onboarding a new developer goes from a half-day setup process to running docker compose up and being productive in minutes.

Leave a Comment

Your email address will not be published. Required fields are marked *