services: postgres: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_DB: tasktopia POSTGRES_USER: tasktopia POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-change-me} volumes: - tasktopia_postgres:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U tasktopia -d tasktopia"] interval: 5s timeout: 5s retries: 12 security_opt: - no-new-privileges:true pids_limit: 100 mem_limit: ${POSTGRES_MEMORY_LIMIT:-1g} cpus: ${POSTGRES_CPU_LIMIT:-1.00} cpu_shares: 512 redis: image: redis:8-alpine profiles: ["cache"] restart: unless-stopped command: ["redis-server", "--save", "", "--appendonly", "no", "--maxmemory", "${REDIS_MEMORY_LIMIT:-256mb}", "--maxmemory-policy", "allkeys-lru"] healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 3s retries: 3 security_opt: - no-new-privileges:true cap_drop: - ALL pids_limit: 64 mem_limit: ${REDIS_CONTAINER_MEMORY_LIMIT:-320m} cpus: ${REDIS_CPU_LIMIT:-0.50} app: stop_grace_period: 35s image: ${APP_IMAGE:-${COMPOSE_PROJECT_NAME:-tasktopia}-app:latest} build: context: . args: STATIC_ORIGIN: ${STATIC_ORIGIN:-} restart: unless-stopped environment: NODE_ENV: production HOST: 0.0.0.0 PORT: 3000 RUNTIME_ROLE: web DATABASE_POOL_MAX: 10 GENERATION_WAIT_MS: ${GENERATION_WAIT_MS:-55000} REDIS_URL: ${REDIS_URL:-} REDIS_CHUNK_TTL_SECONDS: ${REDIS_CHUNK_TTL_SECONDS:-300} REDIS_OPERATION_TIMEOUT_MS: ${REDIS_OPERATION_TIMEOUT_MS:-40} DATABASE_URL: postgres://tasktopia:${POSTGRES_PASSWORD:-change-me}@postgres:5432/tasktopia APP_ORIGIN: ${APP_ORIGIN:-http://localhost:3000} STATIC_ORIGIN: ${STATIC_ORIGIN:-} SESSION_COOKIE_SECURE: ${SESSION_COOKIE_SECURE:-false} REGISTRATION_ENABLED: ${REGISTRATION_ENABLED:-false} VAPID_SUBJECT: ${VAPID_SUBJECT:-} VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY:-} VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY:-} TRUST_PROXY: "true" LOG_LEVEL: ${LOG_LEVEL:-info} OBSERVABILITY_TOKEN: ${OBSERVABILITY_TOKEN:-} UPLOAD_DIR: /data/uploads volumes: - tasktopia_uploads:/data/uploads - tasktopia_asset_revisions:/app/dist/public/game-assets/v5/revisions depends_on: postgres: condition: service_healthy ports: - "${APP_BIND_ADDRESS:-127.0.0.1}:${APP_PORT:-3000}:3000" healthcheck: test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/health').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"] interval: 30s timeout: 5s retries: 3 start_period: 10s security_opt: - no-new-privileges:true cap_drop: - ALL pids_limit: 160 mem_limit: ${APP_MEMORY_LIMIT:-1536m} cpus: ${APP_CPU_LIMIT:-2.00} cpu_shares: 1024 init: true logging: driver: json-file options: max-size: "10m" max-file: "5" mcp: stop_grace_period: 35s image: ${APP_IMAGE:-${COMPOSE_PROJECT_NAME:-tasktopia}-app:latest} restart: unless-stopped environment: NODE_ENV: production HOST: 0.0.0.0 PORT: 3000 RUNTIME_ROLE: mcp DATABASE_POOL_MAX: 4 GENERATION_WAIT_MS: ${GENERATION_WAIT_MS:-55000} REDIS_URL: ${REDIS_URL:-} REDIS_CHUNK_TTL_SECONDS: ${REDIS_CHUNK_TTL_SECONDS:-300} REDIS_OPERATION_TIMEOUT_MS: ${REDIS_OPERATION_TIMEOUT_MS:-40} DATABASE_URL: postgres://tasktopia:${POSTGRES_PASSWORD:-change-me}@postgres:5432/tasktopia APP_ORIGIN: ${APP_ORIGIN:-http://localhost:3000} SESSION_COOKIE_SECURE: ${SESSION_COOKIE_SECURE:-false} TRUST_PROXY: "true" LOG_LEVEL: ${LOG_LEVEL:-info} OBSERVABILITY_TOKEN: ${OBSERVABILITY_TOKEN:-} UPLOAD_DIR: /data/uploads volumes: - tasktopia_uploads:/data/uploads depends_on: postgres: condition: service_healthy ports: - "127.0.0.1:${MCP_PORT:-3002}:3000" healthcheck: test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/health').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"] interval: 30s timeout: 5s retries: 3 start_period: 10s security_opt: - no-new-privileges:true cap_drop: - ALL pids_limit: 96 mem_limit: ${MCP_MEMORY_LIMIT:-768m} cpus: ${MCP_CPU_LIMIT:-1.00} cpu_shares: 512 init: true logging: driver: json-file options: max-size: "10m" max-file: "5" world: stop_grace_period: 35s image: ${APP_IMAGE:-${COMPOSE_PROJECT_NAME:-tasktopia}-app:latest} restart: unless-stopped environment: NODE_ENV: production HOST: 0.0.0.0 PORT: 3000 RUNTIME_ROLE: world DATABASE_POOL_MAX: 4 REDIS_URL: ${REDIS_URL:-} REDIS_CHUNK_TTL_SECONDS: ${REDIS_CHUNK_TTL_SECONDS:-300} REDIS_OPERATION_TIMEOUT_MS: ${REDIS_OPERATION_TIMEOUT_MS:-40} DATABASE_URL: postgres://tasktopia:${POSTGRES_PASSWORD:-change-me}@postgres:5432/tasktopia APP_ORIGIN: ${APP_ORIGIN:-http://localhost:3000} SESSION_COOKIE_SECURE: ${SESSION_COOKIE_SECURE:-false} REGISTRATION_ENABLED: "false" TRUST_PROXY: "true" LOG_LEVEL: ${LOG_LEVEL:-info} OBSERVABILITY_TOKEN: ${OBSERVABILITY_TOKEN:-} UPLOAD_DIR: /data/uploads volumes: - tasktopia_uploads:/data/uploads depends_on: postgres: condition: service_healthy ports: - "127.0.0.1:${WORLD_PORT:-3003}:3000" healthcheck: test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/health').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"] interval: 30s timeout: 5s retries: 3 start_period: 10s security_opt: - no-new-privileges:true cap_drop: - ALL pids_limit: 160 mem_limit: ${WORLD_MEMORY_LIMIT:-1536m} cpus: ${WORLD_CPU_LIMIT:-2.00} cpu_shares: 768 init: true logging: driver: json-file options: max-size: "10m" max-file: "5" volumes: tasktopia_postgres: tasktopia_uploads: tasktopia_asset_revisions: