Master strategist! Senator Cicero says: "The Empire never sleeps, and the roads must always be open." Just as Roman legions changed guard without leaving the walls undefended, modern applications must be updated without interruptions. It's time to learn advanced deployment strategies!
Blue-Green Deployment is a technique where we maintain two identical production environments (blue and green). At any given moment, only one serves traffic while the other is ready for the new version. The switch happens instantly - with zero downtime.
Imagine two gates to the Imperial city. One (Blue) is currently open and serving travelers. The other (Green) is being prepared with new fortifications. When Green is ready, the guards redirect traffic - zero downtime!
1// src/deployment/blue-green.config.ts
2interface DeploymentSlot {
3 name: 'blue' | 'green';
4 port: number;
5 version: string;
6 status: 'active' | 'standby' | 'deploying';
7 healthCheckUrl: string;
8 startedAt: Date;
9}
10
11interface BlueGreenConfig {
12 activeSlot: 'blue' | 'green';
13 slots: {
14 blue: DeploymentSlot;
15 green: DeploymentSlot;
16 };
17 loadBalancerUrl: string;
18 healthCheckInterval: number; // ms
19 healthCheckRetries: number;
20 rollbackTimeout: number; // ms
21}
22
23const deploymentConfig: BlueGreenConfig = {
24 activeSlot: 'blue',
25 slots: {
26 blue: {
27 name: 'blue',
28 port: 3001,
29 version: '2.3.0',
30 status: 'active',
31 healthCheckUrl: 'http://blue.imperium.internal:3001/health',
32 startedAt: new Date(),
33 },
34 green: {
35 name: 'green',
36 port: 3002,
37 version: '2.4.0',
38 status: 'standby',
39 healthCheckUrl: 'http://green.imperium.internal:3002/health',
40 startedAt: new Date(),
41 },
42 },
43 loadBalancerUrl: 'http://lb.imperium.internal',
44 healthCheckInterval: 5000,
45 healthCheckRetries: 3,
46 rollbackTimeout: 30000,
47};Rolling updates is another strategy - gradually replacing old instances with new ones, one by one, like changing centurion guards. This way, some instances are always serving traffic.
1// src/deployment/rolling-update.service.ts
2import { Injectable, Logger } from '@nestjs/common';
3
4interface Instance {
5 id: string;
6 version: string;
7 status: 'running' | 'updating' | 'ready' | 'failed';
8 port: number;
9}
10
11@Injectable()
12export class RollingUpdateService {
13 private readonly logger = new Logger(RollingUpdateService.name);
14
15 async performRollingUpdate(
16 instances: Instance[],
17 newVersion: string,
18 maxUnavailable: number = 1,
19 ): Promise<void> {
20 this.logger.log(
21 \`Starting rolling update to version \${newVersion}\`
22 );
23 this.logger.log(
24 \`Instances: \${instances.length}, max unavailable: \${maxUnavailable}\`
25 );
26
27 // Update instances in batches
28 for (let i = 0; i < instances.length; i += maxUnavailable) {
29 const batch = instances.slice(i, i + maxUnavailable);
30
31 for (const instance of batch) {
32 instance.status = 'updating';
33 this.logger.log(\`Updating instance \${instance.id}...\`);
34
35 // 1. Remove instance from load balancer
36 await this.removeFromLoadBalancer(instance);
37
38 // 2. Wait for current requests to complete
39 await this.drainConnections(instance);
40
41 // 3. Update to new version
42 await this.deployNewVersion(instance, newVersion);
43
44 // 4. Check health
45 const healthy = await this.waitForHealthy(instance);
46
47 if (healthy) {
48 instance.status = 'running';
49 instance.version = newVersion;
50 await this.addToLoadBalancer(instance);
51 this.logger.log(\`Instance \${instance.id} updated\`);
52 } else {
53 instance.status = 'failed';
54 this.logger.error(
55 \`Instance \${instance.id} - rollback!\`
56 );
57 await this.rollback(instance);
58 }
59 }
60 }
61 }
62
63 private async removeFromLoadBalancer(instance: Instance) {
64 // Remove instance from load balancer pool
65 }
66
67 private async drainConnections(instance: Instance) {
68 // Wait for active connections to finish (graceful)
69 }
70
71 private async deployNewVersion(
72 instance: Instance, version: string
73 ) {
74 // Deploy new version on the instance
75 }
76
77 private async waitForHealthy(instance: Instance): Promise<boolean> {
78 const maxRetries = 10;
79 for (let i = 0; i < maxRetries; i++) {
80 try {
81 // Check /health endpoint
82 const healthy = true; // await fetch health check
83 if (healthy) return true;
84 } catch {
85 await new Promise(r => setTimeout(r, 2000));
86 }
87 }
88 return false;
89 }
90
91 private async addToLoadBalancer(instance: Instance) {
92 // Add instance back to the pool
93 }
94
95 private async rollback(instance: Instance) {
96 // Restore previous version
97 }
98}Health checks are the Empire's scouts - they verify whether the new fortification is ready to accept traffic before we open the gates.
1// src/health/deployment-health.controller.ts
2import { Controller, Get } from '@nestjs/common';
3import {
4 HealthCheck, HealthCheckService,
5 MongooseHealthIndicator, MemoryHealthIndicator,
6 DiskHealthIndicator,
7} from '@nestjs/terminus';
8
9@Controller('health')
10export class DeploymentHealthController {
11 constructor(
12 private health: HealthCheckService,
13 private mongoose: MongooseHealthIndicator,
14 private memory: MemoryHealthIndicator,
15 private disk: DiskHealthIndicator,
16 ) {}
17
18 // Liveness - is the application alive?
19 @Get('live')
20 @HealthCheck()
21 checkLiveness() {
22 return this.health.check([
23 () => this.memory.checkHeap('memory_heap', 200 * 1024 * 1024),
24 ]);
25 }
26
27 // Readiness - is it ready for traffic?
28 @Get('ready')
29 @HealthCheck()
30 checkReadiness() {
31 return this.health.check([
32 () => this.mongoose.pingCheck('mongodb'),
33 () => this.memory.checkHeap('memory_heap', 200 * 1024 * 1024),
34 () => this.disk.checkStorage('disk', {
35 thresholdPercent: 0.9, path: '/',
36 }),
37 ]);
38 }
39
40 // Startup - did the application start correctly?
41 @Get('startup')
42 @HealthCheck()
43 checkStartup() {
44 return this.health.check([
45 () => this.mongoose.pingCheck('mongodb'),
46 ]);
47 }
48}Database migrations are the most challenging element of zero-downtime deployment. Old and new code must coexist with the same database.
1// src/database/migration-strategy.ts
2
3// RULE: Migrations must be BACKWARD COMPATIBLE
4// The old version of the code must work with the new database
5
6// Example: Renaming a column
7// BAD (causes downtime):
8// Step 1: ALTER TABLE legions RENAME COLUMN name TO legion_name;
9// --> Old code looks for "name" and crashes!
10
11// GOOD (zero-downtime, 3 steps):
12interface MigrationStrategy {
13 // Deployment 1: Add new column
14 step1_addColumn: string;
15 // Deployment 2: Code reads from both, writes to both
16 step2_dualWrite: string;
17 // Deployment 3: Remove old column (after full migration)
18 step3_removeOldColumn: string;
19}
20
21const renameMigration: MigrationStrategy = {
22 step1_addColumn: \`
23 // Migration 1: Add legion_name column
24 db.collection('legions').updateMany(
25 {},
26 [{ $set: { legion_name: '$name' } }]
27 );
28 // Old code still reads 'name' - works!
29 \`,
30 step2_dualWrite: \`
31 // Code v2: Write to both columns
32 async updateLegion(id: string, name: string) {
33 await this.model.updateOne(
34 { _id: id },
35 { $set: { name, legion_name: name } }
36 );
37 }
38 // Read from the new column
39 async getLegion(id: string) {
40 const doc = await this.model.findById(id);
41 return doc.legion_name || doc.name; // Fallback
42 }
43 \`,
44 step3_removeOldColumn: \`
45 // Migration 3: Remove old column (after full deployment of v2)
46 db.collection('legions').updateMany(
47 {},
48 { $unset: { name: '' } }
49 );
50 \`,
51};Feature flags are like the emperor's secret orders - they allow enabling and disabling features without deploying new code.
1// src/features/feature-flag.service.ts
2import { Injectable } from '@nestjs/common';
3
4interface FeatureFlag {
5 name: string;
6 enabled: boolean;
7 rolloutPercentage: number; // 0-100
8 allowedUsers: string[];
9}
10
11@Injectable()
12export class FeatureFlagService {
13 private flags: Map<string, FeatureFlag> = new Map();
14
15 isEnabled(flagName: string, userId?: string): boolean {
16 const flag = this.flags.get(flagName);
17 if (!flag) return false;
18
19 // Flag completely disabled
20 if (!flag.enabled) return false;
21
22 // User on the allowed list (e.g., testers)
23 if (userId && flag.allowedUsers.includes(userId)) {
24 return true;
25 }
26
27 // Gradual rollout (canary release)
28 if (flag.rolloutPercentage < 100) {
29 const hash = this.hashUserId(userId || 'anonymous');
30 return (hash % 100) < flag.rolloutPercentage;
31 }
32
33 return true;
34 }
35
36 private hashUserId(userId: string): number {
37 let hash = 0;
38 for (let i = 0; i < userId.length; i++) {
39 hash = ((hash << 5) - hash) + userId.charCodeAt(i);
40 hash |= 0;
41 }
42 return Math.abs(hash);
43 }
44}
45
46// Usage in a controller
47@Controller('legions')
48class LegionsController {
49 constructor(private features: FeatureFlagService) {}
50
51 @Get()
52 async getLegions(@Req() req) {
53 if (this.features.isEnabled('new-ranking-system', req.user?.id)) {
54 return this.getNewRanking();
55 }
56 return this.getOldRanking();
57 }
58}PM2 in cluster mode is like deploying multiple garrisons - it uses all CPU cores and provides automatic restart and zero-downtime reload.
1// ecosystem.config.js - PM2 configuration
2module.exports = {
3 apps: [{
4 name: 'imperium-api',
5 script: 'dist/main.js',
6 instances: 'max', // Use all CPU cores
7 exec_mode: 'cluster', // Cluster mode
8 autorestart: true,
9 watch: false,
10 max_memory_restart: '500M',
11
12 // Zero-downtime reload
13 wait_ready: true, // Wait for process.send('ready')
14 listen_timeout: 10000, // Max time for readiness
15 kill_timeout: 5000, // Time for graceful shutdown
16
17 env_production: {
18 NODE_ENV: 'production',
19 PORT: 3000,
20 },
21 }],
22};
23
24// In main.ts - signaling readiness
25async function bootstrap() {
26 const app = await NestFactory.create(AppModule);
27 await app.listen(3000);
28
29 // Notify PM2 that the application is ready
30 if (process.send) {
31 process.send('ready');
32 }
33}PM2 commands for zero-downtime:
1# Reload without downtime (graceful)
2pm2 reload imperium-api
3
4# Instance status
5pm2 status
6
7# Real-time monitoring
8pm2 monitBlue-Green Deployment, rolling updates, and feature flags are powerful tools in every Imperial architect's arsenal. Thanks to them, your deployments will be as smooth as a guard change in the best-organized legion!