Skip to content

Updates, backups and logs

Terminal window
cd ~/beholder
docker compose pull
docker compose up -d

Connected players reconnect automatically after a few seconds. Changes they made during the restart are sent once they’re back online.

If the new server speaks a newer protocol, older apps show Please update Beholder and keep working offline until they’re updated.

latest is the newest release; it only moves when a version is published (a v… tag). Pushes to main build a separate main image for testing. To stay on a given version, set BEHOLDER_TAG=0.1.0 in .env.

On Kubernetes with the latest tag, the pod only downloads a new image when it restarts: run kubectl rollout restart deploy/beholder -n <namespace> after a release.

Everything is in the beholder-data Docker volume:

Path in the volume Content
beholder.db (+ -wal, -shm) SQLite database: accounts, sessions, campaigns, event log
images/ab/<hash>-thumb.webp Cover thumbnails (~480 px wide)
images/ab/<hash>-display.webp Covers at display size (max 1920 px)

The simplest safe backup stops the server for a few seconds:

Terminal window
docker compose stop server
docker run --rm -v beholder_beholder-data:/data -v "$PWD":/backup alpine \
tar czf /backup/beholder-$(date +%F).tar.gz -C /data .
docker compose start server

The volume name is <folder>_beholder-data; check it with docker volume ls. To restore, stop the server, extract the archive into the volume the same way, and start it again.

Without stopping anything, beholder-server backup [keep] writes a consistent copy of the database to backups/beholder-<time>.db in the data folder and keeps the newest keep copies (14 by default). Images aren’t included, since they rarely change and the archive above covers them:

Terminal window
docker compose exec server /beholder-server backup

To restore one, stop the server, put the copy in place of beholder.db (and delete beholder.db-wal and beholder.db-shm), and start it again.

On Kubernetes, deploy/k8s/ runs this command every night from a CronJob; its README shows how to copy backups off the cluster.

To fill the server library from a repository of JSON files laid out like nick-aschenbach/dnd-data (one file per category: spells.json, monsters.json, items.json, classes.json, species.json, backgrounds.json), set these in .env and run docker compose up -d:

Terminal window
BEHOLDER_IMPORT_REPO=https://github.com/owner/repository
BEHOLDER_IMPORT_REF=HEAD

On its next start the server downloads the repository’s .tar.gz archive over HTTPS (GitHub, GitLab, Gitea and Codeberg style URLs, or a direct link to a .tar.gz), reads the JSON files in it one record at a time, converts them like cargo xtask dnd-data does, and adds them to the library as one import. Entries already in the library (same type, name, book and rules version) are skipped. It runs once: after a successful import it isn’t repeated, even if the variables stay set. If it fails (no network, wrong URL), the log says why and it’s tried again at the next start. BEHOLDER_IMPORT_REF is a branch or tag; HEAD means the default branch on GitHub, and GitLab needs a branch name.

The imported entries belong to the server rather than to a user, so only a DM can edit them. A DM can also undo the whole import from Recent imports in the Compendium. The content stays on your server and is never bundled with the app; only import content you’re allowed to use.

Passwords are never stored, only an Argon2id hash of them, so nobody can read a password back, not even the admin. When someone forgets theirs, give them a new one:

Terminal window
docker compose exec server /beholder-server users
docker compose exec server /beholder-server reset-password alice

users lists every account with its display name and number of active sessions. reset-password sets a new random password for that username (case doesn’t matter), prints it once, and signs the user out everywhere. Send it to them privately; they log in with it. Locally, run the same commands with BEHOLDER_DATA_DIR=./data-dev cargo run -p beholder-server -- users. They work while the server is running.

Terminal window
docker compose logs -f server

Set BEHOLDER_LOG=debug in .env (then docker compose up -d) for more detail; info is the default.

Once a day the server:

  • keeps the last 1,000 events of each campaign (older history is summarised; apps that were offline for a very long time simply receive the current state),
  • forgets command receipts older than 8 days,
  • deletes sessions unused for 30 days.