We use cookies to enhance your experience on the site
CodeWorlds

Deployment - marching the fort onto the battlefield

Commander! Consul Caesar.js announces that the time has come for the most important phase of our adventure - marching our Roman NestJS fort onto the battlefield of production! This is the moment when all our preparations, training, and tests will be put to the real test under actual conditions.

Deployment is the process of moving an application from a safe development environment to a production environment, where it will serve real users around the world.

Preparing the fort for departure

Before our application sets off on campaign, we must make sure everything is ready for the long journey:

1. Building the production application

1// package.json - our fort's manifest
2{
3 "name": "legionaries-legion-api",
4 "version": "1.0.0",
5 "scripts": {
6 "build": "nest build",
7 "start": "node dist/main",
8 "start:prod": "NODE_ENV=production node dist/main",
9 "start:dev": "nest start --watch",
10 "start:debug": "nest start --debug --watch",
11 "prestart:prod": "npm run build"
12 },
13 "dependencies": {
14 "@nestjs/common": "^10.0.0",
15 "@nestjs/core": "^10.0.0",
16 "@nestjs/platform-express": "^10.0.0",
17 "helmet": "^7.0.0",
18 "compression": "^1.7.4"
19 }
20}
21
22// nest-cli.json - fort build configuration
23{
24 "collection": "@nestjs/schematics",
25 "sourceRoot": "src",
26 "compilerOptions": {
27 "deleteOutDir": true,
28 "webpack": true,
29 "tsConfigPath": "tsconfig.build.json"
30 }
31}
32
33// tsconfig.build.json - build instructions
34{
35 "extends": "./tsconfig.json",
36 "exclude": [
37 "node_modules",
38 "test",
39 "dist",
40 "**/*spec.ts"
41 ]
42}

2. Production environment configuration

1// src/config/production.config.ts
2export const productionConfig = {
3 port: process.env.PORT || 3000,
4 nodeEnv: 'production',
5
6 // Fort security
7 security: {
8 helmet: true,
9 cors: {
10 origin: process.env.ALLOWED_ORIGINS?.split(',') || [],
11 credentials: true,
12 },
13 rateLimit: {
14 windowMs: 15 * 60 * 1000, // 15 minutes
15 max: 1000, // limit per IP
16 },
17 },
18
19 // Database - the main tribute warehouse
20 database: {
21 type: 'postgres',
22 host: process.env.DB_HOST,
23 port: parseInt(process.env.DB_PORT) || 5432,
24 username: process.env.DB_USERNAME,
25 password: process.env.DB_PASSWORD,
26 database: process.env.DB_DATABASE,
27 ssl: process.env.DB_SSL === 'true' ? { rejectUnauthorized: false } : false,
28 synchronize: false, // NEVER true in production!
29 logging: process.env.DB_LOGGING === 'true',
30 maxConnections: parseInt(process.env.DB_MAX_CONNECTIONS) || 10,
31 },
32
33 // Redis - fast communication between forts
34 redis: {
35 host: process.env.REDIS_HOST,
36 port: parseInt(process.env.REDIS_PORT) || 6379,
37 password: process.env.REDIS_PASSWORD,
38 retryDelayOnFailover: 100,
39 maxRetriesPerRequest: 3,
40 },
41
42 // Logging - the fort's chronicle
43 logging: {
44 level: process.env.LOG_LEVEL || 'info',
45 format: 'json',
46 transports: [
47 {
48 type: 'file',
49 filename: 'logs/legionaries-legion.log',
50 maxFiles: 5,
51 maxSize: '20m',
52 },
53 {
54 type: 'error-file',
55 filename: 'logs/errors.log',
56 level: 'error',
57 },
58 ],
59 },
60
61 // Monitoring - sea observation system
62 monitoring: {
63 healthcheck: {
64 enabled: true,
65 endpoint: '/health',
66 },
67 metrics: {
68 enabled: true,
69 endpoint: '/metrics',
70 },
71 apm: {
72 enabled: process.env.APM_ENABLED === 'true',
73 serverUrl: process.env.APM_SERVER_URL,
74 serviceName: 'legionaries-legion-api',
75 },
76 },
77};

3. Performance optimization for production

