version: "3.9" # ============================================================ # H3R7Tech Turf SaaS — Docker Compose # Services: app (x4) + postgres + redis + prometheus + grafana + nginx # ============================================================ x-app-common: &app-common build: context: . dockerfile: Dockerfile target: runner restart: unless-stopped env_file: - .env depends_on: postgres: condition: service_healthy networks: - turf-net volumes: - ml-models:/app/data/models - app-logs:/app/logs services: # ---------------------------------------------------------- # PostgreSQL — primary database # ---------------------------------------------------------- postgres: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_DB: ${POSTGRES_DB:-turf_saas} POSTGRES_USER: ${POSTGRES_USER:-turf} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes: - postgres-data:/var/lib/postgresql/data - ./infra/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-turf} -d ${POSTGRES_DB:-turf_saas}"] interval: 10s timeout: 5s retries: 5 start_period: 30s networks: - turf-net ports: - "127.0.0.1:5432:5432" # ---------------------------------------------------------- # Redis — caching & session store # ---------------------------------------------------------- redis: image: redis:7-alpine restart: unless-stopped command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD} volumes: - redis-data:/data healthcheck: test: ["CMD", "redis-cli", "--pass", "${REDIS_PASSWORD}", "ping"] interval: 10s timeout: 5s retries: 3 networks: - turf-net ports: - "127.0.0.1:6379:6379" # ---------------------------------------------------------- # Combined API — main predictions + ideas API (port 8790) # ---------------------------------------------------------- combined-api: <<: *app-common container_name: turf-combined-api command: gunicorn --bind 0.0.0.0:8790 --workers 2 --timeout 120 --access-logfile - --error-logfile - combined_api:app ports: - "127.0.0.1:8790:8790" healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8790/health"] interval: 30s timeout: 10s retries: 3 start_period: 60s environment: PORT: 8790 SERVICE_NAME: combined-api # ---------------------------------------------------------- # Dashboard API — analytics & ML scoring (port 8791) # ---------------------------------------------------------- dashboard-api: <<: *app-common container_name: turf-dashboard-api command: gunicorn --bind 0.0.0.0:8791 --workers 2 --timeout 120 --access-logfile - --error-logfile - dashboard_api:app ports: - "127.0.0.1:8791:8791" healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8791/health"] interval: 30s timeout: 10s retries: 3 start_period: 60s environment: PORT: 8791 SERVICE_NAME: dashboard-api # ---------------------------------------------------------- # Portal Server — frontend portal (port 8792) # ---------------------------------------------------------- portal: <<: *app-common container_name: turf-portal command: gunicorn --bind 0.0.0.0:8792 --workers 2 --timeout 60 --access-logfile - --error-logfile - portal_server:app ports: - "127.0.0.1:8792:8792" healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8792/"] interval: 30s timeout: 10s retries: 3 start_period: 30s environment: PORT: 8792 SERVICE_NAME: portal # ---------------------------------------------------------- # Scheduler — background jobs (no external port) # ---------------------------------------------------------- scheduler: <<: *app-common container_name: turf-scheduler command: python turf_scheduler.py environment: SERVICE_NAME: scheduler # ---------------------------------------------------------- # Prometheus — metrics scraping # ---------------------------------------------------------- prometheus: image: prom/prometheus:v2.53.4 restart: unless-stopped command: - "--config.file=/etc/prometheus/prometheus.yml" - "--storage.tsdb.path=/prometheus" - "--storage.tsdb.retention.time=30d" - "--web.enable-lifecycle" volumes: - ./infra/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro - ./infra/prometheus/alerts.yml:/etc/prometheus/alerts.yml:ro - prometheus-data:/prometheus ports: - "127.0.0.1:9090:9090" networks: - turf-net healthcheck: test: ["CMD", "wget", "-q", "--tries=1", "--spider", "http://localhost:9090/-/healthy"] interval: 30s timeout: 10s retries: 3 # ---------------------------------------------------------- # Grafana — dashboards # ---------------------------------------------------------- grafana: image: grafana/grafana:11.5.2 restart: unless-stopped environment: GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin} GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD} GF_USERS_ALLOW_SIGN_UP: "false" GF_SERVER_DOMAIN: ${DOMAIN:-localhost} GF_SERVER_ROOT_URL: https://${DOMAIN:-localhost}/grafana/ GF_SERVER_SERVE_FROM_SUB_PATH: "true" volumes: - grafana-data:/var/lib/grafana - ./infra/grafana/provisioning:/etc/grafana/provisioning:ro - ./infra/grafana/dashboards:/var/lib/grafana/dashboards:ro ports: - "127.0.0.1:3000:3000" networks: - turf-net depends_on: - prometheus # ---------------------------------------------------------- # Nginx — reverse proxy + TLS termination # ---------------------------------------------------------- nginx: image: nginx:1.27-alpine restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./infra/nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./infra/nginx/conf.d:/etc/nginx/conf.d:ro - certbot-www:/var/www/certbot:ro - certbot-certs:/etc/letsencrypt:ro networks: - turf-net depends_on: - combined-api - dashboard-api - portal healthcheck: test: ["CMD", "nginx", "-t"] interval: 60s timeout: 10s retries: 3 # ---------------------------------------------------------- # Certbot — Let's Encrypt TLS certificate renewal # ---------------------------------------------------------- certbot: image: certbot/certbot:latest restart: "no" volumes: - certbot-www:/var/www/certbot - certbot-certs:/etc/letsencrypt command: certonly --webroot --webroot-path=/var/www/certbot --email ${ADMIN_EMAIL} --agree-tos --no-eff-email -d ${DOMAIN} networks: - turf-net # ============================================================ # Named volumes — persistent storage # ============================================================ volumes: postgres-data: driver: local redis-data: driver: local ml-models: driver: local app-logs: driver: local prometheus-data: driver: local grafana-data: driver: local certbot-www: driver: local certbot-certs: driver: local # ============================================================ # Network # ============================================================ networks: turf-net: driver: bridge