daed2690c1
The previous commit stamped `galaxy.stack=<value>` on services, volumes, and networks. Putting it on volumes/networks changes their compose config-hash on every label revision, so `docker compose up` tries to recreate them — which on the long-lived dev environment either destroys the postgres data volume or deadlocks while trying to remove `galaxy-dev-internal` with containers still bound to it. Observed live: run #184 hung in compose recreate after the three stateful services were stopped, with no recovery. Containers alone are sufficient for the cleanup contract (we filter containers, not volumes or networks). Roll back the label on volumes and networks in both compose files and capture the rule in docs/ARCHITECTURE.md so the next contributor does not reintroduce it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
228 lines
8.5 KiB
YAML
228 lines
8.5 KiB
YAML
# 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.
|
|
|
|
name: galaxy-local-dev
|
|
|
|
services:
|
|
postgres:
|
|
image: postgres:16-alpine
|
|
container_name: galaxy-local-dev-postgres
|
|
restart: unless-stopped
|
|
labels:
|
|
galaxy.stack: local-dev
|
|
environment:
|
|
POSTGRES_USER: galaxy
|
|
POSTGRES_PASSWORD: galaxy
|
|
POSTGRES_DB: galaxy_backend
|
|
ports:
|
|
- "${LOCAL_DEV_POSTGRES_PORT:-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
|
|
labels:
|
|
galaxy.stack: local-dev
|
|
command:
|
|
- redis-server
|
|
- --requirepass
|
|
- galaxy-dev
|
|
- --appendonly
|
|
- "no"
|
|
- --save
|
|
- ""
|
|
ports:
|
|
- "${LOCAL_DEV_REDIS_PORT:-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
|
|
labels:
|
|
galaxy.stack: local-dev
|
|
ports:
|
|
- "${LOCAL_DEV_MAILPIT_PORT:-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
|
|
labels:
|
|
galaxy.stack: local-dev
|
|
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_STACK_LABEL: local-dev
|
|
BACKEND_GAME_STATE_ROOT: /tmp/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:-}
|
|
BACKEND_DEV_SANDBOX_EMAIL: ${BACKEND_DEV_SANDBOX_EMAIL:-}
|
|
BACKEND_DEV_SANDBOX_ENGINE_IMAGE: ${BACKEND_DEV_SANDBOX_ENGINE_IMAGE:-}
|
|
BACKEND_DEV_SANDBOX_ENGINE_VERSION: ${BACKEND_DEV_SANDBOX_ENGINE_VERSION:-}
|
|
BACKEND_DEV_SANDBOX_PLAYER_COUNT: ${BACKEND_DEV_SANDBOX_PLAYER_COUNT:-}
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock
|
|
# Per-game state directories live under the same absolute path
|
|
# both inside the backend container and on the Docker daemon
|
|
# host (colima VM), so the bind-mount source the backend hands
|
|
# to the daemon resolves correctly when spawning engine
|
|
# containers. See backend/internal/runtime/service.go:454.
|
|
- type: bind
|
|
source: /tmp/galaxy-game-state
|
|
target: /tmp/galaxy-game-state
|
|
bind:
|
|
create_host_path: true
|
|
- ../../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
|
|
labels:
|
|
galaxy.stack: local-dev
|
|
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"
|
|
# public_misc class wraps the authenticated EdgeGateway gRPC
|
|
# endpoints (ExecuteCommand, SubscribeEvents). The gateway's
|
|
# default for this class is 0 bytes, which rejects every
|
|
# non-empty body with HTTP 413; override with a generous limit
|
|
# so browser-side commands carrying signed envelopes go through.
|
|
GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_PUBLIC_MISC_MAX_BODY_BYTES: "131072"
|
|
GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_PUBLIC_MISC_RATE_LIMIT_REQUESTS: "10000"
|
|
GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_PUBLIC_MISC_RATE_LIMIT_BURST: "1000"
|
|
GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_BROWSER_BOOTSTRAP_MAX_BODY_BYTES: "65536"
|
|
GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_BROWSER_ASSET_MAX_BODY_BYTES: "65536"
|
|
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:
|
|
- "${LOCAL_DEV_GATEWAY_REST_PORT:-8080}:8080"
|
|
# Authenticated EdgeGateway connect-web/gRPC listener. The
|
|
# browser reaches it via the Vite dev proxy in
|
|
# ui/frontend/vite.config.ts.
|
|
- "${LOCAL_DEV_GATEWAY_GRPC_PORT:-9090}:9090"
|
|
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
|
|
|
|
# See note in tools/dev-deploy/docker-compose.yml — labels live only
|
|
# on services (containers), not on volumes or networks, to keep the
|
|
# compose config-hash for stateful resources stable across deploys.
|
|
volumes:
|
|
postgres-data:
|
|
name: galaxy-local-dev-postgres-data
|