1// src/main.ts - the fort's main entry point
2import { NestFactory } from '@nestjs/core';
3import { ValidationPipe, Logger } from '@nestjs/common';
4import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
5import helmet from 'helmet';
6import compression from 'compression';
7import { AppModule } from './app.module';
8import { productionConfig } from './config/production.config';
9
10async function bootstrap() {
11 const logger = new Logger('Bootstrap');
12
13 try {
14 const app = await NestFactory.create(AppModule, {
15 logger: productionConfig.nodeEnv === 'production'
16 ? ['error', 'warn', 'log']
17 : ['error', 'warn', 'log', 'debug', 'verbose'],
18 });
19
20 // Fort security
21 app.use(helmet({
22 crossOriginEmbedderPolicy: false,
23 contentSecurityPolicy: {
24 directives: {
25 defaultSrc: ["'self'"],
26 styleSrc: ["'self'", "'unsafe-inline'"],
27 scriptSrc: ["'self'"],
28 imgSrc: ["'self'", "data:", "https:"],
29 },
30 },
31 }));
32
33 // Response compression - less cargo space needed
34 app.use(compression());
35
36 // CORS - access control
37 app.enableCors(productionConfig.security.cors);
38
39 // Global data validation
40 app.useGlobalPipes(new ValidationPipe({
41 whitelist: true,
42 forbidNonWhitelisted: true,
43 transform: true,
44 disableErrorMessages: productionConfig.nodeEnv === 'production',
45 }));
46
47 // Global API prefixes
48 app.setGlobalPrefix('api/v1');
49
50 // API documentation (dev only)
51 if (productionConfig.nodeEnv !== 'production') {
52 const config = new DocumentBuilder()
53 .setTitle('Legionary Legion API')
54 .setDescription('API for managing legionaries legion operations')
55 .setVersion('1.0')
56 .addBearerAuth()
57 .build();
58
59 const document = SwaggerModule.createDocument(app, config);
60 SwaggerModule.setup('api/docs', app, document);
61 }
62
63 // Launch the fort
64 const port = productionConfig.port;
65 await app.listen(port, '0.0.0.0');
66
67 logger.log(` Legionary Legion API is marching on port ${port}`);
68 logger.log(` Environment: ${productionConfig.nodeEnv}`);
69 logger.log(` Health check: http://localhost:${port}/health`);
70
71 } catch (error) {
72 logger.error('Failed to start the cohort!', error);
73 process.exit(1);
74 }
75}
76
77// Graceful shutdown - safe docking
78process.on('SIGTERM', () => {
79 console.log('SIGTERM received, shutting down gracefully');
80 process.exit(0);
81});
82
83process.on('SIGINT', () => {
84 console.log('SIGINT received, shutting down gracefully');
85 process.exit(0);
86});
87
88bootstrap();

Deployment strategies

1. Manual Deployment - traditional methods

1#!/bin/bash
2# deploy.sh - fort deployment script
3
4set -e
5
6echo " Starting deployment of Legionary Legion API..."
7
8# Check environment
9if [ -z "$NODE_ENV" ]; then
10 echo "NODE_ENV not set!"
11 exit 1
12fi
13
14# Database backup
15echo " Creating database backup..."
16pg_dump $DATABASE_URL > backups/backup-$(date +%Y%m%d_%H%M%S).sql
17
18# Update code
19echo "Pulling latest code..."
20git pull origin main
21
22# Install dependencies
23echo " Installing dependencies..."
24npm ci --only=production
25
26# Build application
27echo "Building application..."
28npm run build
29
30# Database migrations
31echo "Running database migrations..."
32npm run migration:run
33
34# Restart application
35echo "Restarting application..."
36pm2 restart legionaries-legion-api
37
38# Check health
39echo "Checking application health..."
40sleep 10
41curl -f http://localhost:3000/health || exit 1
42
43echo "Deployment completed successfully!"
44echo " Legionary Legion API is marching smoothly!"

2. Process Manager - PM2

1// ecosystem.config.js - legion configuration
2module.exports = {
3 apps: [{
4 name: 'legionaries-legion-api',
5 script: 'dist/main.js',
6 instances: 'max', // Use all CPUs
7 exec_mode: 'cluster',
8
9 // Environment variables
10 env: {
11 NODE_ENV: 'production',
12 PORT: 3000,
13 },
14
15 // Restart settings
16 max_memory_restart: '1G',
17 restart_delay: 4000,
18 max_restarts: 10,
19 min_uptime: '10s',
20
21 // Logging
22 log_file: 'logs/combined.log',
23 out_file: 'logs/out.log',
24 error_file: 'logs/error.log',
25 time: true,
26
27 // Monitoring
28 monitoring: true,
29 pmx: true,
30 }],
31
32 deploy: {
33 production: {
34 user: 'legionaries',
35 host: 'legion.legionariesapi.com',
36 ref: 'origin/main',
37 repo: 'git@github.com:legionaries-legion/legion-api.git',
38 path: '/var/www/legionaries-legion-api',
39 'post-deploy': 'npm install && npm run build && pm2 reload ecosystem.config.js --env production',
40 env: {
41 NODE_ENV: 'production'
42 }
43 }
44 }
45};

