Disaster Recovery
What you must be able to rebuild
Section titled “What you must be able to rebuild”- Panel database — users, servers, roles, audit logs: everything except
the game files. It lives in the
catalyst-postgres-datavolume (Compose prefixes the name with the project name). - Panel configuration — the compose directory the installer created
(
./catalyst-dockerunder the directory you ran it from) with its.env. The.envholdsPOSTGRES_PASSWORD,BETTER_AUTH_SECRET,API_KEY_SECRET, andBACKUP_CREDENTIALS_ENCRYPTION_KEY; losing them invalidates sessions, API keys, and stored backup credentials even if the database survives. - Node agents — reproducible: reinstall from the panel with a fresh Deploy command (installing a node). Game data comes back from user backups (user backups).
- File volumes —
catalyst-server-data,catalyst-backup-data, andcatalyst-plugin-dataon the panel host. Redis has no volume: it is an ephemeral cache and is rebuilt from the database automatically.
Back up the panel
Section titled “Back up the panel”Schedule, retention, and the storage backend are covered in
backups; this section is the command reference. Run these on
the panel host, from the compose directory. POSTGRES_USER and POSTGRES_DB
default to catalyst and catalyst_db; use your .env values, and read
CATALYST_COMPOSE_DIR there if you have moved the stack.
cd ./catalyst-docker # the directory install.sh createddocker exec catalyst-postgres pg_dump -U catalyst -d catalyst_db \ > catalyst-panel-$(date +%F).sqltar czf catalyst-config-$(date +%F).tgz \ .env docker-compose.yml docker-compose.*.yml nginx caddy traefikTo capture the file volumes as well, archive each one with a throwaway container. Compose prefixes volume names with the project name (the compose directory name), so discover the real names first:
docker volume ls --format '{{.Name}}' | grep -E '_catalyst-(server|backup|plugin)-data$' \ | while read -r v; do docker run --rm -v "$v":/data -v "$PWD":/backup alpine \ tar czf "/backup/${v}.tgz" -C /data . doneShip the dump, the config archive, and the file archives off the host. Check that the dump is non-empty before you trust it.
Restore the panel
Section titled “Restore the panel”- On the replacement host, install a fresh stack with the one-liner:
curl -fsSL https://github.com/catalystctl/catalyst/releases/latest/download/install.sh | sudo bash(installing Catalyst). Stop it before importing:docker compose down— neverdown -v, which deletes the volumes. - Put the saved
.envback into the compose directory. Restoring the original secrets is what makes existing sessions, API keys, and backup credentials valid again. - Start PostgreSQL only, then restore the dump into it:
Terminal window docker compose up -d postgrescat catalyst-panel-<date>.sql | docker exec -i catalyst-postgres \psql -U catalyst -d catalyst_db - Restore the file volumes, if you captured them. The volume names must match
the ones Compose expects, which is why the compose directory keeps the same
name:
Terminal window shopt -s nullglobfor f in catalyst-*_catalyst-{server,backup,plugin}-data.tgz; dov="${f%.tgz}"docker run --rm -v "$v":/data -v "$PWD":/backup alpine \sh -c "rm -rf /data/* && tar xzf /backup/$f -C /data"done - Start the rest of the stack:
docker compose up -d. - Log in, open Admin → Nodes, and confirm each node reconnects. Nodes that were wiped or rebuilt need a fresh agent install; their game data comes from backups on the node or in the backup volume.
bash update.sh --restore <backup-dir> restores the compose files from the
backup update.sh takes before it rewrites them — useful if an update is what
broke the stack.
Restore failures that are not the archive’s fault
Section titled “Restore failures that are not the archive’s fault”A restore that dies partway is usually the node, not the backup. Two known causes, both fixed in current agent releases — keep nodes updated before drilling:
Function not implemented(ENOSYS) on Fedora 44. Fedora’s GNU tar usesopenat2(2)when extracting, and systemd’sRestrictSUIDSGID=yesblocks that syscall. The agent unit now shipsRestrictSUIDSGID=nowith the other sandbox directives intact, and a node upgraded in place repairs itself on the next agent start. See backup restores fail with Function not implemented.- Symbolic and hard links in the archive. Older agents refused them. Current agents restore links inside the server directory as they were and still refuse archive-supplied ownership, permissions, device entries, and any member or link that would escape the server directory.
The recovery drill (quarterly)
Section titled “The recovery drill (quarterly)”If you have never restored, you do not have recovery. Take a fresh dump, config archive, and file archive; restore them into a scratch host as above; log in, open servers, list backups, check the audit log; and time the whole thing — that duration is your real RTO.
During a real incident: declare who is in charge and where status is posted, restore in dependency order (database → panel → nodes → verify servers), and keep the recovery instructions somewhere other than the host they recover.
Related
Section titled “Related”- Backup schedule, storage backend, and retention: backups.
- Installing a fresh stack: installation.
- Re-adding nodes: installing a node.
- User-side restore: user backups.
