vite.config.ts now proxies `/api` and `/galaxy.gateway.v1.EdgeGateway` to the gateway, so the browser sees only `localhost:5173` and never trips a cross-origin preflight. `.env.development` accordingly points `VITE_GATEWAY_BASE_URL` at the Vite origin. The proxy target is overridable via `VITE_DEV_PROXY_TARGET=...` for non-default gateways without touching the compose file. `gateway/Dockerfile` previously failed to build because gateway imports `galaxy/core` (replaced to `../ui/core` in `gateway/go.mod`) but the Dockerfile did not copy `ui/core/` into the build context nor declare the replace in the synthesised `go.work`. Adding both makes `docker build -f gateway/Dockerfile .` succeed; this is the same fix already shipped in `tools/local-dev/gateway.Dockerfile`, back-ported to upstream. Verified: - docker build -f gateway/Dockerfile . — builds cleanly - pnpm test 14/14, pnpm exec playwright test 44/44 (with CI=1 to force a fresh dev server; reuse keeps the previous startup env) - curl POST through localhost:5173/api/* and /galaxy.gateway.v1.* — reach the gateway, no CORS preflight on the browser side tools/local-dev/README.md updated with the new network map and the `VITE_DEV_PROXY_TARGET` override. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
8.0 KiB
tools/local-dev/ — Galaxy local development stack
A docker-compose stack that brings up postgres + redis + mailpit + backend + gateway so the UI Vite dev server (run on the host) can talk to a real authenticated stack without any cloud dependency.
The stack is the recommended baseline for UI work that goes beyond the mocked Playwright fixtures: every payload exercises the real FlatBuffers wire, every authenticated call verifies the response signature against the dev keypair, and every email passes through Mailpit's web UI for inspection.
This stack is not a CI gate (that role belongs to
tools/local-ci/, which boots a Gitea +
Actions runner and replays workflow files). The two stacks are
independent and can coexist on the same machine; they bind different
ports and use different networks.
Bring it up
make -C tools/local-dev up
up builds the local-dev backend and gateway images on first run
(pulls postgres, redis, mailpit), waits for every service to report
healthy, and returns. Subsequent invocations reuse the built images.
After the stack is healthy:
pnpm -C ui/frontend dev
Open http://localhost:5173 for the UI and http://localhost:8025 for Mailpit.
Daily flow
make -C tools/local-dev up # bring up (idempotent, fast on warm cache)
pnpm -C ui/frontend dev # in another terminal
# ...edit UI, browse, repeat...
make -C tools/local-dev down # stop containers, keep state
State persists in named Docker volumes between up/down cycles, so
games created on Tuesday survive into Wednesday. Wipe with
make clean when you want a fresh database.
Logging in
Two paths coexist by default:
- Fixed dev code (fast).
tools/local-dev/.envshipsBACKEND_AUTH_DEV_FIXED_CODE=123456. After requesting a code in the UI, type123456—ConfirmEmailCodeaccepts that literal in addition to the real bcrypt-verified code stored on the challenge row. The override emits a loud warning at backend boot and is rejected by the production env loader (BACKEND_ENVguard inbackend/internal/config). - Real Mailpit code. Open http://localhost:8025, find the most recent message, copy the six-digit code, paste it into the UI. This exercises the full mail outbox path, including SMTP handoff and gomail TLS-mode handling.
To force the second path (no fast-bypass), edit
tools/local-dev/.env and clear BACKEND_AUTH_DEV_FIXED_CODE, then
make rebuild (or simply docker compose up -d backend to recreate
the backend with the new env).
Network map
host compose network "galaxy-local-dev-net"
┌────────────────────────────────┐ ┌──────────────────────────────┐
│ browser localhost:5173 │── pnpm dev (Vite, host) ──┐ │
│ ↳ /api/* proxied ───┼──────────────────────────▶│ gateway:8080 │
│ ↳ /galaxy.gateway... ┼──────────────────────────▶│ │
│ browser localhost:8025 │─────────────────────────▶│ mailpit:8025 │
│ psql localhost:5433 │─────────────────────────▶│ postgres:5432 │
│ redis-cli localhost:6380 │─────────────────────────▶│ redis:6379 │
└────────────────────────────────┘ │ ↳ backend:8080 (HTTP) │
│ ↳ backend:8081 (gRPC push) │
│ ↳ mailpit:1025 (SMTP in) │
└────────────────────────────────┘
Vite's dev server proxies /api and /galaxy.gateway.v1.EdgeGateway
to the gateway, so every browser request stays same-origin (no CORS
preflight). The gateway is therefore reachable only through Vite at
http://localhost:5173, not at http://localhost:8080 from the
browser tab. Direct curl/wget against http://localhost:8080 still
works for diagnostic probes — only the browser-side requests are
proxied.
Mailpit (8025), postgres (5433), and redis (6380) remain directly
reachable for diagnostics (make psql, redis-cli -h localhost -p 6380 -a galaxy-dev).
To point the proxy at a non-local gateway, run
VITE_DEV_PROXY_TARGET=http://gateway.host:8080 pnpm -C ui/frontend dev
— no compose changes needed.
Make targets
make up Bring up the stack (build if needed) and wait for health
make rebuild Rebuild the backend / gateway images (ignores cache)
make down Stop containers, keep volumes
make clean Stop and wipe volumes (postgres + game-state)
make logs Tail every service's logs
make logs-backend Tail backend only
make logs-gateway Tail gateway only
make logs-mail Tail mailpit only
make psql Open a psql shell as galaxy@galaxy_backend
make status docker compose ps
Files
docker-compose.yml— five services: postgres, redis, mailpit, backend, gateway, plus shared network and volumes.backend.Dockerfile,gateway.Dockerfile— local-dev runtime images built on alpine (sowgetis available for the compose healthchecks). The build stage mirrorsbackend/Dockerfileandgateway/Dockerfileexactly.Makefile— wrapper overdocker composethat keeps the muscle memory close totools/local-ci/'s Makefile..env— committed defaults for the compose${VAR:-}expansions. Edit per-developer or override via your shell.keys/gateway-response.pem,keys/gateway-response.pub— dev-only Ed25519 keypair used by the gateway for response signing. Pairs with theVITE_GATEWAY_RESPONSE_PUBLIC_KEYvalue inui/frontend/.env.development. Seekeys/README.mdbefore rotating.keys/regenerate.go— one-shot Go helper that regenerates the pair and prints the new base64 public key.
Troubleshooting
make upreports a build error mentioningpkg/cronutil— upstream module list drifted; copy any newpkg/<name>/line into the local-devbackend.Dockerfile/gateway.Dockerfileto matchbackend/Dockerfile/gateway/Dockerfile.- Gateway exits at boot with "redis: …" — the redis container is
still bootstrapping.
make up --waitwaits for healthchecks; if it times out, increasestart_periodin the gateway service or inspectmake logs-redis. - Login form rejects every code — confirm
BACKEND_AUTH_DEV_FIXED_CODEis set intools/local-dev/.envand the backend has been recreated since the last edit (docker compose up -d backend). Real Mailpit codes work regardless. - UI talks to old gateway: Vite caches
import.meta.envat boot. Restartpnpm devafter editingui/frontend/.env.development.local. - Port 8080 already in use — stop the conflicting service or
edit the host-side mapping in
docker-compose.yml(gateway'sports:entry) plus the matchingVITE_GATEWAY_BASE_URLinui/frontend/.env.development.local.
Relationship to other infrastructure
tools/local-ci/— Gitea + Actions runner, replays.gitea/workflows/*against a pushed branch. Different stack, different purpose; coexists with local-dev on the same machine.integration/testenv/— testcontainers harness used bymake -C integration integration. Uses the same images (backend/Dockerfile,gateway/Dockerfile) at production defaults; do not confuse with this local-dev stack, which carries alpine-runtime images for ergonomics and the dev-mode auth override.