23 KiB
23 KiB
Deployment Guide: LCBP3-DMS
Project: LCBP3-DMS (Laem Chabang Port Phase 3 - Document Management System) Version: 1.6.0 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:
# 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
# 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:
# 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=<CHANGE_ME_STRONG_PASSWORD>
DB_DATABASE=lcbp3_dms
DB_POOL_SIZE=20
# Redis
REDIS_HOST=lcbp3-redis
REDIS_PORT=6379
REDIS_PASSWORD=<CHANGE_ME_STRONG_PASSWORD>
REDIS_DB=0
# JWT Authentication
JWT_SECRET=<CHANGE_ME_RANDOM_64_CHAR_STRING>
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=<YOUR_EMAIL>
SMTP_PASSWORD=<YOUR_APP_PASSWORD>
SMTP_FROM=noreply@example.com
# Elasticsearch
ELASTICSEARCH_NODE=http://lcbp3-elasticsearch:9200
ELASTICSEARCH_USERNAME=elastic
ELASTICSEARCH_PASSWORD=<CHANGE_ME>
# 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
# 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
# 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
# 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
# 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
# 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
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
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
# Mark blue as current
echo "blue" > /volume1/lcbp3/current
Step 6: Verify Deployment
# 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
# 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
# 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
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.productionif 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
# Check logs
docker logs lcbp3-blue-backend
# Check resource usage
docker stats
# Restart container
docker restart lcbp3-blue-backend
2. Database Connection Failed
# 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
# 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
# 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
# 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
# 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
- Change Default Passwords: Update all passwords in
.env.production - SSL/TLS: Always use HTTPS in production
- Firewall: Only expose ports 80, 443, and 22 (SSH)
- Regular Updates: Keep Docker images updated
- Backup Encryption: Encrypt database backups
- Access Control: Limit SSH access to specific IPs
- Secrets Management: Never commit
.envfiles to Git - Log Monitoring: Review logs daily for suspicious activity
📚 Related Documentation
- Environment Setup Guide
- Backup & Recovery
- Monitoring & Alerting
- Maintenance Procedures
- ADR-015: Deployment Infrastructure
Version: 1.6.0 Last Updated: 2025-12-02 Next Review: 2026-06-01