3. Reverse Proxy - Nginx

1# /etc/nginx/sites-available/legionaries-legion-api
2server {
3 listen 80;
4 listen [::]:80;
5 server_name api.legionaries.com;
6
7 # Redirect to HTTPS
8 return 301 https://$server_name$request_uri;
9}
10
11server {
12 listen 443 ssl http2;
13 listen [::]:443 ssl http2;
14 server_name api.legionaries.com;
15
16 # SSL certificates
17 ssl_certificate /etc/letsencrypt/live/api.legionaries.com/fullchain.pem;
18 ssl_certificate_key /etc/letsencrypt/live/api.legionaries.com/privkey.pem;
19
20 # SSL security
21 ssl_protocols TLSv1.2 TLSv1.3;
22 ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
23 ssl_prefer_server_ciphers off;
24 ssl_session_cache shared:SSL:10m;
25
26 # Gzip compression
27 gzip on;
28 gzip_vary on;
29 gzip_min_length 1024;
30 gzip_types text/plain text/css text/json application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
31
32 # Rate limiting
33 limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
34 limit_req zone=api burst=20 nodelay;
35
36 # Proxy to NestJS application
37 location / {
38 proxy_pass http://127.0.0.1:3000;
39 proxy_http_version 1.1;
40 proxy_set_header Upgrade $http_upgrade;
41 proxy_set_header Connection 'upgrade';
42 proxy_set_header Host $host;
43 proxy_set_header X-Real-IP $remote_addr;
44 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
45 proxy_set_header X-Forwarded-Proto $scheme;
46 proxy_cache_bypass $http_upgrade;
47
48 # Timeouts
49 proxy_connect_timeout 60s;
50 proxy_send_timeout 60s;
51 proxy_read_timeout 60s;
52 }
53
54 # Static files (if any)
55 location /static {
56 alias /var/www/legionaries-legion-api/static;
57 expires 1y;
58 add_header Cache-Control "public, immutable";
59 }
60
61 # Health check (internal)
62 location /health {
63 access_log off;
64 proxy_pass http://127.0.0.1:3000/health;
65 }
66
67 # Security headers
68 add_header X-Frame-Options "SAMEORIGIN" always;
69 add_header X-XSS-Protection "1; mode=block" always;
70 add_header X-Content-Type-Options "nosniff" always;
71 add_header Referrer-Policy "no-referrer-when-downgrade" always;
72 add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
73}

Database Migration in production

1// migrations/1701234567890-InitialLegionStructure.ts
2import { MigrationInterface, QueryRunner } from 'typeorm';
3
4export class InitialLegionStructure1701234567890 implements MigrationInterface {
5 name = 'InitialLegionStructure1701234567890';
6
7 public async up(queryRunner: QueryRunner): Promise<void> {
8 // Backup before migration
9 await queryRunner.query(`
10 CREATE TABLE IF NOT EXISTS migration_backup_${Date.now()} AS
11 SELECT * FROM legionaries WHERE 1=2
12 `);
13
14 // New tables
15 await queryRunner.query(`
16 CREATE TABLE "legions" (
17 "id" SERIAL NOT NULL,
18 "name" character varying NOT NULL,
19 "legate_name" character varying NOT NULL,
20 "established_at" TIMESTAMP NOT NULL DEFAULT now(),
21 CONSTRAINT "UQ_legion_name" UNIQUE ("name"),
22 CONSTRAINT "PK_legions" PRIMARY KEY ("id")
23 )
24 `);
25
26 // Indexes for performance
27 await queryRunner.query(`
28 CREATE INDEX "IDX_legion_legate" ON "legions" ("legate_name")
29 `);
30
31 // Constraints for data consistency
32 await queryRunner.query(`
33 ALTER TABLE "cohorts"
34 ADD CONSTRAINT "CHK_cohort_capacity"
35 CHECK ("capacity" > 0 AND "capacity" <= 1000)
36 `);
37 }
38
39 public async down(queryRunner: QueryRunner): Promise<void> {
40 // Rollback - only if safe
41 await queryRunner.query(`DROP TABLE "legions"`);
42 await queryRunner.query(`
43 ALTER TABLE "cohorts"
44 DROP CONSTRAINT "CHK_cohort_capacity"
45 `);
46 }
47}
48
49// Production migration script
50// migrate-production.sh
51#!/bin/bash
52
53echo "Starting production database migration..."
54
55// Backup before migration
56echo " Creating pre-migration backup..."
57pg_dump $DATABASE_URL > backups/pre-migration-$(date +%Y%m%d_%H%M%S).sql
58
59// Test migration on a copy
60echo "Testing migration on copy..."
61npm run migration:show
62npm run migration:run --dry-run
63
64// Confirm before execution
65read -p "Continue with production migration? (y/N): " -n 1 -r
66echo
67if [[ ! $REPLY =~ ^[Yy]$ ]]; then
68 echo "Migration cancelled"
69 exit 1
70fi
71
72// Execute migration
73echo " Running migration..."
74npm run migration:run
75
76// Verification
77echo "Verifying migration..."
78npm run migration:show
79
80echo "Migration completed successfully!"

