Skip to content

Development Environment

Status: 🚧 Partial — devcontainer, Codespaces URL detection, Playwright, and core docker compose commands are all in place; dev-scripts/ tooling is gitignored and no longer ships in this repo. (re-verified 2026-08-21 vs origin/feat 46bce720)

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:

  1. Local Docker — your laptop runs everything. Lowest setup; highest hardware demand.
  2. Codespaces — GitHub-hosted devcontainer. Zero setup; the URL changes per session.
  3. 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 a version: field — it relies on modern Compose behavior.
  • A .env file at the repo root. Start from Sectorwars2102/.env.example and 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, Tailscale.
  • 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' — no external setup script runs automatically; bring the stack up yourself with the docker compose commands below.

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/ is gitignored at the repo root (removed from tracking because the scripts carried personal VM credentials and usernames). No unified entry-point scripts (start-unified.sh, setup.sh, startup-health-check.sh, reset-first-login.sh, debug-player-auth.js, vm-*.sh) ship in this repo — bring the stack up directly with the docker compose commands below. The one file present locally under dev-scripts/ is an operator-side breadcrumb pointing at a private, out-of-repo hosting doc; it carries no setup logic and isn't referenced by anything in this repo's tracked source.


Common workflows

Bring the stack up (default profile)

docker compose up                 # foreground
docker compose up -d              # detached

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"

Current head (as of 2026-07-11): b9a7404a2c20add unique (owner_player_id, contract_id) index on storage_lockers — see ../DATA_MODELS/migrations.md for the full revision inventory.

Dev-drift reconcile guidance: Base.metadata.create_all is removed from the gameserver's startup path per ADR-0093 item 13 — Alembic is the sole schema authority, in dev as well as stage/prod. A fresh dev DB is schema-populated by running alembic upgrade head before first boot, not by the server's own startup. If alembic current reports a revision behind head, the schema is genuinely behind — resolve it by applying migrations, never by inferring the schema is already current:

  1. Run alembic history --verbose to see what migrations are between the stamped version and head.
  2. alembic upgrade head (or alembic upgrade <rev> one step at a time when verifying a risky migration, e.g. one with ALTER COLUMN, DROP, or a data backfill).
  3. Do not run alembic stamp head to skip past unapplied migrations — stamping without applying leaves the schema itself behind even though alembic_version claims otherwise.

The CI schema-parity gate is required (blocking): a PR whose migrations don't produce a schema matching the models fails CI, so drift of this kind should surface there before it ever reaches a dev machine.

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 single docker-compose.yml selects which service set runs via Compose profiles, not per-tier override files:

  • development / default — the default local dev stack (database, redis-cache, gameserver, player-client, admin-ui, nginx-gateway, region-manager).
  • multi-regional / production — adds central-nexus-db, redis-nexus, central-nexus-server. Used both for running the multi-regional topology locally and for any non-dev deployment (stage, prod) via profile selection plus a tier-specific .env.
  • regional-template — the recipe region-manager clones per provisioned region; not started directly.
  • monitoringprometheus + grafana, layered on top of production.

docker-compose.dev.yml is a narrow override, not a general per-tier file: it pins player-client's and admin-ui's VITE_API_URL / VITE_WS_URL to empty (same-origin) so a host .env copied from another environment can't leak a foreign API origin into the dev client build. It applies only to the development profile:

docker compose -f docker-compose.yml -f docker-compose.dev.yml --profile development up -d

There is no docker-compose.stage.yml or other per-tier override file in this repo. A stage or production deployment runs the same base docker-compose.yml with the multi-regional / production profile, its own .env (DB password, JWT secret, OAuth client_id/secret), and its own Compose project name (-p <name>) so containers, networks, and volumes don't collide with other tiers on a shared host. Tier-specific hostnames, ports, and access details live in the operator's private hosting docs, outside this repo.


URLs at a glance (default ports, dev tier)

Non-dev tiers (stage, prod) bind their own host ports via their own .env (each service's port is a ${..._PORT:-default} Compose variable) and their own Compose project name — there is no fixed cross-tier port-numbering convention checked into this repo.


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:

  1. Connect over a private overlay network (Tailscale, WireGuard, ZeroTier, or a VPN of your choice). The host should not be publicly reachable.
  2. VS Code Remote-SSH to the host. The editor backend, language servers, and integrated terminal all run remotely; your laptop is just the UI.
  3. Run Compose on the host with the multi-tier layering above. Bring up sw2102-dev for active work, sw2102-stage for invited-tester previews.
  4. Bind container ports to 127.0.0.1 on the host (not 0.0.0.0). The overlay network reaches them; the public internet does not.
  5. 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.

No lifecycle-automation scripts for this pattern ship in this repo — connect, run Compose, and tear down using the commands above directly. The host address and any access credentials live entirely outside this repo, in the operator's private hosting docs.

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_secret leaks 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

  • gameserver won't start, complains about JWT_SECRET: your JWT_SECRET is missing or shorter than 32 characters. Generate one with openssl rand -hex 32.
  • /docs returns 404: DEBUG is not true. 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_PORT in .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_modules volume; docker compose down -v and bring back up.
  • OAuth redirect loop in Codespaces: API_BASE_URL / FRONTEND_URL may have stale values. Empty them in .env so detect_environment() can reconstruct them from CODESPACE_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:8080 to 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.