How to Containerize NestJS Applications with Docker
Step-by-step guide to containerizing your NestJS applications using Docker. Includes multi-stage builds, optimization techniques, and deployment strategies.

Containerization has become essential for modern application deployment. In this comprehensive guide, we'll explore how to containerize NestJS applications using Docker, including best practices for production deployments.
Why Containerize NestJS Applications?
Docker containers provide several benefits for NestJS applications:
- Consistency: Same environment across development, staging, and production
- Scalability: Easy horizontal scaling with container orchestration
- Isolation: Applications run in isolated environments
- Portability: Deploy anywhere Docker is supported
- Resource Efficiency: Lightweight compared to virtual machines
Basic Dockerfile for NestJS
Let's start with a basic Dockerfile for a NestJS application:
# Dockerfile
FROM node:18-alpine
# Set working directory
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy source code
COPY . .
# Build the application
RUN npm run build
# Expose port
EXPOSE 3000
# Start the application
CMD ["npm", "run", "start:prod"]
Multi-Stage Docker Build
For production applications, use multi-stage builds to optimize image size:
# Multi-stage Dockerfile
# Stage 1: Build
FROM node:18-alpine AS builder
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install all dependencies (including dev dependencies)
RUN npm ci
# Copy source code
COPY . .
# Build the application
RUN npm run build
# Stage 2: Production
FROM node:18-alpine AS production
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install only production dependencies
RUN npm ci --only=production && npm cache clean --force
# Copy built application from builder stage
COPY --from=builder /app/dist ./dist
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nestjs -u 1001
# Change ownership of the app directory
RUN chown -R nestjs:nodejs /app
USER nestjs
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# Start the application
CMD ["node", "dist/main"]
Docker Compose for Development
Create a docker-compose.yml for local development:
# docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
- DATABASE_URL=postgresql://user:password@db:5432/nestjs_db
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
command: npm run start:dev
db:
image: postgres:15-alpine
ports:
- "5432:5432"
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=nestjs_db
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:
Development Dockerfile
Create a separate Dockerfile for development:
# Dockerfile.dev
FROM node:18-alpine
WORKDIR /app
# Install dependencies
COPY package*.json ./
RUN npm install
# Copy source code
COPY . .
# Expose port
EXPOSE 3000
# Start development server
CMD ["npm", "run", "start:dev"]
Environment Configuration
Environment Variables
Use environment variables for configuration:
// config/configuration.ts
export default () => ({
port: parseInt(process.env.PORT, 10) || 3000,
database: {
host: process.env.DATABASE_HOST || 'localhost',
port: parseInt(process.env.DATABASE_PORT, 10) || 5432,
username: process.env.DATABASE_USERNAME || 'user',
password: process.env.DATABASE_PASSWORD || 'password',
database: process.env.DATABASE_NAME || 'nestjs_db',
},
redis: {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT, 10) || 6379,
},
});
Docker Environment File
Create a .env.docker file:
# .env.docker
NODE_ENV=production
PORT=3000
DATABASE_HOST=db
DATABASE_PORT=5432
DATABASE_USERNAME=user
DATABASE_PASSWORD=password
DATABASE_NAME=nestjs_db
REDIS_HOST=redis
REDIS_PORT=6379
Optimization Techniques
1. Layer Caching
Optimize Docker layer caching by copying package files first:
# Copy package files first for better caching
COPY package*.json ./
RUN npm ci --only=production
# Then copy source code
COPY . .
2. .dockerignore File
Create a .dockerignore file to exclude unnecessary files:
# .dockerignore
node_modules
npm-debug.log
dist
.git
.gitignore
README.md
.env
.nyc_output
coverage
.coverage
.env.test
.env.local
.env.development.local
.env.staging.local
.env.production.local
3. Alpine Linux
Use Alpine Linux for smaller image sizes:
FROM node:18-alpine
4. Non-Root User
Run containers as non-root user for security:
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nestjs -u 1001
USER nestjs
Production Deployment
Docker Compose for Production
# docker-compose.prod.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- NODE_ENV=production
env_file:
- .env.docker
depends_on:
- db
- redis
restart: unless-stopped
deploy:
replicas: 3
resources:
limits:
memory: 512M
reservations:
memory: 256M
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- app
restart: unless-stopped
db:
image: postgres:15-alpine
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=nestjs_db
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
restart: unless-stopped
volumes:
postgres_data:
redis_data:
Health Checks
Implement health checks in your NestJS application:
// health.controller.ts
import { Controller, Get } from '@nestjs/common';
import { HealthCheck, HealthCheckService, TypeOrmHealthIndicator } from '@nestjs/terminus';
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private db: TypeOrmHealthIndicator,
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.db.pingCheck('database'),
]);
}
}
Monitoring and Logging
Structured Logging
Configure structured logging for containers:
// logger.config.ts
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
export const loggerConfig = WinstonModule.createLogger({
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json(),
),
}),
],
});
Container Monitoring
Use Docker stats to monitor container performance:
# Monitor container stats
docker stats
# View container logs
docker logs -f container_name
# Execute commands in running container
docker exec -it container_name sh
CI/CD Pipeline
GitHub Actions Example
# .github/workflows/docker.yml
name: Docker Build and Deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: your-username/nestjs-app:latest
cache-from: type=gha
cache-to: type=gha,mode=max
Security Best Practices
1. Use Official Base Images
Always use official Node.js images:
FROM node:18-alpine
2. Scan for Vulnerabilities
Regularly scan images for vulnerabilities:
docker scan your-image:tag
3. Limit Container Privileges
Run containers with limited privileges:
USER nestjs
4. Use Secrets Management
Don't include secrets in images:
docker run -e DATABASE_PASSWORD_FILE=/run/secrets/db_password your-image
Troubleshooting
Common Issues
- Port conflicts: Ensure ports aren't already in use
- Permission issues: Check file permissions and user context
- Memory limits: Configure appropriate memory limits
- Network connectivity: Verify service communication
Debugging Commands
# Check container logs
docker logs container_name
# Execute shell in container
docker exec -it container_name sh
# Inspect container configuration
docker inspect container_name
# Check container processes
docker top container_name
Conclusion
Containerizing NestJS applications with Docker provides numerous benefits for development and deployment. By following these best practices, you can create efficient, secure, and scalable containerized applications.
Key takeaways:
- Use multi-stage builds for production optimization
- Implement proper health checks and monitoring
- Follow security best practices
- Use Docker Compose for local development
- Implement CI/CD pipelines for automated deployment
Start containerizing your NestJS applications today and experience the benefits of modern deployment practices!

