# Local development stack for the Galaxy UI. # # Brings up postgres + redis + mailpit + backend + gateway so the UI # Vite dev server (run on the host with `pnpm -C ui/frontend dev`) can # talk to a real authenticated stack without any cloud dependency. # # Browser: http://localhost:8080 (gateway public REST + Connect-Web) # Mailpit UI: http://localhost:8025 # Postgres: localhost:5433 (host-mapped) # Redis: localhost:6380 (host-mapped) # # Bring up: make -C tools/local-dev up # Tear down: make -C tools/local-dev down # Wipe state: make -C tools/local-dev clean # # The backend reads `BACKEND_AUTH_DEV_FIXED_CODE=123456` from the # `.env` file alongside this compose; ConfirmEmailCode accepts that # literal in addition to the real bcrypt-verified code, so a developer # can log in without touching Mailpit. Real codes still arrive in # Mailpit; both paths coexist. services: postgres: image: postgres:16-alpine container_name: galaxy-local-dev-postgres restart: unless-stopped environment: POSTGRES_USER: galaxy POSTGRES_PASSWORD: galaxy POSTGRES_DB: galaxy_backend ports: - "5433:5432" volumes: - postgres-data:/var/lib/postgresql/data networks: - galaxy-net healthcheck: test: ["CMD-SHELL", "pg_isready -U galaxy -d galaxy_backend"] interval: 3s timeout: 3s retries: 30 start_period: 5s redis: image: redis:7-alpine container_name: galaxy-local-dev-redis restart: unless-stopped command: - redis-server - --requirepass - galaxy-dev - --appendonly - "no" - --save - "" ports: - "6380:6379" networks: - galaxy-net healthcheck: test: ["CMD", "redis-cli", "-a", "galaxy-dev", "PING"] interval: 3s timeout: 3s retries: 30 start_period: 3s mailpit: image: axllent/mailpit:v1.21 container_name: galaxy-local-dev-mailpit restart: unless-stopped ports: - "8025:8025" networks: - galaxy-net healthcheck: test: ["CMD", "wget", "-q", "-O-", "http://localhost:8025/livez"] interval: 3s timeout: 3s retries: 30 start_period: 3s backend: build: context: ../.. dockerfile: tools/local-dev/backend.Dockerfile image: galaxy/backend:local-dev container_name: galaxy-local-dev-backend restart: unless-stopped user: "0:0" depends_on: postgres: condition: service_healthy mailpit: condition: service_healthy environment: BACKEND_LOGGING_LEVEL: debug BACKEND_HTTP_LISTEN_ADDR: ":8080" BACKEND_GRPC_PUSH_LISTEN_ADDR: ":8081" BACKEND_POSTGRES_DSN: "postgres://galaxy:galaxy@postgres:5432/galaxy_backend?search_path=backend&sslmode=disable" BACKEND_SMTP_HOST: mailpit BACKEND_SMTP_PORT: "1025" BACKEND_SMTP_FROM: "galaxy-backend@galaxy.local" BACKEND_SMTP_TLS_MODE: none BACKEND_DOCKER_NETWORK: galaxy-local-dev-net BACKEND_GAME_STATE_ROOT: /var/lib/galaxy/game-state BACKEND_GEOIP_DB_PATH: /var/lib/galaxy/geoip.mmdb BACKEND_NOTIFICATION_ADMIN_EMAIL: admin@galaxy.local BACKEND_AUTH_CHALLENGE_THROTTLE_MAX: "100" BACKEND_MAIL_WORKER_INTERVAL: 500ms BACKEND_NOTIFICATION_WORKER_INTERVAL: 500ms BACKEND_OTEL_TRACES_EXPORTER: none BACKEND_OTEL_METRICS_EXPORTER: none BACKEND_AUTH_DEV_FIXED_CODE: ${BACKEND_AUTH_DEV_FIXED_CODE:-} volumes: - /var/run/docker.sock:/var/run/docker.sock - game-state:/var/lib/galaxy/game-state - ../../pkg/geoip/test-data/test-data/GeoIP2-Country-Test.mmdb:/var/lib/galaxy/geoip.mmdb:ro networks: - galaxy-net healthcheck: test: ["CMD", "wget", "-q", "-O-", "http://localhost:8080/healthz"] interval: 3s timeout: 3s retries: 60 start_period: 10s gateway: build: context: ../.. dockerfile: tools/local-dev/gateway.Dockerfile image: galaxy/gateway:local-dev container_name: galaxy-local-dev-gateway restart: unless-stopped depends_on: backend: condition: service_healthy redis: condition: service_healthy environment: GATEWAY_LOG_LEVEL: debug GATEWAY_PUBLIC_HTTP_ADDR: ":8080" GATEWAY_AUTHENTICATED_GRPC_ADDR: ":9090" GATEWAY_BACKEND_HTTP_URL: "http://backend:8080" GATEWAY_BACKEND_GRPC_PUSH_URL: "backend:8081" GATEWAY_BACKEND_GATEWAY_CLIENT_ID: local-dev-gateway-1 GATEWAY_RESPONSE_SIGNER_PRIVATE_KEY_PEM_PATH: /run/secrets/gateway-response.pem GATEWAY_REDIS_MASTER_ADDR: "redis:6379" GATEWAY_REDIS_PASSWORD: galaxy-dev # Loosen anti-abuse so a developer hammering the form does not # rate-limit themselves between cycles. GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_PUBLIC_AUTH_RATE_LIMIT_REQUESTS: "10000" GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_PUBLIC_AUTH_RATE_LIMIT_BURST: "1000" GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_SEND_EMAIL_CODE_IDENTITY_RATE_LIMIT_REQUESTS: "10000" GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_SEND_EMAIL_CODE_IDENTITY_RATE_LIMIT_BURST: "1000" GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_CONFIRM_EMAIL_CODE_IDENTITY_RATE_LIMIT_REQUESTS: "10000" GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_CONFIRM_EMAIL_CODE_IDENTITY_RATE_LIMIT_BURST: "1000" GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_IP_RATE_LIMIT_REQUESTS: "10000" GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_IP_RATE_LIMIT_BURST: "1000" GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_SESSION_RATE_LIMIT_REQUESTS: "10000" GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_SESSION_RATE_LIMIT_BURST: "1000" GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_USER_RATE_LIMIT_REQUESTS: "10000" GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_USER_RATE_LIMIT_BURST: "1000" GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_MESSAGE_CLASS_RATE_LIMIT_REQUESTS: "10000" GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_MESSAGE_CLASS_RATE_LIMIT_BURST: "1000" ports: - "8080:8080" volumes: - ./keys/gateway-response.pem:/run/secrets/gateway-response.pem:ro networks: - galaxy-net healthcheck: test: ["CMD", "wget", "-q", "-O-", "http://localhost:8080/healthz"] interval: 3s timeout: 3s retries: 30 start_period: 5s networks: galaxy-net: name: galaxy-local-dev-net volumes: postgres-data: name: galaxy-local-dev-postgres-data game-state: name: galaxy-local-dev-game-state