# 🚀 Production Docker Compose # Complete containerized production setup services: # Reverse Proxy - Nginx proxy: image: nginx:1.27-alpine container_name: portal-proxy ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - certbot-www:/var/www/certbot - letsencrypt:/etc/letsencrypt depends_on: frontend: condition: service_started backend: condition: service_healthy restart: unless-stopped networks: - app-network labels: - "prod.portal.service=proxy" - "prod.portal.version=1.0.0" # Certbot for TLS certificates (manual DNS/HTTP challenges) certbot: image: certbot/certbot:latest container_name: portal-certbot volumes: - certbot-www:/var/www/certbot - letsencrypt:/etc/letsencrypt entrypoint: /bin/sh command: -c "trap exit TERM; while :; do sleep 12h & wait $${!}; certbot renew --webroot -w /var/www/certbot --deploy-hook 'nginx -s reload'; done" networks: - app-network # Frontend - Next.js Portal frontend: build: context: ../../ dockerfile: apps/portal/Dockerfile target: production container_name: portal-frontend expose: - "3000" environment: - NODE_ENV=production - PORT=3000 - HOSTNAME=0.0.0.0 - NEXT_PUBLIC_API_BASE=${NEXT_PUBLIC_API_BASE:-http://localhost} - NEXT_PUBLIC_APP_NAME=${NEXT_PUBLIC_APP_NAME:-Customer Portal} - NEXT_PUBLIC_APP_VERSION=${NEXT_PUBLIC_APP_VERSION:-1.0.0} restart: unless-stopped depends_on: backend: condition: service_healthy networks: - app-network labels: - "prod.portal.service=frontend" - "prod.portal.version=1.0.0" # Backend - NestJS BFF backend: build: context: ../../ dockerfile: apps/bff/Dockerfile target: production container_name: portal-backend expose: - "4000" environment: - NODE_ENV=production - BFF_PORT=${BFF_PORT:-4000} # Database - DATABASE_URL=${DATABASE_URL} # Cache - REDIS_URL=${REDIS_URL} # Security - JWT_SECRET=${JWT_SECRET} - JWT_EXPIRES_IN=${JWT_EXPIRES_IN:-7d} - BCRYPT_ROUNDS=${BCRYPT_ROUNDS:-12} - CORS_ORIGIN=${CORS_ORIGIN} # External APIs - WHMCS_BASE_URL=${WHMCS_BASE_URL} - WHMCS_API_IDENTIFIER=${WHMCS_API_IDENTIFIER} - WHMCS_API_SECRET=${WHMCS_API_SECRET} - SF_LOGIN_URL=${SF_LOGIN_URL} - SF_CLIENT_ID=${SF_CLIENT_ID} - SF_PRIVATE_KEY_PATH=${SF_PRIVATE_KEY_PATH} - SF_USERNAME=${SF_USERNAME} # Logging - LOG_LEVEL=${LOG_LEVEL:-info} - LOG_FORMAT=${LOG_FORMAT:-json} # Performance - NODE_OPTIONS=${NODE_OPTIONS:---max-old-space-size=512} volumes: - ../../secrets:/app/secrets:ro restart: unless-stopped depends_on: database: condition: service_healthy cache: condition: service_healthy networks: - app-network labels: - "prod.portal.service=backend" - "prod.portal.version=1.0.0" # PostgreSQL Production Database database: image: postgres:17-alpine container_name: portal-database environment: POSTGRES_DB: ${POSTGRES_DB:-portal_prod} POSTGRES_USER: ${POSTGRES_USER:-portal} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C" # Performance settings POSTGRES_SHARED_PRELOAD_LIBRARIES: pg_stat_statements volumes: - postgres_data:/var/lib/postgresql/data - ./postgres.conf:/etc/postgresql/postgresql.conf:ro restart: unless-stopped networks: - app-network labels: - "prod.portal.service=database" healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-portal} -d ${POSTGRES_DB:-portal_prod}"] interval: 10s timeout: 5s retries: 5 start_period: 30s # Production PostgreSQL optimizations command: > postgres -c max_connections=100 -c shared_buffers=256MB -c effective_cache_size=1GB -c maintenance_work_mem=64MB -c checkpoint_completion_target=0.9 -c random_page_cost=1.1 -c effective_io_concurrency=200 -c work_mem=4MB -c huge_pages=off -c min_wal_size=1GB -c max_wal_size=4GB # Redis Production Cache cache: image: redis:8-alpine container_name: portal-cache volumes: - redis_data:/data - ./redis.conf:/usr/local/etc/redis/redis.conf:ro restart: unless-stopped networks: - app-network labels: - "prod.portal.service=cache" healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 3s retries: 5 # Production Redis optimizations command: > redis-server /usr/local/etc/redis/redis.conf --maxmemory 256mb --maxmemory-policy allkeys-lru --save 900 1 --save 300 10 --save 60 10000 --appendonly yes --appendfsync everysec volumes: postgres_data: driver: local labels: - "prod.portal.volume=database" redis_data: driver: local labels: - "prod.portal.volume=cache" certbot-www: driver: local letsencrypt: driver: local networks: app-network: driver: bridge name: portal-prod-network labels: - "prod.portal.network=main"