Appearance
Requirements
All figures on this page come from DEPLOY.md. The capacity numbers were measured with test/load.js, not estimated. You can re-run them yourself. The platform facts were checked in August 2026. Free tiers change constantly, so re-check anything that decides your bill.
Software
- Node 22 (the Dockerfile uses
node:22-slim, and CI runs Node 22). The server usesnode:sqliteand Ed25519 in WebCrypto. - One npm dependency:
ws.node_modulesis about 200 KB. - Optional: GNU Backgammon (
gnubg) for a stronghardbot and deep analysis. The Docker image already includes it. BGBlitz is commercial and cannot be bundled. See Analysis & PR.
Measured capacity
One container, one process:
| what | measured |
|---|---|
| idle | 71 MB RSS, 9 MB heap |
200 concurrent 9-point matches (LOAD_MATCH_LENGTH=9 node test/load.js 200) | 231 MB RSS peak, 824 KB per live match, 400 sockets |
| 200 concurrent 1-point matches | 154 MB RSS, 2,180 events/sec |
| 25 concurrent matches against the bot | 110 MB RSS |
cold start (boot → /healthz answering) | 291 ms |
| a finished match on disk | ~4 KB gzipped |
| database | ~92 KB at 22 players / 16 matches |
| runtime dependencies | one (ws); node_modules is 200 KB |
Re-run it
bash
node test/load.js 200 # 200 concurrent 1-point matches
LOAD_MATCH_LENGTH=9 node test/load.js 200 # 200 concurrent 9-point matchestest/load.js opens every socket from one address, so it runs into the connection rate limit. That is how the project discovered the limit needed to be tunable. Raise LIMIT_CONNECT_BURST for large runs. See Rate limiting.
What that means
- Minimum that works: 512 MB RAM, 1 shared vCPU. That holds about 200 concurrent long matches with headroom. 256 MB works for about 100 concurrent long matches. Below that, the idle 71 MB starts to dominate.
- CPU is near zero for games between humans. The server does no evaluation for them. Bot games are the only CPU cost, and deep analysis runs on volunteer workers, not on the server.
- Disk is a rounding error. At 4 KB per match, 10,000 matches take 40 MB. You need a volume for persistence, not for capacity.
- A 4 GB box can also run an analysis worker with gnubg alongside the server. A 512 MB managed instance cannot.
Persistence is not optional
Without a persistent volume, server-key.json is regenerated on every restart, and every record the server has ever signed becomes unverifiable against the key it then publishes. That is not just data loss: it breaks the project's central claim. If your platform has secrets but no disk, pass the key as SERVER_KEY_PKCS8.
Where to run it
| option | what DEPLOY.md says (checked August 2026) |
|---|---|
| Hetzner CX22 (VPS) | 2 vCPU, 4 GB RAM, 40 GB disk, 20 TB traffic. €4.49/month as of the 1 April 2026 price change. Hetzner raised prices again in June 2026, and reports of the size of that increase disagree, so check the current price. IPv4 is billed separately (a few cents a month). This is the path documented in Deploy to a VPS |
| Hetzner CAX11 (ARM) | same 2 vCPU / 4 GB, about €0.70/month cheaper. Not verified with this project |
| Oracle Cloud Always Free | 2 OCPU / 12 GB Ampere A1 (ARM) plus 200 GB block storage, with no time limit. Ampere capacity is a lottery: people wait days or weeks |
| Northflank | the free Sandbox plan is always-on (no sleeping). DEPLOY.md could not confirm whether it includes a persistent volume. Check before relying on it. Pay-as-you-go starts at $2.70/month for 0.1 vCPU / 256 MB, roughly $5–6/month at 512 MB |
| Render | free web services sleep after 15 minutes idle (30–60 s cold start) and cannot attach a persistent disk. render.yaml therefore uses the paid starter plan with a 1 GB disk |
| Railway | $5/month of included credit on Hobby, usage-based, no hard spending cap. Reads railway.json |
| Fly.io | no free allowance for new organisations since 2024: a short trial, then roughly $2–5/month. fly.toml sets pristine = true so the proxy does not interfere with the WebSocket upgrade |
| Cloudflare Workers + Durable Objects | fits the architecture but needs a port (see Architecture) |
Cloudflare Workers + Durable Objects
DEPLOY.md sizes the Workers Free plan for SQLite-backed Durable Objects at about 3M requests/month (100,000/day), about 390K GB-s (13,000/day), and 5 GB storage. Incoming WebSocket messages bill at 20:1, and outgoing messages are free. A 9-point match runs about 990 events, which works out to roughly 50 billable requests per match, or on the order of 60,000 matches a month at no cost. The paid tier starts at $5/month.
A completely free stack, as listed in DEPLOY.md
| piece | provider | free allowance |
|---|---|---|
| server | Northflank Sandbox | 2 services, always-on. Volume support unconfirmed |
| server, alternative | Oracle Always Free | 2 OCPU / 12 GB ARM VM + 200 GB disk, if capacity is available |
| Mailjet | 6,000/month, 200/day, multiple domains | |
| backups | Cloudflare R2 | 10 GB, no egress fees |
| error alerts | Discord webhook | no account needed |
| analysis | your own laptop | npm run worker |
The only unavoidable cost is the domain (about $10–15 a year).
What you take on with a VPS
| managed platform | VPS | |
|---|---|---|
| TLS | included | Caddy |
| deploy on push | included | deploy/push.sh |
| restart on crash | included | restart: unless-stopped (already in the compose file) |
| logs, metrics | UI | docker logs, journalctl |
| OS patching, firewall, SSH hardening | not your problem | yours, permanently |
The last row is the real cost, and it costs time rather than money. That is why deploy/ automates it. DEPLOY.md notes that for roughly what a managed 512 MB instance costs, a CX22 buys about eight times the RAM.