Backup and restore
How the hosted console's data is backed up today, how to restore it, and the recovery point that gives you. Written against deploy/run.sh's actual container topology.
What exists today
No automated backups exist today. deploy/run.sh provisions the database and its volumes for durability across a restart of the container (that is the stated point of running Postgres at all — "Durability is the point. With the in-memory store every session and every account vanished on restart, which is not a property you can ask a user to accept"), but nothing in this repository schedules a periodic export. Backup, if you want one, is a cron job or equivalent that an operator sets up around the procedure below — this document describes the mechanism, not a running schedule.
What to back up
Two things, both named in deploy/run.sh:
- The database, via
pg_dumpfrom theabhed-dbcontainer, connecting asabhed_admin(the provisioning superuser — seedocs/trust/security-posture.mdfor why the application itself never connects this way). - The two named volumes the container engine manages directly:
abhed-workspace($ABHED_VOLUME) — the files the agent reads and writes.abhed-state($ABHED_STATE_VOLUME) — account and session state kept outside the workspace (deploy/run.sh's comment: "Accounts live on their own volume rather than in the workspace").
abhed-db-data ($ABHED_DB_VOLUME), the Postgres data directory itself, is what pg_dump reads from live and does not need a separate raw-volume backup if the pg_dump step runs — a logical dump is the recommended path because it is consistent and version-portable, where a raw copy of the data directory is not.
How to back up
# 1. Database — logical dump via pg_dump, run inside the abhed-db container.
podman exec -t abhed-db pg_dump -U abhed_admin -d abhed \
--format=custom --file=/tmp/abhed-$(date +%F).dump
podman cp abhed-db:/tmp/abhed-$(date +%F).dump ./backups/
# 2. Workspace and state volumes — a throwaway helper container tars each
# volume's contents; no dedicated backup tooling exists, so this is the
# same pattern deploy/run.sh itself uses for one-off container work.
podman run --rm \
--volume abhed-workspace:/data:ro \
--volume "$(pwd)/backups":/backup \
docker.io/library/alpine tar czf /backup/abhed-workspace-$(date +%F).tgz -C /data .
podman run --rm \
--volume abhed-state:/data:ro \
--volume "$(pwd)/backups":/backup \
docker.io/library/alpine tar czf /backup/abhed-state-$(date +%F).tgz -C /data .
Substitute docker for podman if that is the runtime in use — run.sh itself is written to accept either via ABHED_RUNTIME.
deploy/.db-password and deploy/.db-admin-password are not part of this backup by design: they are regenerable secrets, not data, and a backup archive is a worse place for a database credential than the mode-0600 file run.sh already keeps them in.
Restore order
Order matters because the server applies its schema on connect (deploy/run.sh's comment: "Abhed applies its schema on connect (internal/store/postgres.go), but only once the server is actually accepting connections"):
- Bring up a fresh
abhed-dbcontainer against emptyabhed-db-data/replacement volumes, and letdeploy/run.shprovisionabhed_admin/abhed_appas it normally does on first run. - Restore the database into it, before starting the Abhed server: ``
bash podman cp ./backups/abhed-2026-09-14.dump abhed-db:/tmp/restore.dump podman exec -t abhed-db pg_restore -U abhed_admin -d abhed \ --clean --if-exists /tmp/restore.dump`` - Restore the workspace and state volumes before the Abhed container starts, so it never observes a partially-restored workspace: ``
bash podman run --rm \ --volume abhed-workspace:/data \ --volume "$(pwd)/backups":/backup \ docker.io/library/alpine sh -c "rm -rf /data/* && tar xzf /backup/abhed-workspace-2026-09-14.tgz -C /data" # repeat for abhed-state`` - Start Abhed (
./deploy/run.sh serve -addr 0.0.0.0:8080) only after steps 2 and 3 complete, and confirm withabhed doctorand the/v1/healthendpoint perdeploy/GO-LIVE.md's verify steps. - Re-run
deploy/set-access-email.shif Pages secrets (RESEND_API_KEY,ACCESS_TO) were part of what was lost — they live in Cloudflare, not in these volumes, and are unaffected by a database restore, but are called out here because an incident that requires a full restore may also be one where those were rotated perdocs/trust/incident-response.md.
Recovery point objective
Whatever the operator schedules. Because no automated backup job exists in this repository today, the RPO is exactly the gap between manual runs of the procedure above. Run it nightly and the RPO is up to 24 hours of lost events, accounts, and workspace changes in the worst case; run it hourly and it is up to an hour. This document does not claim a number because there is no schedule to point at — stating one would be exactly the kind of unverifiable claim README.md's evidence-discipline section and internal/sitecheck exist to catch.
Until an automated schedule exists, treat this as: RPO = however long since someone last ran the backup command by hand.