Install the server
One server hosts one group. It’s designed for a 1 GB RAM Linux VPS and uses very little:
| Measured | |
|---|---|
| Server memory when idle | about 6 MB |
| Server memory at peak (re-encoding a 40-megapixel photo) | about 157 MB |
| Server binary | about 10 MB |
The VPS never compiles anything. The image is built by GitHub Actions and the VPS only downloads it.
The server also serves this documentation at https://<your domain>/docs/, and its home page redirects there.
If the VPS already runs a Kubernetes cluster with its own ingress on ports 80 and 443, use the manifests in deploy/k8s/ instead of the steps below: they run the same image behind the existing ingress, with a nightly backup.
What you need
Section titled “What you need”- A Linux VPS with Docker and the Docker Compose plugin.
- A domain name pointing at the VPS (an
Arecord, e.g.dnd.example.org). - Ports 80 and 443 open. Caddy uses 80 to obtain the TLS certificate.
1. Add a swap file
Section titled “1. Add a swap file”A small swap file protects the VPS if memory runs short:
sudo fallocate -l 1G /swapfile && sudo chmod 600 /swapfilesudo mkswap /swapfile && sudo swapon /swapfileecho '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab2. Copy the deploy files
Section titled “2. Copy the deploy files”Copy deploy/docker-compose.yml and deploy/Caddyfile from the repository to a folder on the VPS (e.g. ~/beholder), then create .env next to them:
BEHOLDER_DOMAIN=dnd.example.orgGHCR_OWNER=your-github-user-in-lowercase# BEHOLDER_TAG=0.1.0 # pin a release; default is latestIf the image is private on GitHub, log in once with a token that has the read:packages scope:
docker login ghcr.io -u your-github-user3. Start
Section titled “3. Start”docker compose up -ddocker compose logs -fCaddy obtains a certificate automatically. Players then enter https://dnd.example.org (or just dnd.example.org) as the server address.
What’s running
Section titled “What’s running”| Container | Role | Memory limit |
|---|---|---|
server |
The Beholder server, listening on port 8080 inside the Docker network only | 256 MB |
caddy |
TLS on 443, forwards everything (including the /ws WebSocket) to the server |
128 MB |
Caddy speaks HTTP/1.1 and HTTP/2 only. HTTP/3 (QUIC over UDP) is off on purpose, because UDP is often throttled on VPNs.
Accounts
Section titled “Accounts”Registration is open: anyone who knows the address can create an account, but they only see campaigns they create or join with an invite code. Attempts are rate-limited (5 registrations per hour and 20 logins per minute per IP, 10 logins per minute per username, 10 join attempts per minute per user).