version: '3.9' # ================================================ # Math Platform - Production Docker Compose # ================================================ services: # PostgreSQL Database postgres: image: postgres:15-alpine container_name: math-postgres environment: POSTGRES_USER: mathuser POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: mathdb PGDATA: /var/lib/postgresql/data/pgdata volumes: - postgres_data:/var/lib/postgresql/data - ./docker/init-scripts:/docker-entrypoint-initdb.d:ro ports: - "5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U mathuser -d mathdb"] interval: 10s timeout: 5s retries: 5 start_period: 10s networks: - math-network restart: unless-stopped logging: driver: "json-file" options: max-size: "10m" max-file: "3" # Redis Cache & Queue redis: image: redis:7-alpine container_name: math-redis command: > redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru --tcp-backlog 511 --timeout 0 --tcp-keepalive 300 ${REDIS_PASSWORD:+--requirepass $REDIS_PASSWORD} volumes: - redis_data:/data ports: - "6379:6379" healthcheck: test: ["CMD", "redis-cli", "--raw", "incr", "ping"] interval: 10s timeout: 3s retries: 5 start_period: 10s networks: - math-network restart: unless-stopped logging: driver: "json-file" options: max-size: "10m" max-file: "3" # Backend API backend: build: context: . dockerfile: docker/Dockerfile.backend cache_from: - math-backend:${VERSION:-1.0.0} image: math-backend:${VERSION:-1.0.0} container_name: math-backend environment: NODE_ENV: production DATABASE_URL: postgresql://mathuser:${DB_PASSWORD}@postgres:5432/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} 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: http://localhost:3000,http://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 ports: - "3001:3001" depends_on: postgres: condition: service_healthy redis: condition: service_healthy healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3001/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s networks: - math-network restart: unless-stopped logging: driver: "json-file" options: max-size: "10m" max-file: "3" deploy: resources: limits: cpus: '1' memory: 1G reservations: cpus: '0.5' memory: 512M # Frontend (Next.js) frontend: build: context: . dockerfile: docker/Dockerfile.frontend cache_from: - math-frontend:${VERSION:-1.0.0} image: math-frontend:${VERSION:-1.0.0} container_name: math-frontend environment: NODE_ENV: production NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:3001} NEXT_PUBLIC_APP_NAME: ${NEXT_PUBLIC_APP_NAME:-Plataforma de Álgebra Lineal} ports: - "3000:3000" depends_on: backend: condition: service_healthy healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000"] interval: 30s timeout: 10s retries: 3 start_period: 40s networks: - math-network restart: unless-stopped logging: driver: "json-file" options: max-size: "10m" max-file: "3" deploy: resources: limits: cpus: '0.5' memory: 512M reservations: cpus: '0.25' memory: 256M # PDF Processing Worker pdf-worker: build: context: . dockerfile: docker/Dockerfile.worker target: pdf-worker cache_from: - math-worker:${VERSION:-1.0.0} image: math-worker:${VERSION:-1.0.0} container_name: math-pdf-worker environment: NODE_ENV: production DATABASE_URL: postgresql://mathuser:${DB_PASSWORD}@postgres:5432/mathdb REDIS_HOST: redis REDIS_PORT: 6379 REDIS_PASSWORD: ${REDIS_PASSWORD} WORKER_TYPE: pdf LOG_LEVEL: ${LOG_LEVEL:-info} volumes: - worker_logs:/app/logs - ./pdfs:/app/pdfs:ro - ./pdfs/processed:/app/pdfs/processed depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - math-network restart: unless-stopped logging: driver: "json-file" options: max-size: "10m" max-file: "3" deploy: resources: limits: cpus: '0.5' memory: 512M reservations: cpus: '0.25' memory: 256M # Exercise Generation Worker (AI) exercise-worker: build: context: . dockerfile: docker/Dockerfile.worker target: exercise-worker cache_from: - math-worker:${VERSION:-1.0.0} image: math-worker:${VERSION:-1.0.0} container_name: math-exercise-worker environment: NODE_ENV: production DATABASE_URL: postgresql://mathuser:${DB_PASSWORD}@postgres:5432/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} volumes: - worker_logs:/app/logs depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - math-network restart: unless-stopped logging: driver: "json-file" options: max-size: "10m" max-file: "3" deploy: resources: limits: cpus: '0.5' memory: 512M reservations: cpus: '0.25' memory: 256M # Notification Worker (Telegram) notification-worker: build: context: . dockerfile: docker/Dockerfile.worker target: notification-worker cache_from: - math-worker:${VERSION:-1.0.0} image: math-worker:${VERSION:-1.0.0} container_name: math-notification-worker environment: NODE_ENV: production DATABASE_URL: postgresql://mathuser:${DB_PASSWORD}@postgres:5432/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} volumes: - worker_logs:/app/logs depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - math-network restart: unless-stopped logging: driver: "json-file" options: max-size: "10m" max-file: "3" deploy: resources: limits: cpus: '0.25' memory: 256M reservations: cpus: '0.1' memory: 128M # Nginx Reverse Proxy nginx: image: nginx:1.25.3-alpine container_name: math-nginx volumes: - ./docker/nginx.conf:/etc/nginx/nginx.conf:ro - ./docker/logs:/var/log/nginx - ./docker/ssl:/etc/nginx/ssl:ro ports: - "80:80" - "443:443" depends_on: frontend: condition: service_healthy backend: condition: service_healthy healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost/health"] interval: 30s timeout: 10s retries: 3 start_period: 10s networks: - math-network restart: unless-stopped logging: driver: "json-file" options: max-size: "10m" max-file: "3" deploy: resources: limits: cpus: '0.25' memory: 128M reservations: cpus: '0.1' memory: 64M # Named volumes for data persistence volumes: postgres_data: driver: local redis_data: driver: local backend_logs: driver: local worker_logs: driver: local # Network configuration networks: math-network: driver: bridge ipam: config: - subnet: 172.20.0.0/16