This is the runbook for the one canonical, public BotFights arena. Every
node's local instance can proxy match/fighter state to a shared arena via
`ARENA_UPSTREAM_URL` — this document covers deploying the well-known default
one, on the Foundation's VPS2 host.
Nothing here is git-tracked automatically: nginx-proxy-manager's routing
config and the host `.env` (secrets) live only on the VPS2 host. This file is
the only record of how to reproduce or roll back the deployment.
## Architecture: arena-as-relay (read this first)
BotFights' shared-arena design is intentionally decentralized, not
hardcoded to one server:
- **Any node can host a public arena.** It's the exact same container image
any node already runs — a "public arena" is just a BotFights instance with
`ARENA_UPSTREAM_URL`**unset** (standalone mode) that other nodes point at.
- **Each node picks its own community** by setting `ARENA_UPSTREAM_URL` in
its own manifest/environment. Unset = fully standalone, own local SQLite DB.
- **This VPS2 deployment is only the well-known default rendezvous** — like
the vps2 FIPS anchor — not an authority baked into the code. Nothing in
`botfight`'s server or frontend code hardcodes `146.59.87.168`; it is
entirely an environment-variable choice made by whoever configures a node.
- The game UI is always served locally by each node; only match/fighter
state lives wherever `ARENA_UPSTREAM_URL` points.
- **How to host your own arena:** deploy this exact `docker-compose.arena.yml`
pattern (or even the node's normal `docker-compose.yml`) anywhere reachable,
leave `ARENA_UPSTREAM_URL` unset on it, generate your own `JWT_SECRET`, and
point whichever nodes you want in your community at
`ARENA_UPSTREAM_URL=http://<your-host>:<port>`. There is no registration or
allowlist step — the protocol is "point at a URL that speaks the BotFights
API."
## Current canonical instance
| Field | Value |
|---|---|
| Host | VPS2, `debian@146.59.87.168` (docker, not podman — this is host infra, not an Archipelago node) |
| Directory | `/opt/botfights-arena/` |
| Compose file | `/opt/botfights-arena/docker-compose.yml` (copied from this repo's `docker-compose.arena.yml`, not symlinked — re-copy after edits) |
| Container name | `botfights-arena` |
| Image | `localhost:3000/lfg2025/botfights:1.1.0` (Gitea registry on the same host; `localhost:3000` resolves without any insecure-registry config because Docker trusts loopback registries by default — this is why the compose file uses `localhost:3000`, not the public `146.59.87.168:3000`, as the image ref) |
| Port | **9100** (verified free before binding; now bound — see `ss -tlnp` output in this phase's execution log) |
| Data volume | named volume `botfights-arena-data` → `/app/server/data` inside the container (host mountpoint: `docker volume inspect botfights-arena_botfights-arena-data --format '{{.Mountpoint}}'`) |
`http://146.59.87.168:9100` to `https://botfights.archipelago-foundation.org` — DONE 2026-07-30: the user created the DNS A record and the NPM proxy host with a Let's Encrypt cert; `TRUSTED_PROXY=1` was enabled on the arena at the same time (it now sits behind NPM).
**Build gotcha hit this session (pre-existing, unrelated to phase 09's own
code — fixed as an in-scope blocking-issue deviation):** `pnpm install
--frozen-lockfile` inside the `deps` build stage failed with
`ERR_PNPM_LOCKFILE_CONFIG_MISMATCH`. Root cause: an earlier commit
(`bcb323e`, March 2026) moved dependency `overrides` from `package.json`'s
`pnpm.overrides` key (a location modern pnpm no longer reads at all — see
its own deprecation warning) to `pnpm-workspace.yaml`'s `overrides:` key,
but only migrated 2 of 3 override entries and never regenerated
`pnpm-lock.yaml` to match. The Dockerfile's `corepack prepare pnpm@latest`
pulls whatever pnpm is current at build time, which enforces the
lockfile-vs-config check strictly. Fixed by: removing the dead `pnpm`
field from `package.json`, adding the missing `tar: '>=7.5.11'` override to
`pnpm-workspace.yaml` (alongside the two already there), and regenerating
`pnpm-lock.yaml` with `pnpm install --no-frozen-lockfile` — the resulting
lockfile diff contains **zero**`specifier:` changes (verified by grep),
only peer-dependency resolution-graph annotations from the newer pnpm
version explicitly listing `supports-color` as a peer. `pnpm install
--frozen-lockfile` and `tsc --noEmit -p server/tsconfig.json` both pass
clean against the regenerated lockfile.
**Result (this session, 2026-07-31):**
| Field | Value |
|---|---|
| Tag | `146.59.87.168:3000/lfg2025/botfights:1.2.0` |
| Digest | `sha256:5470019a...c1b6` (short form; full digest recorded in `.planning/phases/09-botfights-platform-upgrade/09-05-SUMMARY.md` — re-derive any time with `skopeo inspect` above) |
| Built from | `botfight` repo `main` @ `2a343ac` (HEAD at build time, matches `origin/main`) |
| Local smoke test | `/api/health` → `{"status":"ok",...}`; `/api/docs/prompt` → 200 `text/markdown`; `/api/auth/me` (no auth) → 401 |