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.

How to Containerize NestJS Applications with Docker
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:
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
1. **Port conflicts**: Ensure ports aren't already in use
2. **Permission issues**: Check file permissions and user context
3. **Memory limits**: Configure appropriate memory limits
4. **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:
Start containerizing your NestJS applications today and experience the benefits of modern deployment practices!
Related Articles
Redis Integration in NestJS: Complete Guide
Learn how to integrate Redis with NestJS for caching, session management, and real-time applications. Complete with code examples and best practices.
CLEAN Architecture: Building Maintainable Software
Deep dive into CLEAN Architecture principles, implementation patterns, and how to structure your applications for maximum maintainability and testability.