# 🚀 Production Docker Compose # Complete containerized production setup services: # Frontend - Next.js Portal frontend: build: context: ../../ dockerfile: apps/portal/Dockerfile target: production container_name: portal-frontend ports: - "127.0.0.1:3000: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 ports: - "127.0.0.1:4000: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" networks: app-network: driver: bridge name: portal-prod-network labels: - "prod.portal.network=main"