# ============================================================ # DOCKER COMPOSE PRODUCTION - ENTERPRISE GRADE # Math Platform with SSL/TLS, Health Checks, and Monitoring # ============================================================ version: '3.9' services: # ======================================== # PostgreSQL Database (Production Optimized) # ======================================== postgres: image: postgres:15.4-alpine container_name: math-postgres-prod environment: POSTGRES_USER: ${DB_USER:-mathuser} POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: ${DB_NAME:-mathdb} PGDATA: /var/lib/postgresql/data/pgdata volumes: - postgres_data:/var/lib/postgresql/data - ./docker/init-scripts:/docker-entrypoint-initdb.d:ro - ./backups:/backups ports: - "127.0.0.1:5432:5432" command: - "postgres" - "-c" - "max_connections=200" - "-c" - "shared_buffers=2GB" - "-c" - "effective_cache_size=6GB" - "-c" - "maintenance_work_mem=512MB" - "-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=5242kB" - "-c" - "min_wal_size=1GB" - "-c" - "max_wal_size=4GB" - "-c" - "max_worker_processes=4" - "-c" - "max_parallel_workers_per_gather=2" - "-c" - "max_parallel_workers=4" - "-c" - "max_parallel_maintenance_workers=2" healthcheck: test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-mathuser} -d ${DB_NAME:-mathdb}"] interval: 10s timeout: 5s retries: 5 start_period: 30s deploy: resources: limits: cpus: '2' memory: 4G reservations: cpus: '0.5' memory: 1G networks: - backend restart: unless-stopped logging: driver: "json-file" options: max-size: "50m" max-file: "5" # ======================================== # Redis Cache & Queue (Production Optimized) # ======================================== redis: image: redis:7.2.3-alpine container_name: math-redis-prod command: > redis-server --requirepass ${REDIS_PASSWORD} --appendonly yes --appendfsync everysec --maxmemory 512mb --maxmemory-policy allkeys-lru --tcp-backlog 511 --timeout 0 --tcp-keepalive 300 --save 900 1 --save 300 10 --save 60 10000 volumes: - redis_data:/data ports: - "127.0.0.1:6379:6379" healthcheck: test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"] interval: 10s timeout: 3s retries: 5 start_period: 10s deploy: resources: limits: cpus: '0.5' memory: 512M networks: - backend restart: unless-stopped logging: driver: "json-file" options: max-size: "50m" max-file: "5" # ======================================== # Backend API (Production - Multi-Instance) # ======================================== backend: build: context: . dockerfile: docker/Dockerfile.backend target: production image: math-backend:${VERSION:-1.0.0} # NOTA: container_name omitido para permitir múltiples réplicas con Docker Swarm environment: NODE_ENV: production DATABASE_URL: postgresql://${DB_USER:-mathuser}:${DB_PASSWORD}@postgres:5432/${DB_NAME:-mathdb} REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379 REDIS_HOST: redis REDIS_PORT: 6379 REDIS_PASSWORD: ${REDIS_PASSWORD} AI_API_BASE_URL: ${AI_API_BASE_URL} AI_API_KEY: ${AI_API_KEY} AI_MODEL: ${AI_MODEL} TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN} TELEGRAM_ADMIN_CHAT_ID: ${TELEGRAM_ADMIN_CHAT_ID} JWT_SECRET: ${JWT_SECRET} JWT_EXPIRES_IN: ${JWT_EXPIRES_IN:-15m} BACKEND_PORT: 3001 LOG_LEVEL: ${LOG_LEVEL:-info} CORS_ORIGIN: ${CORS_ORIGIN:-https://localhost} AUTH_RATE_LIMIT_WINDOW_MS: ${AUTH_RATE_LIMIT_WINDOW_MS:-900000} AUTH_RATE_LIMIT_MAX: ${AUTH_RATE_LIMIT_MAX:-20} volumes: - backend_logs:/app/logs - ./pdfs:/app/pdfs:ro expose: - "3001" depends_on: postgres: condition: service_healthy redis: condition: service_healthy healthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3001/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s deploy: replicas: 2 update_config: parallelism: 1 delay: 10s order: start-first restart_policy: condition: on-failure delay: 5s max_attempts: 3 resources: limits: cpus: '1' memory: 1G reservations: cpus: '0.25' memory: 256M networks: - backend - frontend logging: driver: "json-file" options: max-size: "50m" max-file: "5" # ======================================== # Frontend (Next.js - Production) # ======================================== frontend: build: context: . dockerfile: docker/Dockerfile.frontend image: math-frontend:${VERSION:-1.0.0} # NOTA: container_name omitido para permitir múltiples réplicas con Docker Swarm environment: NODE_ENV: production NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-/api} NEXT_PUBLIC_APP_NAME: ${NEXT_PUBLIC_APP_NAME:-Plataforma de Álgebra Lineal} expose: - "3000" depends_on: backend: condition: service_healthy healthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000"] interval: 30s timeout: 10s retries: 3 start_period: 40s deploy: replicas: 2 resources: limits: cpus: '0.5' memory: 512M networks: - frontend logging: driver: "json-file" options: max-size: "50m" max-file: "5" # ======================================== # Nginx Reverse Proxy with SSL # ======================================== nginx: image: nginx:1.25-alpine container_name: math-nginx-prod volumes: - ./docker/nginx/nginx.prod.conf:/etc/nginx/nginx.conf:ro - ./docker/ssl:/etc/nginx/ssl:ro - certbot-data:/etc/letsencrypt - certbot-www:/var/www/certbot - ./docker/logs:/var/log/nginx ports: - "80:80" - "443:443" depends_on: frontend: condition: service_healthy backend: condition: service_healthy healthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"] interval: 30s timeout: 10s retries: 3 start_period: 10s command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'" deploy: resources: limits: cpus: '0.25' memory: 128M networks: - frontend - backend restart: unless-stopped logging: driver: "json-file" options: max-size: "50m" max-file: "5" # ======================================== # Certbot for SSL Certificates # ======================================== certbot: image: certbot/certbot:latest container_name: math-certbot volumes: - certbot-data:/etc/letsencrypt - certbot-www:/var/www/certbot entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'" networks: - frontend restart: unless-stopped # ======================================== # PDF Processing Worker # ======================================== pdf-worker: build: context: . dockerfile: docker/Dockerfile.worker target: pdf-worker image: math-worker:${VERSION:-1.0.0} container_name: math-pdf-worker-prod environment: NODE_ENV: production DATABASE_URL: postgresql://${DB_USER:-mathuser}:${DB_PASSWORD}@postgres:5432/${DB_NAME:-mathdb} REDIS_HOST: redis REDIS_PORT: 6379 REDIS_PASSWORD: ${REDIS_PASSWORD} WORKER_TYPE: pdf LOG_LEVEL: ${LOG_LEVEL:-info} HEALTH_PORT: 3002 volumes: - worker_logs:/app/logs - ./pdfs:/app/pdfs:ro - ./pdfs/processed:/app/pdfs/processed depends_on: postgres: condition: service_healthy redis: condition: service_healthy healthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3002/health"] interval: 30s timeout: 10s retries: 3 start_period: 30s deploy: resources: limits: cpus: '0.5' memory: 512M networks: - backend restart: unless-stopped logging: driver: "json-file" options: max-size: "50m" max-file: "5" # ======================================== # Exercise Generation Worker (AI) # ======================================== exercise-worker: build: context: . dockerfile: docker/Dockerfile.worker target: exercise-worker image: math-worker:${VERSION:-1.0.0} container_name: math-exercise-worker-prod environment: NODE_ENV: production DATABASE_URL: postgresql://${DB_USER:-mathuser}:${DB_PASSWORD}@postgres:5432/${DB_NAME:-mathdb} REDIS_HOST: redis REDIS_PORT: 6379 REDIS_PASSWORD: ${REDIS_PASSWORD} AI_API_BASE_URL: ${AI_API_BASE_URL} AI_API_KEY: ${AI_API_KEY} AI_MODEL: ${AI_MODEL} WORKER_TYPE: exercise LOG_LEVEL: ${LOG_LEVEL:-info} HEALTH_PORT: 3003 volumes: - worker_logs:/app/logs depends_on: postgres: condition: service_healthy redis: condition: service_healthy healthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3003/health"] interval: 30s timeout: 10s retries: 3 start_period: 30s deploy: resources: limits: cpus: '0.5' memory: 512M networks: - backend restart: unless-stopped logging: driver: "json-file" options: max-size: "50m" max-file: "5" # ======================================== # Notification Worker (Telegram) # ======================================== notification-worker: build: context: . dockerfile: docker/Dockerfile.worker target: notification-worker image: math-worker:${VERSION:-1.0.0} container_name: math-notification-worker-prod environment: NODE_ENV: production DATABASE_URL: postgresql://${DB_USER:-mathuser}:${DB_PASSWORD}@postgres:5432/${DB_NAME:-mathdb} REDIS_HOST: redis REDIS_PORT: 6379 REDIS_PASSWORD: ${REDIS_PASSWORD} TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN} TELEGRAM_ADMIN_CHAT_ID: ${TELEGRAM_ADMIN_CHAT_ID} WORKER_TYPE: notification LOG_LEVEL: ${LOG_LEVEL:-info} HEALTH_PORT: 3004 volumes: - worker_logs:/app/logs depends_on: postgres: condition: service_healthy redis: condition: service_healthy healthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3004/health"] interval: 30s timeout: 10s retries: 3 start_period: 30s deploy: resources: limits: cpus: '0.25' memory: 256M networks: - backend restart: unless-stopped logging: driver: "json-file" options: max-size: "50m" max-file: "5" # ======================================== # Named Volumes # ======================================== volumes: postgres_data: driver: local redis_data: driver: local backend_logs: driver: local worker_logs: driver: local certbot-data: driver: local certbot-www: driver: local # ======================================== # Networks # ======================================== networks: backend: driver: bridge internal: true ipam: config: - subnet: 172.20.0.0/16 frontend: driver: bridge ipam: config: - subnet: 172.21.0.0/16