managarten/docker-compose.yml
Wuesteon 4d15d9e764 🔒 security(auth): migrate to EdDSA JWT and add automated monitoring
BREAKING: JWT keys are now auto-managed by Better Auth (EdDSA/Ed25519)
- Remove all JWT_PRIVATE_KEY, JWT_PUBLIC_KEY, JWT_SECRET references
- Keys stored in auth.jwks database table (auto-generated on first run)
- Delete obsolete generate-keys.sh and generate-staging-secrets.sh scripts
- Clean up legacy AUTH_*.md analysis files from root

Security Improvements:
- Add security_events table for audit logging
- Add SecurityEventsService for tracking auth events
- Enhanced security headers (HSTS, CSP, X-Frame-Options)
- Rate limiting configuration

Monitoring Setup:
- Add auth-health-check.sh for automated testing
- Add generate-dashboard.sh for HTML status dashboard
- Tests: health endpoint, JWKS (EdDSA), security headers, response time
- Ready for Hetzner cron deployment

Documentation:
- Update deployment docs with Better Auth notes
- Update environment variable references
- Add security improvements documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 21:42:47 +01:00

190 lines
5.7 KiB
YAML

version: '3.8'
services:
# Traefik reverse proxy
traefik:
image: traefik:v3.0
container_name: manacore-traefik
restart: unless-stopped
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
# Rate limiting
- "--api.insecure=false"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik-letsencrypt:/letsencrypt
networks:
- manacore-network
# PostgreSQL database
postgres:
image: postgres:16-alpine
container_name: manacore-postgres
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB:-manacore}
POSTGRES_USER: ${POSTGRES_USER:-manacore}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --auth=scram-sha-256"
volumes:
- postgres-data:/var/lib/postgresql/data
- ./services/mana-core-auth/postgres/init:/docker-entrypoint-initdb.d:ro
ports:
- "5432:5432"
networks:
- manacore-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-manacore}"]
interval: 10s
timeout: 5s
retries: 5
command:
- "postgres"
- "-c"
- "max_connections=200"
- "-c"
- "shared_buffers=256MB"
- "-c"
- "effective_cache_size=1GB"
- "-c"
- "password_encryption=scram-sha-256"
# PgBouncer connection pooler
pgbouncer:
image: pgbouncer/pgbouncer:latest
container_name: manacore-pgbouncer
restart: unless-stopped
environment:
DATABASES_HOST: postgres
DATABASES_PORT: 5432
DATABASES_USER: ${POSTGRES_USER:-manacore}
DATABASES_PASSWORD: ${POSTGRES_PASSWORD}
DATABASES_DBNAME: ${POSTGRES_DB:-manacore}
PGBOUNCER_POOL_MODE: transaction
PGBOUNCER_MAX_CLIENT_CONN: 1000
PGBOUNCER_DEFAULT_POOL_SIZE: 25
depends_on:
postgres:
condition: service_healthy
networks:
- manacore-network
# Redis cache
redis:
image: redis:7-alpine
container_name: manacore-redis
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD} --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis-data:/data
ports:
- "6379:6379"
networks:
- manacore-network
healthcheck:
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
interval: 10s
timeout: 5s
retries: 5
# Mana Core Auth Service
mana-core-auth:
build:
context: .
dockerfile: ./services/mana-core-auth/Dockerfile
container_name: manacore-auth
restart: unless-stopped
environment:
NODE_ENV: production
PORT: 3001
DATABASE_URL: postgresql://${POSTGRES_USER:-manacore}:${POSTGRES_PASSWORD}@pgbouncer:6432/${POSTGRES_DB:-manacore}
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD}
JWT_PUBLIC_KEY: ${JWT_PUBLIC_KEY}
JWT_PRIVATE_KEY: ${JWT_PRIVATE_KEY}
JWT_ACCESS_TOKEN_EXPIRY: ${JWT_ACCESS_TOKEN_EXPIRY:-15m}
JWT_REFRESH_TOKEN_EXPIRY: ${JWT_REFRESH_TOKEN_EXPIRY:-7d}
JWT_ISSUER: ${JWT_ISSUER:-manacore}
JWT_AUDIENCE: ${JWT_AUDIENCE:-manacore}
STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY}
STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET}
STRIPE_PUBLISHABLE_KEY: ${STRIPE_PUBLISHABLE_KEY}
CORS_ORIGINS: ${CORS_ORIGINS}
CREDITS_SIGNUP_BONUS: ${CREDITS_SIGNUP_BONUS:-150}
CREDITS_DAILY_FREE: ${CREDITS_DAILY_FREE:-5}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- manacore-network
labels:
- "traefik.enable=true"
- "traefik.http.routers.manacore-auth.rule=Host(`${AUTH_DOMAIN}`)"
- "traefik.http.routers.manacore-auth.entrypoints=websecure"
- "traefik.http.routers.manacore-auth.tls.certresolver=letsencrypt"
- "traefik.http.services.manacore-auth.loadbalancer.server.port=3001"
# Rate limiting
- "traefik.http.middlewares.auth-ratelimit.ratelimit.average=100"
- "traefik.http.middlewares.auth-ratelimit.ratelimit.burst=50"
- "traefik.http.routers.manacore-auth.middlewares=auth-ratelimit"
# Prometheus (metrics)
prometheus:
image: prom/prometheus:latest
container_name: manacore-prometheus
restart: unless-stopped
volumes:
- ./docker/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/usr/share/prometheus/console_libraries'
- '--web.console.templates=/usr/share/prometheus/consoles'
ports:
- "9090:9090"
networks:
- manacore-network
# Grafana (dashboards)
grafana:
image: grafana/grafana:latest
container_name: manacore-grafana
restart: unless-stopped
environment:
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD}
GF_USERS_ALLOW_SIGN_UP: false
volumes:
- grafana-data:/var/lib/grafana
- ./docker/grafana/provisioning:/etc/grafana/provisioning:ro
ports:
- "3000:3000"
depends_on:
- prometheus
networks:
- manacore-network
networks:
manacore-network:
driver: bridge
volumes:
postgres-data:
redis-data:
traefik-letsencrypt:
prometheus-data:
grafana-data: