Development Environment¶
How to run SectorWars 2102 for development. The contract is the same in every environment: Docker Compose brings the stack up, source is bind-mounted so edits hot-reload.
Three documented environments, in order of friction:
- Local Docker — your laptop runs everything. Lowest setup; highest hardware demand.
- Codespaces — GitHub-hosted devcontainer. Zero setup; the URL changes per session.
- Remote dev host — a Linux host (yours, the team's, or any cloud VM) accessed over SSH. Highest fidelity for OAuth and multi-tier work.
All three speak the same Compose stack with the same .env shape; only the where it runs differs.
Prerequisites¶
- Docker (Desktop or Engine) with Compose v2 (
docker compose ...). The compose file does not pin aversion:field — it relies on modern Compose behavior. - A
.envfile at the repo root. Start fromSectorwars2102/.env.exampleand fill in: DATABASE_URL(use the local container's connection string for fully-local dev).JWT_SECRET— must be at least 32 characters, otherwise the gameserver refuses to boot (services/gameserver/src/core/config.py).ADMIN_USERNAME/ADMIN_PASSWORD.- Any OAuth keys you want to test (
CLIENT_ID_GITHUB,GOOGLE_CLIENT_ID,STEAM_API_KEY, …). Use per-tier OAuth apps so dev secrets are isolated from stage / prod. - Optional: Node 18 and Python 3.11 if you want to run lint/build outside containers. The devcontainer image bundles both.
Devcontainer (Codespaces / VS Code Remote)¶
Sectorwars2102/.devcontainer/devcontainer.json defines a universal devcontainer with:
- Base image
mcr.microsoft.com/devcontainers/universal:2. - Features: Docker-in-Docker, Node 18, Python 3.11.
- Forwarded ports:
3000(player),3001(admin),8080(gameserver),5433(Postgres),6379(Redis),80/443(Nginx). - VS Code extensions: Python, TypeScript, Tailwind, JSON.
postCreateCommand:echo 'Devcontainer setup complete'— the heavy lifting is indev-scripts/setup.sh, not the devcontainer hook.
In Codespaces, settings.detect_environment() (in core/config.py) inspects CODESPACE_NAME / GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN and rewrites API_BASE_URL / FRONTEND_URL to the Codespaces-forwarded hostnames automatically.
dev-scripts¶
Sectorwars2102/dev-scripts/ holds the unified entry points.
| Script | Purpose |
|---|---|
start-unified.sh |
Detects environment, optionally switches between development/production/test env files, then runs docker compose up. Accepts --no-host-check and --production-db. |
setup.sh |
Idempotent first-run setup: Node + npm, Python deps, frontend installs, .env scaffolding. |
startup-health-check.sh |
Pings each service after start to verify they are healthy. |
reset-first-login.sh |
Clears the first-login flag on a player so you can replay the AI dialogue flow. |
debug-player-auth.js |
Standalone Node script that exercises the player auth flow end-to-end. |
vm-start.sh / vm-stop.sh / vm-sync.sh |
Remote-host lifecycle helpers (see "Remote dev host" below). The host's address and any access credentials live in gitignored files. |
The README at Sectorwars2102/dev-scripts/README.md documents script-level behavior.
Common workflows¶
Bring the stack up (default profile)¶
docker compose up # foreground
docker compose up -d # detached
./dev-scripts/start-unified.sh # equivalent, with environment auto-detection
This starts: database, redis-cache, gameserver, player-client, admin-ui, nginx-gateway, region-manager.
Multi-regional topology¶
docker compose --profile multi-regional up
Adds: central-nexus-db, redis-nexus, central-nexus-server. The region-manager is shared with the dev profile.
Tear down¶
docker compose down # stop containers
docker compose down -v # also delete named volumes (DB wipe)
Inspect logs¶
docker compose logs -f gameserver
docker compose logs -f player-client admin-ui
Run commands inside containers¶
# Gameserver tests (Poetry-managed)
docker compose exec gameserver poetry run pytest
docker compose exec gameserver poetry run pytest tests/unit/
# Frontend type-check / build
docker compose exec player-client npm run build
docker compose exec admin-ui npm run lint
# Alembic migrations
docker compose exec gameserver poetry run alembic upgrade head
docker compose exec gameserver poetry run alembic revision --autogenerate -m "description"
End-to-end tests¶
npx playwright test -c e2e_tests/playwright.config.ts
npx playwright test --reporter=html
Screenshots land in e2e_tests/screenshots/.
Multi-tier compose layering¶
The same compose stack supports multiple isolated tiers (e.g. dev and stage) on the same host by combining a base file with a per-tier override and a unique project name.
# Dev tier
docker compose -p sw2102-dev \
-f docker-compose.yml -f docker-compose.dev.yml up -d
# Stage tier (different ports, no source mounts, separate volumes)
docker compose -p sw2102-stage \
-f docker-compose.yml -f docker-compose.stage.yml up -d
Conventions:
-p sw2102-<tier>namespaces containers, networks, and volumes — tiers cannot collide.- Each tier has its own
.env(with its own DB password, JWT secret, OAuth client_id/secret). - Each tier binds host ports in its own range (e.g. dev = 80xx, stage = 90xx) so both can run simultaneously.
- Dev mounts
./<service>/srcinto containers for hot reload; stage and prod build images and don't mount source.
When tiers live on different hosts (dev/stage on one host, prod on another), the project-name namespacing still applies — the host topology is described in the operator-side hosting plan, which lives outside this repo.
URLs at a glance (default ports, dev tier)¶
- Player client: http://localhost:3000
- Admin UI: http://localhost:3001
- Gameserver API: http://localhost:8080/api/v1/
- Swagger / OpenAPI: http://localhost:8080/docs (only when
DEBUG=true) - Region manager: http://localhost:8081/health
- Postgres:
postgresql://postgres@localhost:5433/sectorwars_dev - Nginx (if in production-style profile): https://localhost/
For the stage tier, shift each port by +1000 (3000 → 4000, 8080 → 9080, etc.) — the per-tier override file owns those mappings.
Remote dev host (optional pattern)¶
For laptops that can't comfortably run the full stack — or when you want a stage tier on the same machine as dev — running everything on a remote Linux host is the common path. The pattern:
- Connect over a private overlay network (Tailscale, WireGuard, ZeroTier, or a VPN of your choice). The host should not be publicly reachable.
- VS Code Remote-SSH to the host. The editor backend, language servers, and integrated terminal all run remotely; your laptop is just the UI.
- Run Compose on the host with the multi-tier layering above. Bring up
sw2102-devfor active work,sw2102-stagefor invited-tester previews. - Bind container ports to
127.0.0.1on the host (not0.0.0.0). The overlay network reaches them; the public internet does not. - For OAuth in dev, use SSH local port forwarding from your laptop:
ssh -L 8080:localhost:8080 -L 8081:localhost:8081 -L 8083:localhost:8083 <dev-host>
GitHub, Google, and Steam all allow http://localhost:* callbacks. Your laptop browser hits http://localhost:8080, the SSH tunnel forwards to the dev host's container, and OAuth redirects back through localhost cleanly. The provider never directly reaches your dev host.
The vm-start.sh / vm-stop.sh / vm-sync.sh helpers in dev-scripts/ automate this lifecycle for the team's preferred remote host. The host address itself lives in gitignored files; clone those locally to your dev-scripts/ directory if they're shared with you out-of-band.
For Apple Sign-In (which forbids localhost callbacks), use the team's stage tier instead — its hostname is publicly reachable behind operator-defined access control.
OAuth callback URLs per tier¶
Register separate OAuth apps at each provider for each tier you run. Per-tier apps mean:
- Dev
client_id/client_secretleaks can't grant access to prod. - Independent rate limits — dev iteration churn doesn't burn prod's quota.
- Clean revocation — kill the dev app entirely without touching users.
| Tier | Callback URL pattern (GitHub example) |
|---|---|
| Local / dev (with SSH port-forward) | http://localhost:8080/auth/github/callback |
| Stage | https://<stage-host>/auth/github/callback |
| Prod | https://<prod-host>/auth/github/callback |
Steam uses OpenID 2.0 rather than OAuth 2.0 — register a Web API key per tier and configure the return_to / realm URLs analogously. Steam allows http://localhost:* for the return_to, so the SSH-port-forward pattern above works for it too.
Quick troubleshooting¶
gameserverwon't start, complains about JWT_SECRET: yourJWT_SECRETis missing or shorter than 32 characters. Generate one withopenssl rand -hex 32./docsreturns 404:DEBUGis nottrue. The OpenAPI surface is intentionally hidden in production (services/gameserver/src/main.py).- Port 5433 already in use: another Postgres instance is bound. Either stop it, or change
DB_PORTin.env. If you're running multiple tiers on one host, give each tier its own port range via the per-tier compose override. - Hot reload not working: confirm the bind mount didn't get shadowed by a stale
node_modulesvolume;docker compose down -vand bring back up. - OAuth redirect loop in Codespaces:
API_BASE_URL/FRONTEND_URLmay have stale values. Empty them in.envsodetect_environment()can reconstruct them fromCODESPACE_NAME. - OAuth callback fails on remote dev host: you forgot the SSH port forward. Open the tunnel before clicking "Login with GitHub" — the provider's redirect goes through your laptop's browser, which needs
localhost:8080to resolve to the forwarded socket. - Two tiers fighting for the same host port: each tier's compose override must pick a unique port range. Convention: dev = 80xx, stage = 90xx.