# Deployment Guide: LCBP3-DMS --- **Project:** LCBP3-DMS (Laem Chabang Port Phase 3 - Document Management System) **Version:** 1.5.1 **Last Updated:** 2025-12-02 **Owner:** Operations Team **Status:** Active --- ## 📋 Overview This guide provides step-by-step instructions for deploying the LCBP3-DMS system on QNAP Container Station using Docker Compose with Blue-Green deployment strategy. ### Deployment Strategy - **Platform:** QNAP TS-473A with Container Station - **Orchestration:** Docker Compose - **Deployment Method:** Blue-Green Deployment - **Zero Downtime:** Yes - **Rollback Capability:** Instant rollback via NGINX switch --- ## 🎯 Prerequisites ### Hardware Requirements | Component | Minimum Specification | | ---------- | -------------------------- | | CPU | 4 cores @ 2.0 GHz | | RAM | 16 GB | | Storage | 500 GB SSD (System + Data) | | Network | 1 Gbps Ethernet | | QNAP Model | TS-473A or equivalent | ### Software Requirements | Software | Version | Purpose | | ----------------- | ------- | ------------------------ | | QNAP QTS | 5.x+ | Operating System | | Container Station | 3.x+ | Docker Management | | Docker | 20.10+ | Container Runtime | | Docker Compose | 2.x+ | Multi-container Orchestr | ### Network Requirements - Static IP address for QNAP server - Domain name (e.g., `lcbp3-dms.example.com`) - SSL certificate (Let's Encrypt or commercial) - Firewall rules: - Port 80 (HTTP → HTTPS redirect) - Port 443 (HTTPS) - Port 22 (SSH for management) --- ## 🏗️ Infrastructure Setup ### 1. Directory Structure Create the following directory structure on QNAP: ```bash # SSH into QNAP ssh admin@qnap-ip # Create base directory mkdir -p /volume1/lcbp3 # Create blue-green environments mkdir -p /volume1/lcbp3/blue mkdir -p /volume1/lcbp3/green # Create shared directories mkdir -p /volume1/lcbp3/shared/uploads mkdir -p /volume1/lcbp3/shared/logs mkdir -p /volume1/lcbp3/shared/backups # Create persistent volumes mkdir -p /volume1/lcbp3/volumes/mariadb-data mkdir -p /volume1/lcbp3/volumes/redis-data mkdir -p /volume1/lcbp3/volumes/elastic-data # Create NGINX proxy directory mkdir -p /volume1/lcbp3/nginx-proxy # Set permissions chmod -R 755 /volume1/lcbp3 chown -R admin:administrators /volume1/lcbp3 ``` **Final Structure:** ``` /volume1/lcbp3/ ├── blue/ # Blue environment │ ├── docker-compose.yml │ ├── .env.production │ └── nginx.conf │ ├── green/ # Green environment │ ├── docker-compose.yml │ ├── .env.production │ └── nginx.conf │ ├── nginx-proxy/ # Main reverse proxy │ ├── docker-compose.yml │ ├── nginx.conf │ └── ssl/ │ ├── cert.pem │ └── key.pem │ ├── shared/ # Shared across blue/green │ ├── uploads/ │ ├── logs/ │ └── backups/ │ ├── volumes/ # Persistent data │ ├── mariadb-data/ │ ├── redis-data/ │ └── elastic-data/ │ ├── scripts/ # Deployment scripts │ ├── deploy.sh │ ├── rollback.sh │ └── health-check.sh │ └── current # File containing "blue" or "green" ``` ### 2. SSL Certificate Setup ```bash # Option 1: Let's Encrypt (Recommended) # Install certbot on QNAP opkg install certbot # Generate certificate certbot certonly --standalone \ -d lcbp3-dms.example.com \ --email admin@example.com \ --agree-tos # Copy to nginx-proxy cp /etc/letsencrypt/live/lcbp3-dms.example.com/fullchain.pem \ /volume1/lcbp3/nginx-proxy/ssl/cert.pem cp /etc/letsencrypt/live/lcbp3-dms.example.com/privkey.pem \ /volume1/lcbp3/nginx-proxy/ssl/key.pem # Option 2: Commercial Certificate # Upload cert.pem and key.pem to /volume1/lcbp3/nginx-proxy/ssl/ ``` --- ## 📝 Configuration Files ### 1. Environment Variables (.env.production) Create `.env.production` in both `blue/` and `green/` directories: ```bash # File: /volume1/lcbp3/blue/.env.production # DO NOT commit this file to Git! # Application NODE_ENV=production APP_NAME=LCBP3-DMS APP_URL=https://lcbp3-dms.example.com # Database DB_HOST=lcbp3-mariadb DB_PORT=3306 DB_USERNAME=lcbp3_user DB_PASSWORD= DB_DATABASE=lcbp3_dms DB_POOL_SIZE=20 # Redis REDIS_HOST=lcbp3-redis REDIS_PORT=6379 REDIS_PASSWORD= REDIS_DB=0 # JWT Authentication JWT_SECRET= JWT_EXPIRES_IN=8h JWT_REFRESH_EXPIRES_IN=7d # File Storage UPLOAD_PATH=/app/uploads MAX_FILE_SIZE=52428800 ALLOWED_FILE_TYPES=.pdf,.doc,.docx,.xls,.xlsx,.dwg,.zip # Email (SMTP) SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_SECURE=false SMTP_USERNAME= SMTP_PASSWORD= SMTP_FROM=noreply@example.com # Elasticsearch ELASTICSEARCH_NODE=http://lcbp3-elasticsearch:9200 ELASTICSEARCH_USERNAME=elastic ELASTICSEARCH_PASSWORD= # Rate Limiting THROTTLE_TTL=60 THROTTLE_LIMIT=100 # Logging LOG_LEVEL=info LOG_FILE_PATH=/app/logs # ClamAV (Virus Scanning) CLAMAV_HOST=lcbp3-clamav CLAMAV_PORT=3310 ``` ### 2. Docker Compose - Blue Environment ```yaml # File: /volume1/lcbp3/blue/docker-compose.yml version: '3.8' services: backend: image: lcbp3-backend:latest container_name: lcbp3-blue-backend restart: unless-stopped env_file: - .env.production volumes: - /volume1/lcbp3/shared/uploads:/app/uploads - /volume1/lcbp3/shared/logs:/app/logs depends_on: - mariadb - redis - elasticsearch networks: - lcbp3-network healthcheck: test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] interval: 30s timeout: 10s retries: 3 start_period: 40s frontend: image: lcbp3-frontend:latest container_name: lcbp3-blue-frontend restart: unless-stopped environment: - NEXT_PUBLIC_API_URL=https://lcbp3-dms.example.com/api depends_on: - backend networks: - lcbp3-network healthcheck: test: ['CMD', 'curl', '-f', 'http://localhost:3000'] interval: 30s timeout: 10s retries: 3 mariadb: image: mariadb:11.8 container_name: lcbp3-mariadb restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} MYSQL_DATABASE: ${DB_DATABASE} MYSQL_USER: ${DB_USERNAME} MYSQL_PASSWORD: ${DB_PASSWORD} volumes: - /volume1/lcbp3/volumes/mariadb-data:/var/lib/mysql networks: - lcbp3-network command: > --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max_connections=200 --innodb_buffer_pool_size=2G healthcheck: test: ['CMD', 'mysqladmin', 'ping', '-h', 'localhost'] interval: 10s timeout: 5s retries: 5 redis: image: redis:7-alpine container_name: lcbp3-redis restart: unless-stopped command: > redis-server --requirepass ${REDIS_PASSWORD} --appendonly yes --appendfsync everysec --maxmemory 2gb --maxmemory-policy allkeys-lru volumes: - /volume1/lcbp3/volumes/redis-data:/data networks: - lcbp3-network healthcheck: test: ['CMD', 'redis-cli', 'ping'] interval: 10s timeout: 3s retries: 3 elasticsearch: image: elasticsearch:8.11.0 container_name: lcbp3-elasticsearch restart: unless-stopped environment: - discovery.type=single-node - xpack.security.enabled=true - ELASTIC_PASSWORD=${ELASTICSEARCH_PASSWORD} - "ES_JAVA_OPTS=-Xms2g -Xmx2g" volumes: - /volume1/lcbp3/volumes/elastic-data:/usr/share/elasticsearch/data networks: - lcbp3-network healthcheck: test: ['CMD-SHELL', 'curl -f http://localhost:9200/_cluster/health || exit 1'] interval: 30s timeout: 10s retries: 5 networks: lcbp3-network: name: lcbp3-blue-network driver: bridge ``` ### 3. Docker Compose - NGINX Proxy ```yaml # File: /volume1/lcbp3/nginx-proxy/docker-compose.yml version: '3.8' services: nginx: image: nginx:alpine container_name: lcbp3-nginx restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./ssl:/etc/nginx/ssl:ro - /volume1/lcbp3/shared/logs/nginx:/var/log/nginx networks: - lcbp3-blue-network - lcbp3-green-network healthcheck: test: ['CMD', 'nginx', '-t'] interval: 30s timeout: 10s retries: 3 networks: lcbp3-blue-network: external: true lcbp3-green-network: external: true ``` ### 4. NGINX Configuration ```nginx # File: /volume1/lcbp3/nginx-proxy/nginx.conf user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; client_max_body_size 50M; # Gzip compression gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss; # Upstream backends (switch between blue/green) upstream backend { server lcbp3-blue-backend:3000 max_fails=3 fail_timeout=30s; keepalive 32; } upstream frontend { server lcbp3-blue-frontend:3000 max_fails=3 fail_timeout=30s; keepalive 32; } # HTTP to HTTPS redirect server { listen 80; server_name lcbp3-dms.example.com; return 301 https://$server_name$request_uri; } # HTTPS server server { listen 443 ssl http2; server_name lcbp3-dms.example.com; # SSL configuration ssl_certificate /etc/nginx/ssl/cert.pem; ssl_certificate_key /etc/nginx/ssl/key.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; # Security headers add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; # Frontend (Next.js) location / { proxy_pass http://frontend; 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; } # Backend API location /api { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ""; 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; # Timeouts for file uploads proxy_connect_timeout 300s; proxy_send_timeout 300s; proxy_read_timeout 300s; } # Health check endpoint (no logging) location /health { proxy_pass http://backend/health; access_log off; } # Static files caching location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ { proxy_pass http://frontend; expires 1y; add_header Cache-Control "public, immutable"; } } } ``` --- ## 🚀 Initial Deployment ### Step 1: Prepare Docker Images ```bash # Build images (on development machine) cd /path/to/lcbp3/backend docker build -t lcbp3-backend:1.0.0 . docker tag lcbp3-backend:1.0.0 lcbp3-backend:latest cd /path/to/lcbp3/frontend docker build -t lcbp3-frontend:1.0.0 . docker tag lcbp3-frontend:1.0.0 lcbp3-frontend:latest # Save images to tar files docker save lcbp3-backend:latest | gzip > lcbp3-backend-latest.tar.gz docker save lcbp3-frontend:latest | gzip > lcbp3-frontend-latest.tar.gz # Transfer to QNAP scp lcbp3-backend-latest.tar.gz admin@qnap-ip:/volume1/lcbp3/ scp lcbp3-frontend-latest.tar.gz admin@qnap-ip:/volume1/lcbp3/ # Load images on QNAP ssh admin@qnap-ip cd /volume1/lcbp3 docker load < lcbp3-backend-latest.tar.gz docker load < lcbp3-frontend-latest.tar.gz ``` ### Step 2: Initialize Database ```bash # Start MariaDB only cd /volume1/lcbp3/blue docker-compose up -d mariadb # Wait for MariaDB to be ready docker exec lcbp3-mariadb mysqladmin ping -h localhost # Run migrations docker-compose up -d backend docker exec lcbp3-blue-backend npm run migration:run # Seed initial data (if needed) docker exec lcbp3-blue-backend npm run seed ``` ### Step 3: Start Blue Environment ```bash cd /volume1/lcbp3/blue # Start all services docker-compose up -d # Check status docker-compose ps # View logs docker-compose logs -f # Wait for health checks sleep 30 # Test health endpoint curl http://localhost:3000/health ``` ### Step 4: Start NGINX Proxy ```bash cd /volume1/lcbp3/nginx-proxy # Create networks (if not exist) docker network create lcbp3-blue-network docker network create lcbp3-green-network # Start NGINX docker-compose up -d # Test NGINX configuration docker exec lcbp3-nginx nginx -t # Check NGINX logs docker logs lcbp3-nginx ``` ### Step 5: Set Current Environment ```bash # Mark blue as current echo "blue" > /volume1/lcbp3/current ``` ### Step 6: Verify Deployment ```bash # Test HTTPS endpoint curl -k https://lcbp3-dms.example.com/health # Test API curl -k https://lcbp3-dms.example.com/api/health # Check all containers docker ps --filter "name=lcbp3" # Check logs for errors docker-compose -f /volume1/lcbp3/blue/docker-compose.yml logs --tail=100 ``` --- ## 🔄 Blue-Green Deployment Process ### Deployment Script ```bash # File: /volume1/lcbp3/scripts/deploy.sh #!/bin/bash set -e # Exit on error # Configuration LCBP3_DIR="/volume1/lcbp3" CURRENT=$(cat $LCBP3_DIR/current) TARGET=$([[ "$CURRENT" == "blue" ]] && echo "green" || echo "blue") echo "=========================================" echo "LCBP3-DMS Blue-Green Deployment" echo "=========================================" echo "Current environment: $CURRENT" echo "Target environment: $TARGET" echo "=========================================" # Step 1: Backup database echo "[1/9] Creating database backup..." BACKUP_FILE="$LCBP3_DIR/shared/backups/db-backup-$(date +%Y%m%d-%H%M%S).sql" docker exec lcbp3-mariadb mysqldump -u root -p${DB_PASSWORD} lcbp3_dms > $BACKUP_FILE gzip $BACKUP_FILE echo "✓ Backup created: $BACKUP_FILE.gz" # Step 2: Pull latest images echo "[2/9] Pulling latest Docker images..." cd $LCBP3_DIR/$TARGET docker-compose pull echo "✓ Images pulled" # Step 3: Update configuration echo "[3/9] Updating configuration..." # Copy .env if changed if [ -f "$LCBP3_DIR/.env.production.new" ]; then cp $LCBP3_DIR/.env.production.new $LCBP3_DIR/$TARGET/.env.production echo "✓ Configuration updated" fi # Step 4: Start target environment echo "[4/9] Starting $TARGET environment..." docker-compose up -d echo "✓ $TARGET environment started" # Step 5: Wait for services to be ready echo "[5/9] Waiting for services to be healthy..." sleep 10 # Check backend health for i in {1..30}; do if docker exec lcbp3-${TARGET}-backend curl -f http://localhost:3000/health > /dev/null 2>&1; then echo "✓ Backend is healthy" break fi if [ $i -eq 30 ]; then echo "✗ Backend health check failed!" docker-compose logs backend exit 1 fi sleep 2 done # Step 6: Run database migrations echo "[6/9] Running database migrations..." docker exec lcbp3-${TARGET}-backend npm run migration:run echo "✓ Migrations completed" # Step 7: Switch NGINX to target environment echo "[7/9] Switching NGINX to $TARGET..." sed -i "s/lcbp3-${CURRENT}-backend/lcbp3-${TARGET}-backend/g" $LCBP3_DIR/nginx-proxy/nginx.conf sed -i "s/lcbp3-${CURRENT}-frontend/lcbp3-${TARGET}-frontend/g" $LCBP3_DIR/nginx-proxy/nginx.conf docker exec lcbp3-nginx nginx -t docker exec lcbp3-nginx nginx -s reload echo "✓ NGINX switched to $TARGET" # Step 8: Verify new environment echo "[8/9] Verifying new environment..." sleep 5 if curl -f -k https://lcbp3-dms.example.com/health > /dev/null 2>&1; then echo "✓ New environment is responding" else echo "✗ New environment verification failed!" echo "Rolling back..." ./rollback.sh exit 1 fi # Step 9: Stop old environment echo "[9/9] Stopping $CURRENT environment..." cd $LCBP3_DIR/$CURRENT docker-compose down echo "✓ $CURRENT environment stopped" # Update current pointer echo "$TARGET" > $LCBP3_DIR/current echo "=========================================" echo "✓ Deployment completed successfully!" echo "Active environment: $TARGET" echo "=========================================" # Send notification (optional) # /scripts/send-notification.sh "Deployment completed: $TARGET is now active" ``` ### Rollback Script ```bash # File: /volume1/lcbp3/scripts/rollback.sh #!/bin/bash set -e LCBP3_DIR="/volume1/lcbp3" CURRENT=$(cat $LCBP3_DIR/current) PREVIOUS=$([[ "$CURRENT" == "blue" ]] && echo "green" || echo "blue") echo "=========================================" echo "LCBP3-DMS Rollback" echo "=========================================" echo "Current: $CURRENT" echo "Rolling back to: $PREVIOUS" echo "=========================================" # Switch NGINX back echo "[1/3] Switching NGINX to $PREVIOUS..." sed -i "s/lcbp3-${CURRENT}-backend/lcbp3-${PREVIOUS}-backend/g" $LCBP3_DIR/nginx-proxy/nginx.conf sed -i "s/lcbp3-${CURRENT}-frontend/lcbp3-${PREVIOUS}-frontend/g" $LCBP3_DIR/nginx-proxy/nginx.conf docker exec lcbp3-nginx nginx -s reload echo "✓ NGINX switched" # Start previous environment if stopped echo "[2/3] Ensuring $PREVIOUS environment is running..." cd $LCBP3_DIR/$PREVIOUS docker-compose up -d sleep 10 echo "✓ $PREVIOUS environment is running" # Verify echo "[3/3] Verifying rollback..." if curl -f -k https://lcbp3-dms.example.com/health > /dev/null 2>&1; then echo "✓ Rollback successful" echo "$PREVIOUS" > $LCBP3_DIR/current else echo "✗ Rollback verification failed!" exit 1 fi echo "=========================================" echo "✓ Rollback completed" echo "Active environment: $PREVIOUS" echo "=========================================" ``` ### Make Scripts Executable ```bash chmod +x /volume1/lcbp3/scripts/deploy.sh chmod +x /volume1/lcbp3/scripts/rollback.sh ``` --- ## 📋 Deployment Checklist ### Pre-Deployment - [ ] Backup current database - [ ] Tag Docker images with version - [ ] Update `.env.production` if needed - [ ] Review migration scripts - [ ] Notify stakeholders of deployment window - [ ] Verify SSL certificate validity (> 30 days) - [ ] Check disk space (> 20% free) - [ ] Review recent error logs ### During Deployment - [ ] Pull latest Docker images - [ ] Start target environment (blue/green) - [ ] Run database migrations - [ ] Verify health checks pass - [ ] Switch NGINX proxy - [ ] Verify application responds correctly - [ ] Check for errors in logs - [ ] Monitor performance metrics ### Post-Deployment - [ ] Monitor logs for 30 minutes - [ ] Check performance metrics - [ ] Verify all features working - [ ] Test critical user flows - [ ] Stop old environment - [ ] Update deployment log - [ ] Notify stakeholders of completion - [ ] Archive old Docker images --- ## 🔍 Troubleshooting ### Common Issues #### 1. Container Won't Start ```bash # Check logs docker logs lcbp3-blue-backend # Check resource usage docker stats # Restart container docker restart lcbp3-blue-backend ``` #### 2. Database Connection Failed ```bash # Check MariaDB is running docker ps | grep mariadb # Test connection docker exec lcbp3-mariadb mysql -u lcbp3_user -p -e "SELECT 1" # Check environment variables docker exec lcbp3-blue-backend env | grep DB_ ``` #### 3. NGINX 502 Bad Gateway ```bash # Check backend is running curl http://localhost:3000/health # Check NGINX configuration docker exec lcbp3-nginx nginx -t # Check NGINX logs docker logs lcbp3-nginx # Reload NGINX docker exec lcbp3-nginx nginx -s reload ``` #### 4. Migration Failed ```bash # Check migration status docker exec lcbp3-blue-backend npm run migration:show # Revert last migration docker exec lcbp3-blue-backend npm run migration:revert # Re-run migrations docker exec lcbp3-blue-backend npm run migration:run ``` --- ## 📊 Monitoring ### Health Checks ```bash # Backend health curl https://lcbp3-dms.example.com/health # Database health docker exec lcbp3-mariadb mysqladmin ping # Redis health docker exec lcbp3-redis redis-cli ping # All containers status docker ps --filter "name=lcbp3" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" ``` ### Performance Monitoring ```bash # Container resource usage docker stats --no-stream # Disk usage df -h /volume1/lcbp3 # Database size docker exec lcbp3-mariadb mysql -u root -p -e " SELECT table_schema AS 'Database', ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS 'Size (MB)' FROM information_schema.tables WHERE table_schema = 'lcbp3_dms' GROUP BY table_schema;" ``` --- ## 🔐 Security Best Practices 1. **Change Default Passwords:** Update all passwords in `.env.production` 2. **SSL/TLS:** Always use HTTPS in production 3. **Firewall:** Only expose ports 80, 443, and 22 (SSH) 4. **Regular Updates:** Keep Docker images updated 5. **Backup Encryption:** Encrypt database backups 6. **Access Control:** Limit SSH access to specific IPs 7. **Secrets Management:** Never commit `.env` files to Git 8. **Log Monitoring:** Review logs daily for suspicious activity --- ## 📚 Related Documentation - [Environment Setup Guide](./environment-setup.md) - [Backup & Recovery](./backup-recovery.md) - [Monitoring & Alerting](./monitoring-alerting.md) - [Maintenance Procedures](./maintenance-procedures.md) - [ADR-015: Deployment Infrastructure](../05-decisions/ADR-015-deployment-infrastructure.md) --- **Version:** 1.5.1 **Last Updated:** 2025-12-02 **Next Review:** 2026-06-01