Files
galaxy-game/tools/local-dev/README.md
T
Ilia Denisov 69fa6b30e1 tools/local-dev: docker-compose stack for UI development
Adds tools/local-dev/ with postgres + redis + mailpit + backend +
gateway plus a Make wrapper, so `make -C tools/local-dev up` brings
the full authenticated stack online and `pnpm -C ui/frontend dev`
talks to it directly. The committed `.env.development` already
points at the stack and pins the matching gateway response public
key from the dev keypair under tools/local-dev/keys/.

The backend ships a new opt-in env, BACKEND_AUTH_DEV_FIXED_CODE
(`tools/local-dev/.env` defaults it to 123456). When set,
ConfirmEmailCode accepts that literal in addition to the real
bcrypt-verified code; SendEmailCode still queues a real email so
Mailpit captures the issued code at http://localhost:8025/, and
both paths coexist. The override is rejected as non-six-digit by
config validation and emits a loud warning at backend startup.

The local-dev Dockerfiles mirror backend/Dockerfile and
gateway/Dockerfile but switch the runtime stage to alpine so
docker-compose healthchecks can wget /healthz; the gateway
Dockerfile additionally copies ui/core/ into the build context
because gateway/go.mod's `replace galaxy/core => ../ui/core` is
required to compile the gateway main.

Smoke tested:
- `make -C tools/local-dev up` boots all five services to healthy.
- send-email-code + confirm-email-code with code=123456 returns a
  device_session_id; a real code in Mailpit also redeems
  successfully.
- `pnpm test` 14/14, `pnpm exec playwright test` 44/44.
- `go test ./backend/internal/config/...` green.

Docs: tools/local-dev/README.md, tools/local-dev/keys/README.md,
new "Local development stack" section in ui/docs/testing.md, and a
short pointer in ui/README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 09:42:29 +02:00

7.1 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:

  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"
  ┌────────────────────────────┐             ┌──────────────────────────────┐
  │ pnpm dev   localhost:5173  │──HMR──▶│ host (Vite)                       │
  │ browser    localhost:8080  │──REST/Connect─▶│ gateway:8080            │
  │ browser    localhost:8025  │─────▶│ mailpit:8025  (web UI)             │
  │ psql       localhost:5433  │─────▶│ postgres:5432                      │
  │ redis-cli  localhost:6380  │─────▶│ redis:6379                         │
  └────────────────────────────┘             │  ↳ backend:8080  (HTTP)      │
                                             │  ↳ backend:8081  (gRPC push) │
                                             │  ↳ mailpit:1025  (SMTP in)   │
                                             └──────────────────────────────┘

Only the gateway public port (8080) and the mailpit web UI (8025) are needed for normal UI work. Postgres (5433) and Redis (6380) are exposed for direct inspection (make psql, redis-cli -h localhost -p 6380 -a galaxy-dev).

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.