Invite request feature: - Public form to request an invite when INVITE_REQUEST_ENABLED=true - Stores requests in new invite_requests DB table - Emails admins on new request, emails requester on approve/deny - Admin panel tab to review, approve, and deny requests - Approval auto-creates invite code and sends signup link CI/CD pipeline: - Build & push Docker image to Gitea registry on release - Auto-deploy to staging with health check - Manual workflow_dispatch for production deploys Also includes client layout/sizing improvements for card grid and opponent spacing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
138 lines
4.2 KiB
YAML
138 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
|
|
- INVITE_REQUEST_ENABLED=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
|