name: eve-space services: postgres: image: postgres:17-alpine environment: POSTGRES_DB: ${POSTGRES_DB:-eve_space} POSTGRES_USER: ${POSTGRES_USER:-eve_space} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in .env} ports: - '${POSTGRES_PORT:-5432}:5432' volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ['CMD-SHELL', 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}'] interval: 10m timeout: 3s retries: 3 start_period: 30s start_interval: 2s queue-redis: image: redis:7.4.7-alpine command: [ 'redis-server', '--appendonly', 'yes', '--appendfsync', 'always', '--maxmemory', '${QUEUE_REDIS_MAXMEMORY:-512mb}', '--maxmemory-policy', 'noeviction', ] volumes: - queue_redis_data:/data ports: - '${QUEUE_REDIS_PORT:-6379}:6379' healthcheck: test: [ 'CMD-SHELL', 'redis-cli ping | grep -qx PONG && redis-cli --raw config get maxmemory-policy | grep -qx noeviction && maxmemory=$$(redis-cli --raw config get maxmemory | grep -E ''^[0-9]+$$'') && used=$$(redis-cli --raw info memory | grep ''^used_memory:'' | cut -d: -f2) && test "$$maxmemory" -gt 0 && test "$$used" -lt "$$((maxmemory * 90 / 100))"', ] interval: 10m timeout: 3s retries: 3 start_period: 30s start_interval: 2s cache-redis: image: redis:7.4.7-alpine command: [ 'redis-server', '--appendonly', 'no', '--save', '', '--maxmemory', '${CACHE_REDIS_MAXMEMORY:-256mb}', '--maxmemory-policy', 'allkeys-lfu', ] ports: - '${CACHE_REDIS_PORT:-6380}:6379' healthcheck: test: [ 'CMD-SHELL', 'redis-cli ping | grep -qx PONG && redis-cli --raw config get maxmemory-policy | grep -qx allkeys-lfu && maxmemory=$$(redis-cli --raw config get maxmemory | grep -E ''^[0-9]+$$'') && used=$$(redis-cli --raw info memory | grep ''^used_memory:'' | cut -d: -f2) && test "$$maxmemory" -gt 0 && test "$$used" -lt "$$((maxmemory * 90 / 100))"', ] interval: 10m timeout: 3s retries: 3 start_period: 30s start_interval: 2s api: build: context: . dockerfile: api/Dockerfile stop_grace_period: 40s # Pool, refresh, cache, and heartbeat tuning is documented in `.env.example` and read by # `env.ts` in both processes. The explicit `environment` map below still wins, so topology # stays container-relative. env_file: - path: .env required: false environment: NODE_ENV: production PORT: 8787 DATABASE_URL: postgres://${POSTGRES_USER:-eve_space}@postgres:5432/${POSTGRES_DB:-eve_space} PGPASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in .env} WEB_ORIGIN: ${WEB_ORIGIN:-http://localhost:3000} EVE_CLIENT_ID: ${EVE_CLIENT_ID:-} EVE_CLIENT_SECRET: ${EVE_CLIENT_SECRET:-} EVE_CALLBACK_URL: ${EVE_CALLBACK_URL:-http://localhost:8788/auth/eve/callback} EVE_SCOPES: ${EVE_SCOPES:-esi-assets.read_assets.v1 esi-wallet.read_character_wallet.v1 esi-markets.read_character_orders.v1 esi-markets.structure_markets.v1 esi-contracts.read_character_contracts.v1 esi-location.read_location.v1 esi-location.read_ship_type.v1 esi-skills.read_skills.v1 esi-skills.read_skillqueue.v1 esi-clones.read_clones.v1 esi-clones.read_implants.v1 esi-mail.read_mail.v1 esi-mail.organize_mail.v1 esi-mail.send_mail.v1 esi-search.search_structures.v1 esi-characters.read_contacts.v1 esi-characters.read_corporation_roles.v1 esi-corporations.read_corporation_membership.v1 esi-characters.read_freelance_jobs.v1 esi-corporations.read_freelance_jobs.v1 esi-corporations.read_projects.v1 esi.activity.char:read} ESI_USER_AGENT: ${ESI_USER_AGENT:-EveSpace/0.1 (+https://github.com/mskry/eve-space) @evespace/esi-client/3.1.0} TOKEN_ENCRYPTION_KEY: ${TOKEN_ENCRYPTION_KEY:-} ADMIN_SETUP_SECRET: ${ADMIN_SETUP_SECRET:-} QUEUE_REDIS_URL: redis://queue-redis:6379 CACHE_REDIS_URL: redis://cache-redis:6379 ports: - '8788:8787' depends_on: postgres: condition: service_healthy queue-redis: condition: service_healthy cache-redis: condition: service_started healthcheck: test: [ 'CMD', 'node', '-e', "fetch('http://localhost:8787/health').then(r => { if (!r.ok) process.exit(1) })", ] interval: 10m timeout: 3s retries: 3 start_period: 30s start_interval: 2s worker: build: context: . dockerfile: api/Dockerfile init: true entrypoint: ['node', 'dist/worker.js'] stop_grace_period: 40s env_file: - path: .env required: false environment: NODE_ENV: production DATABASE_URL: postgres://${POSTGRES_USER:-eve_space}@postgres:5432/${POSTGRES_DB:-eve_space} PGPASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in .env} QUEUE_REDIS_URL: redis://queue-redis:6379 CACHE_REDIS_URL: redis://cache-redis:6379 EVE_SCOPES: ${EVE_SCOPES:-esi-assets.read_assets.v1 esi-wallet.read_character_wallet.v1 esi-markets.read_character_orders.v1 esi-markets.structure_markets.v1 esi-contracts.read_character_contracts.v1 esi-location.read_location.v1 esi-location.read_ship_type.v1 esi-skills.read_skills.v1 esi-skills.read_skillqueue.v1 esi-clones.read_clones.v1 esi-clones.read_implants.v1 esi-mail.read_mail.v1 esi-mail.organize_mail.v1 esi-mail.send_mail.v1 esi-search.search_structures.v1 esi-characters.read_contacts.v1 esi-characters.read_corporation_roles.v1 esi-corporations.read_corporation_membership.v1 esi-characters.read_freelance_jobs.v1 esi-corporations.read_freelance_jobs.v1 esi-corporations.read_projects.v1 esi.activity.char:read} QUEUE_PLANNER_SCHEDULE: ${QUEUE_PLANNER_SCHEDULE:-*/15 * * * *} QUEUE_PLANNER_SCHEDULE_OFFSET_MS: ${QUEUE_PLANNER_SCHEDULE_OFFSET_MS:-} QUEUE_PLANNER_INITIAL_DELAY_MAX_MS: ${QUEUE_PLANNER_INITIAL_DELAY_MAX_MS:-60000} depends_on: api: condition: service_healthy queue-redis: condition: service_healthy cache-redis: condition: service_started healthcheck: test: ['CMD', 'node', 'dist/worker/health.js'] interval: 10m timeout: 3s retries: 3 start_period: 30s start_interval: 2s volumes: postgres_data: queue_redis_data: