The droplet copy of this file had drifted from the repo across multiple sessions. This commit brings the repo in line with what is actually running in production. Changes landing from the droplet: - Add restart: unless-stopped to app, postgres, and redis services (the deploy.restart_policy block was swarm-only and silently ignored by compose v2; top-level restart: is the correct form) - Drop the dead deploy.replicas: 1 and deploy.restart_policy: subfields from the app service (swarm-only noise) - Remove the traefik: service block entirely; Traefik now runs as its own stack at /opt/traefik from the adlee-traefik repo - Remove the letsencrypt: volume declaration (owned by adlee-traefik now via the external golfgame_letsencrypt volume) - Change web network from "driver: bridge" to "name: traefik_web, external: true" so this stack attaches to the shared ingress network created by the traefik stack This closes INC-0001/AI-10 and unblocks future deploys of this repo. The other uncommitted client/*, tui_client/* changes in the working tree are unrelated and intentionally left alone. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
137 lines
4.2 KiB
YAML
137 lines
4.2 KiB
YAML
# Production Docker Compose for Golf Card Game
|
|
#
|
|
# Usage:
|
|
# # Set required environment variables first
|
|
# export DB_PASSWORD=your-secure-password
|
|
# export SECRET_KEY=your-secret-key
|
|
# export ACME_EMAIL=your-email@example.com
|
|
#
|
|
# # Start services
|
|
# docker-compose -f docker-compose.prod.yml up -d
|
|
#
|
|
# # View logs
|
|
# docker-compose -f docker-compose.prod.yml logs -f app
|
|
#
|
|
# # Scale app instances
|
|
# docker-compose -f docker-compose.prod.yml up -d --scale app=2
|
|
|
|
services:
|
|
app:
|
|
restart: unless-stopped
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
environment:
|
|
- POSTGRES_URL=postgresql://golf:${DB_PASSWORD}@postgres:5432/golf
|
|
- DATABASE_URL=postgresql://golf:${DB_PASSWORD}@postgres:5432/golf
|
|
- REDIS_URL=redis://redis:6379
|
|
- SECRET_KEY=${SECRET_KEY}
|
|
- RESEND_API_KEY=${RESEND_API_KEY:-}
|
|
- EMAIL_FROM=${EMAIL_FROM:-Golf Cards <noreply@contact.golfcards.club>}
|
|
- SENTRY_DSN=${SENTRY_DSN:-}
|
|
- ENVIRONMENT=${ENVIRONMENT:-production}
|
|
- LOG_LEVEL=${LOG_LEVEL:-WARNING}
|
|
- LOG_LEVEL_GAME=${LOG_LEVEL_GAME:-}
|
|
- LOG_LEVEL_AI=${LOG_LEVEL_AI:-}
|
|
- LOG_LEVEL_HANDLERS=${LOG_LEVEL_HANDLERS:-}
|
|
- LOG_LEVEL_ROOM=${LOG_LEVEL_ROOM:-}
|
|
- LOG_LEVEL_AUTH=${LOG_LEVEL_AUTH:-}
|
|
- LOG_LEVEL_STORES=${LOG_LEVEL_STORES:-}
|
|
- BASE_URL=${BASE_URL:-https://golf.example.com}
|
|
- RATE_LIMIT_ENABLED=true
|
|
- INVITE_ONLY=true
|
|
- DAILY_OPEN_SIGNUPS=${DAILY_OPEN_SIGNUPS:-0}
|
|
- DAILY_SIGNUPS_PER_IP=${DAILY_SIGNUPS_PER_IP:-3}
|
|
- BOOTSTRAP_ADMIN_USERNAME=${BOOTSTRAP_ADMIN_USERNAME:-}
|
|
- BOOTSTRAP_ADMIN_PASSWORD=${BOOTSTRAP_ADMIN_PASSWORD:-}
|
|
- MATCHMAKING_ENABLED=true
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
redis:
|
|
condition: service_healthy
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 256M
|
|
reservations:
|
|
memory: 64M
|
|
networks:
|
|
- internal
|
|
- web
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.docker.network=traefik_web"
|
|
- "traefik.http.routers.golf.rule=Host(`${DOMAIN:-golf.example.com}`)"
|
|
- "traefik.http.routers.golf.entrypoints=websecure"
|
|
- "traefik.http.routers.golf.tls=true"
|
|
- "traefik.http.routers.golf.tls.certresolver=letsencrypt"
|
|
# www -> bare domain redirect
|
|
- "traefik.http.routers.golf-www.rule=Host(`www.${DOMAIN:-golf.example.com}`)"
|
|
- "traefik.http.routers.golf-www.entrypoints=websecure"
|
|
- "traefik.http.routers.golf-www.tls=true"
|
|
- "traefik.http.routers.golf-www.tls.certresolver=letsencrypt"
|
|
- "traefik.http.routers.golf-www.middlewares=www-redirect"
|
|
- "traefik.http.middlewares.www-redirect.redirectregex.regex=^https://www\\.(.+)"
|
|
- "traefik.http.middlewares.www-redirect.redirectregex.replacement=https://$${1}"
|
|
- "traefik.http.middlewares.www-redirect.redirectregex.permanent=true"
|
|
- "traefik.http.services.golf.loadbalancer.server.port=8000"
|
|
# WebSocket sticky sessions
|
|
- "traefik.http.services.golf.loadbalancer.sticky.cookie=true"
|
|
- "traefik.http.services.golf.loadbalancer.sticky.cookie.name=golf_server"
|
|
|
|
postgres:
|
|
restart: unless-stopped
|
|
image: postgres:16-alpine
|
|
environment:
|
|
POSTGRES_DB: golf
|
|
POSTGRES_USER: golf
|
|
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U golf -d golf"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
networks:
|
|
- internal
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 192M
|
|
reservations:
|
|
memory: 64M
|
|
|
|
redis:
|
|
restart: unless-stopped
|
|
image: redis:7-alpine
|
|
command: redis-server --appendonly yes --maxmemory 32mb --maxmemory-policy allkeys-lru
|
|
volumes:
|
|
- redis_data:/data
|
|
healthcheck:
|
|
test: ["CMD", "redis-cli", "ping"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
networks:
|
|
- internal
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 64M
|
|
reservations:
|
|
memory: 16M
|
|
|
|
|
|
volumes:
|
|
postgres_data:
|
|
redis_data:
|
|
|
|
networks:
|
|
internal:
|
|
driver: bridge
|
|
web:
|
|
name: traefik_web
|
|
external: true
|