Architecture Overview
Our deployment architecture consists of multiple NestJS microservices communicating via various transport layers, managed by PM2 for process orchestration, with Prometheus and Grafana providing monitoring.
| Component | Purpose |
|---|---|
| NestJS Services | API Gateway, User Service, Order Service, Notification Service |
| PM2 | Process management, clustering, zero-downtime deployments |
| Redis | Message broker for microservice communication |
| Prometheus | Metrics collection and time-series database |
| Grafana | Visualization dashboards and alerting |
| NGINX | Reverse proxy and load balancer |
Prerequisites
- Ubuntu 22.04 or 24.04 LTS with at least 4GB RAM and 2 vCPUs
- Root or sudo access to the server
- Domain name configured with DNS A records pointing to your server
- Basic familiarity with Node.js and TypeScript
System Setup
Update System and Install Dependencies
sudo apt update && sudo apt upgrade -y
sudo apt install -y build-essential git curl wget software-properties-commonInstall Node.js via NVM
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install 20
nvm use 20
nvm alias default 20Install PM2 Globally
npm install -g pm2
pm2 startup systemdInstall and Configure Redis
sudo apt install -y redis-server
sudo systemctl enable redis-server
sudo systemctl start redis-serverConfigure Redis for production by editing /etc/redis/redis.conf:
maxmemory 512mb
maxmemory-policy allkeys-lru
bind 127.0.0.1Project Structure
Create a monorepo structure for managing multiple microservices:
mkdir -p /var/www/microservices && cd /var/www/microservices
npm init -y
npm install -g @nestjs/cliRecommended directory structure:
microservices/
├── apps/
│ ├── api-gateway/
│ ├── user-service/
│ ├── order-service/
│ └── notification-service/
├── libs/
│ └── common/
├── ecosystem.config.js
└── package.jsonCreate the API Gateway
The API Gateway serves as the entry point for all client requests:
nest new api-gateway --directory apps/api-gateway --skip-git
cd apps/api-gateway
npm install @nestjs/microservices @nestjs/axios ioredisCreate the main gateway module at apps/api-gateway/src/app.module.ts:
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [
ClientsModule.register([
{
name: 'USER_SERVICE',
transport: Transport.REDIS,
options: {
host: 'localhost',
port: 6379,
},
},
{
name: 'ORDER_SERVICE',
transport: Transport.REDIS,
options: {
host: 'localhost',
port: 6379,
},
},
]),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}Create Microservices
User Service
nest new user-service --directory apps/user-service --skip-git
cd apps/user-service
npm install @nestjs/microservices ioredisConfigure the microservice bootstrap in apps/user-service/src/main.ts:
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.REDIS,
options: {
host: 'localhost',
port: 6379,
},
},
);
await app.listen();
console.log('User Service is running');
}
bootstrap();Create message patterns in apps/user-service/src/app.controller.ts:
import { Controller } from '@nestjs/common';
import { MessagePattern, Payload } from '@nestjs/microservices';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@MessagePattern({ cmd: 'get_user' })
async getUser(@Payload() data: { userId: string }) {
return this.appService.findUser(data.userId);
}
@MessagePattern({ cmd: 'create_user' })
async createUser(@Payload() data: CreateUserDto) {
return this.appService.createUser(data);
}
@MessagePattern({ cmd: 'validate_user' })
async validateUser(@Payload() data: { email: string; password: string }) {
return this.appService.validateUser(data.email, data.password);
}
}PM2 Configuration
Create the PM2 ecosystem configuration file at /var/www/microservices/ecosystem.config.js:
module.exports = {
apps: [
{
name: 'api-gateway',
script: 'dist/main.js',
cwd: '/var/www/microservices/apps/api-gateway',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000,
},
max_memory_restart: '500M',
error_file: '/var/log/pm2/api-gateway-error.log',
out_file: '/var/log/pm2/api-gateway-out.log',
merge_logs: true,
time: true,
},
{
name: 'user-service',
script: 'dist/main.js',
cwd: '/var/www/microservices/apps/user-service',
instances: 2,
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
},
max_memory_restart: '300M',
},
{
name: 'order-service',
script: 'dist/main.js',
cwd: '/var/www/microservices/apps/order-service',
instances: 2,
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
},
max_memory_restart: '300M',
},
{
name: 'notification-service',
script: 'dist/main.js',
cwd: '/var/www/microservices/apps/notification-service',
instances: 1,
exec_mode: 'fork',
env: {
NODE_ENV: 'production',
},
max_memory_restart: '200M',
},
],
};PM2 Configuration Options
| Option | Description |
|---|---|
| instances | Number of instances; use 'max' for CPU count |
| exec_mode | 'cluster' for load balancing, 'fork' for single process |
| max_memory_restart | Automatic restart when memory exceeds threshold |
| merge_logs | Combine logs from all cluster instances |
| time | Prefix log entries with timestamps |
Start Services
cd /var/www/microservices
npm run build --workspaces
sudo mkdir -p /var/log/pm2
pm2 start ecosystem.config.js
pm2 saveEssential PM2 Commands
| Command | Description |
|---|---|
pm2 status | View status of all processes |
pm2 logs | Stream logs from all services |
pm2 reload all | Zero-downtime reload |
pm2 monit | Real-time monitoring dashboard |
Prometheus Metrics
Add Prometheus Metrics to NestJS
npm install prom-client @willsoto/nestjs-prometheusConfigure Prometheus module in your app.module.ts:
import { Module } from '@nestjs/common';
import { PrometheusModule, makeCounterProvider, makeHistogramProvider } from '@willsoto/nestjs-prometheus';
@Module({
imports: [
PrometheusModule.register({
path: '/metrics',
defaultMetrics: {
enabled: true,
},
}),
],
providers: [
makeCounterProvider({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'path', 'status'],
}),
makeHistogramProvider({
name: 'http_request_duration_seconds',
help: 'HTTP request duration in seconds',
labelNames: ['method', 'path', 'status'],
buckets: [0.1, 0.5, 1, 2, 5],
}),
],
})
export class AppModule {}Install Prometheus Server
sudo apt install -y prometheusConfigure Prometheus at /etc/prometheus/prometheus.yml:
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'api-gateway'
static_configs:
- targets: ['localhost:3000']
metrics_path: /metrics
- job_name: 'user-service'
static_configs:
- targets: ['localhost:3001']
metrics_path: /metrics
- job_name: 'pm2'
static_configs:
- targets: ['localhost:9209']sudo systemctl restart prometheus
sudo systemctl enable prometheusGrafana Dashboards
Install Grafana
sudo apt install -y apt-transport-https software-properties-common
wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
echo "deb https://packages.grafana.com/oss/deb stable main" | sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt update
sudo apt install -y grafana
sudo systemctl enable grafana-server
sudo systemctl start grafana-serverAccess Grafana at http://your-server:3030 (default credentials: admin/admin).
Essential Dashboard Queries
sum(rate(http_requests_total{job="api-gateway"}[5m])) by (path)histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{job="api-gateway"}[5m])) by (le, path))sum(rate(http_requests_total{job="api-gateway", status=~"5.."}[5m])) / sum(rate(http_requests_total{job="api-gateway"}[5m])) * 100NGINX Reverse Proxy
sudo apt install -y nginx
sudo systemctl enable nginxCreate NGINX configuration at /etc/nginx/sites-available/microservices:
upstream api_gateway {
least_conn;
server 127.0.0.1:3000 weight=1 max_fails=3 fail_timeout=30s;
keepalive 32;
}
server {
listen 80;
server_name api.yourdomain.com;
location / {
proxy_pass http://api_gateway;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
location /metrics {
deny all;
return 404;
}
}sudo ln -s /etc/nginx/sites-available/microservices /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginxHealth Checks and Alerting
NestJS Health Check Module
npm install @nestjs/terminusimport { Controller, Get } from '@nestjs/common';
import { HealthCheck, HealthCheckService, MemoryHealthIndicator, DiskHealthIndicator } from '@nestjs/terminus';
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private memory: MemoryHealthIndicator,
private disk: DiskHealthIndicator,
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.memory.checkHeap('memory_heap', 200 * 1024 * 1024),
() => this.memory.checkRSS('memory_rss', 300 * 1024 * 1024),
() => this.disk.checkStorage('disk', { path: '/', thresholdPercent: 0.9 }),
]);
}
}Prometheus Alerting Rules
groups:
- name: microservices
rules:
- alert: ServiceDown
expr: up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Service {{ $labels.job }} is down"
- alert: HighErrorRate
expr: sum(rate(http_requests_total{status=~"5.."}[5m])) by (job) /
sum(rate(http_requests_total[5m])) by (job) > 0.05
for: 5m
labels:
severity: warning
- alert: PM2ProcessRestarting
expr: increase(pm2_restarts[5m]) > 3
for: 1m
labels:
severity: warningZero-Downtime Deployments
Create a deployment script at /var/www/microservices/deploy.sh:
#!/bin/bash
set -e
SERVICE=$1
if [ -z "$SERVICE" ]; then
echo "Usage: ./deploy.sh <service-name|all>"
exit 1
fi
cd /var/www/microservices
echo "Pulling latest changes..."
git pull origin main
if [ "$SERVICE" = "all" ]; then
echo "Building all services..."
npm run build --workspaces
echo "Performing zero-downtime reload..."
pm2 reload ecosystem.config.js
else
echo "Building $SERVICE..."
npm run build --workspace=apps/$SERVICE
echo "Performing zero-downtime reload for $SERVICE..."
pm2 reload $SERVICE
fi
pm2 save
echo "Deployment complete!"
pm2 statuschmod +x /var/www/microservices/deploy.shGraceful Shutdown Configuration
Add graceful shutdown handling in each NestJS service:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableShutdownHooks();
process.on('SIGTERM', async () => {
console.log('SIGTERM received, gracefully shutting down...');
await app.close();
process.exit(0);
});
await app.listen(process.env.PORT || 3000);
}
bootstrap();Troubleshooting
| Issue | Solution |
|---|---|
| Service not starting | Check logs with pm2 logs <service>; verify build completed |
| High memory usage | Adjust max_memory_restart; check for memory leaks with pm2 monit |
| Redis connection failed | Verify Redis is running: systemctl status redis-server |
| Metrics not appearing | Ensure /metrics endpoint is accessible; check Prometheus targets |
| Cluster mode issues | Use fork mode for stateful services; ensure shared session storage |
| 502 Bad Gateway | Check if service is running; verify NGINX upstream configuration |
Log Rotation
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 50M
pm2 set pm2-logrotate:retain 10
pm2 set pm2-logrotate:compress true