Monitoring and Health Checks

1// src/health/health.controller.ts
2import { Controller, Get } from '@nestjs/common';
3import {
4 HealthCheck,
5 HealthCheckService,
6 TypeOrmHealthIndicator,
7 MemoryHealthIndicator,
8 DiskHealthIndicator,
9} from '@nestjs/terminus';
10
11@Controller('health')
12export class HealthController {
13 constructor(
14 private health: HealthCheckService,
15 private db: TypeOrmHealthIndicator,
16 private memory: MemoryHealthIndicator,
17 private disk: DiskHealthIndicator,
18 ) {}
19
20 @Get()
21 @HealthCheck()
22 check() {
23 return this.health.check([
24 // Database check
25 () => this.db.pingCheck('database'),
26
27 // Memory check (max 150MB heap)
28 () => this.memory.checkHeap('memory_heap', 150 * 1024 * 1024),
29
30 // RSS memory check (max 300MB)
31 () => this.memory.checkRSS('memory_rss', 300 * 1024 * 1024),
32
33 // Disk space check (min 250GB free)
34 () => this.disk.checkStorage('storage', {
35 thresholdPercent: 0.9,
36 path: '/'
37 }),
38 ]);
39 }
40
41 @Get('detailed')
42 @HealthCheck()
43 detailedCheck() {
44 return this.health.check([
45 () => this.db.pingCheck('database'),
46 () => this.memory.checkHeap('memory_heap', 150 * 1024 * 1024),
47 () => this.checkCustomServices(),
48 ]);
49 }
50
51 private async checkCustomServices() {
52 // Custom health checks for external services
53 const checks = {
54 redis: await this.checkRedis(),
55 externalApi: await this.checkExternalAPI(),
56 backgroundJobs: await this.checkBackgroundJobs(),
57 };
58
59 const allHealthy = Object.values(checks).every(check => check.status === 'up');
60
61 return {
62 'custom-services': {
63 status: allHealthy ? 'up' : 'down',
64 details: checks,
65 },
66 };
67 }
68
69 private async checkRedis() {
70 try {
71 // Check Redis connection
72 return { status: 'up', latency: '< 10ms' };
73 } catch (error) {
74 return { status: 'down', error: error.message };
75 }
76 }
77
78 private async checkExternalAPI() {
79 try {
80 // Check external API
81 return { status: 'up', responseTime: '< 500ms' };
82 } catch (error) {
83 return { status: 'down', error: error.message };
84 }
85 }
86
87 private async checkBackgroundJobs() {
88 try {
89 // Check job queue status
90 return { status: 'up', pendingJobs: 0 };
91 } catch (error) {
92 return { status: 'down', error: error.message };
93 }
94 }
95}

Best Practices for Production

  1. Never deploy on Friday - leave time to fix problems
  2. Always test on staging - a copy of production for testing
  3. Back up before deploying - data safety
  4. Monitor metrics - CPU, memory, network, database
  5. Log everything - but not sensitive data
  6. Use blue-green deployment - zero downtime
  7. Automate everything - fewer human errors
  8. Document procedures - for the whole team

Deployment is not the end of the adventure - it's only the beginning of the real campaign! Your fort must be ready for all the trials and challenges that await across the vast provinces of production.

Go to CodeWorlds