dowódco! Konsul Caesar.js ogłasza, że nadszedł czas na najważniejszą fazę naszej kampanii - wyprowadzenie naszego rzymskiego fortu NestJS na pole bitwy produkcji! To moment, gdy wszystkie nasze przygotowania, treningi i testy zostaną poddane prawdziwej próbie w realnych warunkach.
Deployment to proces przeniesienia aplikacji z bezpiecznego środowiska deweloperskiego do środowiska produkcyjnego, gdzie będzie służyć prawdziwym użytkownikom na całym świecie.
Zanim nasza aplikacja wyruszy na kampanię, musimy upewnić się, że wszystko jest gotowe do długiej podróży:
1// package.json - manifest naszego fortu
2{
3 "name": "legionariusze-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 - konfiguracja budowy fortu
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 - instrukcje budowy
34{
35 "extends": "./tsconfig.json",
36 "exclude": [
37 "node_modules",
38 "test",
39 "dist",
40 "**/*spec.ts"
41 ]
42}1// src/config/production.config.ts
2export const productionConfig = {
3 port: process.env.PORT || 3000,
4 nodeEnv: 'production',
5
6 // Bezpieczeństwo fortu
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 minut
15 max: 1000, // limit na IP
16 },
17 },
18
19 // Baza danych - główny magazyn tributów
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, // NIGDY true w produkcji!
29 logging: process.env.DB_LOGGING === 'true',
30 maxConnections: parseInt(process.env.DB_MAX_CONNECTIONS) || 10,
31 },
32
33 // Redis - szybka komunikacja między fortami
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 // Logowanie - kronika fortu
43 logging: {
44 level: process.env.LOG_LEVEL || 'info',
45 format: 'json',
46 transports: [
47 {
48 type: 'file',
49 filename: 'logs/legionariusze-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 - system obserwacji prowincji
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: 'legionariusze-legion-api',
75 },
76 },
77};1// src/main.ts - główny punkt startowy fortu
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 // Bezpieczeństwo fortu
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 // Kompresja odpowiedzi - mniej miejsca w ładowni
34 app.use(compression());
35
36 // CORS - kontrola dostępu
37 app.enableCors(productionConfig.security.cors);
38
39 // Globalna walidacja danych
40 app.useGlobalPipes(new ValidationPipe({
41 whitelist: true,
42 forbidNonWhitelisted: true,
43 transform: true,
44 disableErrorMessages: productionConfig.nodeEnv === 'production',
45 }));
46
47 // Globalne przedrostki API
48 app.setGlobalPrefix('api/v1');
49
50 // Dokumentacja API (tylko w dev)
51 if (productionConfig.nodeEnv !== 'production') {
52 const config = new DocumentBuilder()
53 .setTitle('Legionary Legion API')
54 .setDescription('API for managing legionariusze 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 // Uruchomienie fortu
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 - bezpieczne dokowanie
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();1#!/bin/bash
2# deploy.sh - skrypt wyprowadzenia fortu
3
4set -e
5
6echo " Starting deployment of Legionary Legion API..."
7
8# Sprawdzenie środowiska
9if [ -z "$NODE_ENV" ]; then
10 echo "❌ NODE_ENV not set!"
11 exit 1
12fi
13
14# Backup bazy danych
15echo " Creating database backup..."
16pg_dump $DATABASE_URL > backups/backup-$(date +%Y%m%d_%H%M%S).sql
17
18# Aktualizacja kodu
19echo "⬇ Pulling latest code..."
20git pull origin main
21
22# Instalacja zależności
23echo " Installing dependencies..."
24npm ci --only=production
25
26# Budowanie aplikacji
27echo "Building application..."
28npm run build
29
30# Migracje bazy danych
31echo "Running database migrations..."
32npm run migration:run
33
34# Restart aplikacji
35echo "Restarting application..."
36pm2 restart legionariusze-legion-api
37
38# Sprawdzenie health check
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!"1// ecosystem.config.js - konfiguracja legionu
2module.exports = {
3 apps: [{
4 name: 'legionariusze-legion-api',
5 script: 'dist/main.js',
6 instances: 'max', // Wykorzystaj wszystkie CPU
7 exec_mode: 'cluster',
8
9 // Zmienne środowiskowe
10 env: {
11 NODE_ENV: 'production',
12 PORT: 3000,
13 },
14
15 // Ustawienia restartów
16 max_memory_restart: '1G',
17 restart_delay: 4000,
18 max_restarts: 10,
19 min_uptime: '10s',
20
21 // Logowanie
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: 'legionariusze',
35 host: 'legion.legionariuszeapi.com',
36 ref: 'origin/main',
37 repo: 'git@github.com:legionariusze-legion/legion-api.git',
38 path: '/var/www/legionariusze-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};1# /etc/nginx/sites-available/legionariusze-legion-api
2server {
3 listen 80;
4 listen [::]:80;
5 server_name api.legionariuszelegion.com;
6
7 # Przekierowanie na 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.legionariuszelegion.com;
15
16 # Certyfikaty SSL
17 ssl_certificate /etc/letsencrypt/live/api.legionariuszelegion.com/fullchain.pem;
18 ssl_certificate_key /etc/letsencrypt/live/api.legionariuszelegion.com/privkey.pem;
19
20 # Bezpieczeństwo SSL
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 do aplikacji NestJS
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 # Timeouty
49 proxy_connect_timeout 60s;
50 proxy_send_timeout 60s;
51 proxy_read_timeout 60s;
52 }
53
54 # Statyczne pliki (jeśli są)
55 location /static {
56 alias /var/www/legionariusze-legion-api/static;
57 expires 1y;
58 add_header Cache-Control "public, immutable";
59 }
60
61 # Health check (wewnętrzny)
62 location /health {
63 access_log off;
64 proxy_pass http://127.0.0.1:3000/health;
65 }
66
67 # Bezpieczeństwo 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}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 przed migracją
9 await queryRunner.query(`
10 CREATE TABLE IF NOT EXISTS migration_backup_${Date.now()} AS
11 SELECT * FROM legionariuszes WHERE 1=2
12 `);
13
14 // Nowe tabele
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 // Indeksy dla wydajności
27 await queryRunner.query(`
28 CREATE INDEX "IDX_legion_legate" ON "legions" ("legate_name")
29 `);
30
31 // Constraints dla spójności danych
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 - tylko jeśli bezpieczne
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// Skrypt migracji produkcyjnej
50// migrate-production.sh
51#!/bin/bash
52
53echo "Starting production database migration..."
54
55# Backup przed migracją
56echo " Creating pre-migration backup..."
57pg_dump $DATABASE_URL > backups/pre-migration-$(date +%Y%m%d_%H%M%S).sql
58
59# Test migracji na kopii
60echo "🧪 Testing migration on copy..."
61npm run migration:show
62npm run migration:run --dry-run
63
64# Confirm przed wykonaniem
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# Wykonanie migracji
73echo " Running migration..."
74npm run migration:run
75
76# Weryfikacja
77echo "✅ Verifying migration..."
78npm run migration:show
79
80echo "Migration completed successfully!"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 // Sprawdzenie bazy danych
25 () => this.db.pingCheck('database'),
26
27 // Sprawdzenie pamięci (max 150MB heap)
28 () => this.memory.checkHeap('memory_heap', 150 * 1024 * 1024),
29
30 // Sprawdzenie pamięci RSS (max 300MB)
31 () => this.memory.checkRSS('memory_rss', 300 * 1024 * 1024),
32
33 // Sprawdzenie miejsca na dysku (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 dla zewnętrznych serwisów
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 // Sprawdź połączenie z Redis
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 // Sprawdź zewnętrzne 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 // Sprawdź status job queue
90 return { status: 'up', pendingJobs: 0 };
91 } catch (error) {
92 return { status: 'down', error: error.message };
93 }
94 }
95}Deployment to nie koniec kampanii - to dopiero początek prawdziwej służby na rubieżach Imperium! Twój fort musi być gotowy na wszystkie oblężenia i wyzwania, jakie czekają na froncie produkcji.