# ============================================================== # OPTIM AI BRE Engine — Production Docker Compose # Single command deployment: docker compose up -d # # Architecture: # nginx (port 80/443) → frontend (3000) + backend (8080) # backend → postgres (5432) + redis (6379) # # All services run inside Docker. Nothing needs to be installed # on the host except Docker Desktop / Docker Engine. # ============================================================== name: optim-ai-bre # ============================================================== # NETWORKS # ============================================================== networks: bre-public: driver: bridge bre-internal: driver: bridge internal: true # ============================================================== # PERSISTENT VOLUMES # ============================================================== volumes: postgres-data: driver: local name: bre_postgres_data redis-data: driver: local name: bre_redis_data backend-logs: driver: local name: bre_backend_logs backend-reports: driver: local name: bre_backend_reports nginx-logs: driver: local name: bre_nginx_logs db-backups: driver: local name: bre_db_backups # ============================================================== # SERVICES # ============================================================== services: # ------------------------------------------------------------ # NGINX — Reverse Proxy & SSL Termination # Entry point for all traffic on port 80 (and 443 for HTTPS) # Routes: /api/* → backend | /* → frontend # ------------------------------------------------------------ bre-nginx: image: nginx:${NGINX_VERSION:-1.25-alpine} container_name: bre-nginx restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./docker/nginx.conf:/etc/nginx/nginx.conf:ro - nginx-logs:/var/log/nginx # SSL certificates (optional — uncomment when you have certs): # - ./docker/ssl:/etc/nginx/ssl:ro networks: - bre-public - bre-internal depends_on: bre-backend: condition: service_healthy bre-frontend: condition: service_healthy healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost/nginx-health"] interval: 30s timeout: 10s retries: 3 start_period: 15s logging: driver: json-file options: max-size: "10m" max-file: "5" # ------------------------------------------------------------ # BACKEND — .NET 8 BRE Engine API # Internal only — not exposed to host, nginx proxies to it # ------------------------------------------------------------ bre-backend: build: context: ./src/backend dockerfile: ../../docker/Dockerfile.backend image: optim-ai-bre/backend:latest container_name: bre-backend restart: unless-stopped networks: - bre-internal environment: ASPNETCORE_ENVIRONMENT: Production ASPNETCORE_URLS: http://+:8080 DOTNET_RUNNING_IN_CONTAINER: "true" # Database — reads from .env file ConnectionStrings__PostgreSQL: >- Host=bre-postgres;Port=5432; Database=${POSTGRES_DB:-optimai_bre}; Username=${POSTGRES_USER:-optimai}; Password=${POSTGRES_PASSWORD}; Pooling=true;Minimum Pool Size=5;Maximum Pool Size=100; Connection Lifetime=300;Keepalive=60 ConnectionStrings__Redis: >- bre-redis:6379, password=${REDIS_PASSWORD}, abortConnect=false, connectRetry=3, connectTimeout=5000, syncTimeout=5000 # JWT Jwt__SecretKey: ${JWT_SECRET_KEY} Jwt__Issuer: ${JWT_ISSUER:-OptimAI.BRE} Jwt__Audience: ${JWT_AUDIENCE:-OptimAI.BRE.Clients} Jwt__AccessTokenExpiryMinutes: ${JWT_ACCESS_TOKEN_EXPIRY_MINUTES:-480} Jwt__RefreshTokenExpiryDays: ${JWT_REFRESH_TOKEN_EXPIRY_DAYS:-30} # AI Service AiOptions__UseAzureOpenAI: ${USE_AZURE_OPENAI:-false} AiOptions__Endpoint: ${AZURE_OPENAI_ENDPOINT:-} AiOptions__ApiKey: ${AZURE_OPENAI_KEY:-} AiOptions__ModelName: ${AZURE_OPENAI_MODEL:-gpt-4o} # CORS AllowedOrigins__0: ${APP_DOMAIN:-http://localhost} AllowedOrigins__1: http://bre-frontend:3000 # Rule Engine RuleEngine__MaxConcurrentExecutions: ${RULE_ENGINE_MAX_CONCURRENT:-100} RuleEngine__DefaultTimeoutMs: ${RULE_ENGINE_TIMEOUT_MS:-5000} RuleEngine__EnableAiByDefault: "false" RuleEngine__CacheRulesTtlMinutes: ${RULE_ENGINE_CACHE_TTL_MINUTES:-5} # Serilog Serilog__MinimumLevel__Default: ${LOG_LEVEL:-Information} volumes: - backend-logs:/app/logs - backend-reports:/app/reports depends_on: bre-postgres: condition: service_healthy bre-redis: condition: service_healthy healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 30s timeout: 15s retries: 5 start_period: 60s logging: driver: json-file options: max-size: "20m" max-file: "10" # ------------------------------------------------------------ # FRONTEND — Next.js 14 UI # Internal only — nginx proxies / to this service # NEXT_PUBLIC_API_URL=/api/v1 → nginx routes to backend # ------------------------------------------------------------ bre-frontend: build: context: ./src/frontend/optim-ai-bre-ui dockerfile: ../../../docker/Dockerfile.frontend args: NEXT_PUBLIC_API_URL: /api/v1 image: optim-ai-bre/frontend:latest container_name: bre-frontend restart: unless-stopped networks: - bre-internal environment: NODE_ENV: production NEXT_TELEMETRY_DISABLED: "1" PORT: "3000" HOSTNAME: "0.0.0.0" healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost:3000/api/health"] interval: 30s timeout: 10s retries: 3 start_period: 30s logging: driver: json-file options: max-size: "10m" max-file: "5" # ------------------------------------------------------------ # POSTGRESQL 15 — Primary Database # Persistent volume: bre_postgres_data # Init scripts auto-run on first empty volume start # ------------------------------------------------------------ bre-postgres: image: postgres:${POSTGRES_VERSION:-15-alpine} container_name: bre-postgres restart: unless-stopped networks: - bre-internal environment: POSTGRES_DB: ${POSTGRES_DB:-optimai_bre} POSTGRES_USER: ${POSTGRES_USER:-optimai} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_INITDB_ARGS: "--encoding=UTF8 --lc-collate=C --lc-ctype=C" PGDATA: /var/lib/postgresql/data/pgdata volumes: # Persistent data volume - postgres-data:/var/lib/postgresql/data # Init scripts — run automatically on empty volume (first start) - ./docker/postgres-init:/docker-entrypoint-initdb.d/init:ro - ./database/migrations:/docker-entrypoint-initdb.d/migrations:ro # Backup folder — mapped to host for easy backup access - db-backups:/backups healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-optimai} -d ${POSTGRES_DB:-optimai_bre}"] interval: 10s timeout: 5s retries: 10 start_period: 30s command: > postgres -c max_connections=200 -c shared_buffers=256MB -c effective_cache_size=768MB -c maintenance_work_mem=64MB -c checkpoint_completion_target=0.9 -c wal_buffers=16MB -c default_statistics_target=100 -c random_page_cost=1.1 -c effective_io_concurrency=200 -c work_mem=4MB -c min_wal_size=1GB -c max_wal_size=4GB -c log_min_duration_statement=1000 -c log_line_prefix='%t [%p] %u@%d ' logging: driver: json-file options: max-size: "10m" max-file: "5" # ------------------------------------------------------------ # REDIS 7 — Rule Cache & Session Store # Persistent volume: bre_redis_data # Password-protected in production # ------------------------------------------------------------ bre-redis: image: redis:${REDIS_VERSION:-7-alpine} container_name: bre-redis restart: unless-stopped networks: - bre-internal command: > redis-server --requirepass ${REDIS_PASSWORD} --maxmemory 512mb --maxmemory-policy allkeys-lru --save 900 1 --save 300 10 --save 60 10000 --appendonly yes --appendfsync everysec --loglevel notice volumes: - redis-data:/data healthcheck: test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"] interval: 10s timeout: 5s retries: 5 start_period: 10s logging: driver: json-file options: max-size: "5m" max-file: "3" # ------------------------------------------------------------ # DB BACKUP — Scheduled PostgreSQL backup (runs daily at 2AM) # Backups stored in the db-backups volume # Keep 7 days of backups # ------------------------------------------------------------ bre-db-backup: image: postgres:${POSTGRES_VERSION:-15-alpine} container_name: bre-db-backup restart: unless-stopped networks: - bre-internal environment: PGPASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB:-optimai_bre} POSTGRES_USER: ${POSTGRES_USER:-optimai} volumes: - db-backups:/backups entrypoint: > sh -c ' while true; do TIMESTAMP=$$(date +%Y%m%d_%H%M%S) BACKUP_FILE="/backups/bre_backup_$$TIMESTAMP.sql.gz" echo "[$$TIMESTAMP] Starting database backup..." pg_dump -h bre-postgres -U $$POSTGRES_USER -d $$POSTGRES_DB | gzip > $$BACKUP_FILE echo "[$$TIMESTAMP] Backup saved: $$BACKUP_FILE" # Delete backups older than 7 days find /backups -name "bre_backup_*.sql.gz" -mtime +7 -delete echo "[$$TIMESTAMP] Old backups cleaned. Sleeping 24h..." sleep 86400 done ' depends_on: bre-postgres: condition: service_healthy logging: driver: json-file options: max-size: "5m" max-file: "3"