Files
galaxy-game/tools/local-dev
Ilia Denisov 6f6a854337 local-dev: Vite proxy for same-origin requests + upstream gateway Dockerfile fix
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>
2026-05-08 11:04:00 +02:00
..

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:

  1. Fixed dev code (fast). tools/local-dev/.env ships BACKEND_AUTH_DEV_FIXED_CODE=123456. After requesting a code in the UI, type 123456ConfirmEmailCode accepts 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_ENV guard in backend/internal/config).
  2. 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 (so wget is available for the compose healthchecks). The build stage mirrors backend/Dockerfile and gateway/Dockerfile exactly.
  • Makefile — wrapper over docker compose that keeps the muscle memory close to tools/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 the VITE_GATEWAY_RESPONSE_PUBLIC_KEY value in ui/frontend/.env.development. See keys/README.md before rotating.
  • keys/regenerate.go — one-shot Go helper that regenerates the pair and prints the new base64 public key.

Troubleshooting

  • make up reports a build error mentioning pkg/cronutil — upstream module list drifted; copy any new pkg/<name>/ line into the local-dev backend.Dockerfile / gateway.Dockerfile to match backend/Dockerfile / gateway/Dockerfile.
  • Gateway exits at boot with "redis: …" — the redis container is still bootstrapping. make up --wait waits for healthchecks; if it times out, increase start_period in the gateway service or inspect make logs-redis.
  • Login form rejects every code — confirm BACKEND_AUTH_DEV_FIXED_CODE is set in tools/local-dev/.env and 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.env at boot. Restart pnpm dev after editing ui/frontend/.env.development.local.
  • Port 8080 already in use — stop the conflicting service or edit the host-side mapping in docker-compose.yml (gateway's ports: entry) plus the matching VITE_GATEWAY_BASE_URL in ui/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 by make -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.