Draft: not yet reviewed by a clinician
Use this page to prepare questions for your care team. It does not replace their advice. Your own targets and doses come from your nephrologist, diabetes team and renal dietitian.
Backups¶
Everything kidney-health knows is in one SQLite file, kidney.db: every person's log, password hashes,
encrypted API keys and the activity log. A backup is a consistent copy of that file, taken with the
app's own command while it keeps running (deployment guide).
Take a backup¶
The built-in command, python -m app.admin backup, uses SQLite's online backup API, which copies a
database in use and leaves a consistent snapshot (SQLite). It needs no shell in the
container and never overwrites a file. The one-line command for Podman, Docker and Kubernetes is in the
deployment guide's backups section.
Then, every time:
- Encrypt the copy (restic, borg or age). It holds health data for everyone on the server.
- Keep it
0600, and keep more than one generation, off this machine. - Note where the backups go and how long you keep them: people who delete their account are still in older backups until those expire (design note 07).
Run it daily from a systemd user timer or cron on the host. If a backup tool on the host copies the files
for you, let the app write them into their own folder and keep the newest few: python -m app.admin backup
--dir /backups --keep 14 writes kidney- plus the date and time in UTC, and deletes only older files of
that name. Mount /backups as a separate volume and point the backup tool there, never at the live
database.
Server administration → About this server shows when the last app.admin backup ran. When there was none in the last
7 days, or ever, it says so with the command and a link to Restore. BACKUP_REMINDER_DAYS changes the 7 days; 0
turns the reminder off.
If your backups run another way (a snapshot of the volume, restic, a NAS job), the app cannot see them. End the backup job with
python -m app.admin record-backup; the command for each engine is in the guide's
backups section. The app then
counts that run as the last backup. It copies nothing, so check now and then that your backups really restore.
The secret key is separate¶
SECRET_KEY encrypts the stored API keys. A python -m app.admin backup copy does not contain it.
A volume-level backup (restic of the volume directory, a PVC snapshot, podman volume export)
does, if the key was auto-generated as /data/secret.key, and then anyone holding that backup can
read the keys. That is why every shipped profile mounts SECRET_KEY_FILE from the engine's secret
store, outside /data (operator security guide).
- Keep a copy of the secret key in a password manager.
- Restoring without it loses only the stored API keys; people re-enter them. Health data and passwords are not affected.
Check a backup¶
python -m app.admin restore-check, run with the same image, reports integrity, the schema version, the
number of users, and whether stored keys decrypt with the current key. That last check needs the app's
secret passed in; without it the command says the keys were not checked. Do it now and then, not only
when you need the backup. The command, for each engine, is step 1 of the guide's
restore steps.
The file can stay on a read-only (:ro) mount: app.admin backup writes its copies in SQLite's
rollback-journal mode, and restore-check opens a read-only file as immutable, so SQLite never has
to create a -shm file next to it.
Restore¶
What happens, in order; the commands for Podman, Docker and Kubernetes are in the guide's restore steps:
- Stop the app so nothing writes during the copy.
- Make the backup readable for UID 10001 (rootless engines map your own UID to container root).
- Copy it into the volume through SQLite, which also handles the
-waland-shmfiles and works from a read-only mount. On Kubernetes the image has notar, sokubectl cpcannot copy into it: the guide streams the file into a one-off restore pod (deploy/k8s/restore-pod.example.yaml). - Optional: sign everyone out of the restored copy (sessions in the backup become valid again), with the app still stopped.
- Start the app. Migrations run on start, so a backup restores into the same or a newer version, never an older one.
The automatic pre-v0.3 backup¶
Before the accounts migration, the upgrade copies the database to /data/kidney.db.pre-v3.bak. It is
the rollback path and is deleted automatically 30 days later (or at once with
python -m app.admin purge-pre-v3-backup). Server administration → About this server shows it while it exists
(Upgrades).
Export is not a backup¶
Each person can export their own data as a .zip (v0.3). That is for moving data or keeping a personal
copy, and for the right to take your data with you on a shared server (GDPR, Article 20). It is
not a server backup: it has no passwords, keys or other people's data (design note 07).
Replication is not a backup either¶
A replicated volume (Longhorn, Ceph) faithfully copies a corrupted file. Keep real, dated backups (deployment guide).
If something goes wrong¶
restore-checkreports keys "unreadable": the currentSECRET_KEYis not the one the backup used. Restore the old key, or have people re-enter their keys.attempt to write a readonly databaseafter a restore: fix the owner (UID 10001) of the files in the volume.- The app will not start after restoring an older backup into an older version: restore into the version that made it, or newer.
Related pages¶
Sources¶
- Deployment guide: backups and restore; operator security guide, section 6.
- Design note 07 §4.15 and the security review; design note 01.
- SQLite: online backup API; GDPR, Article 20.
Not yet reviewed by a clinician. Facts and sources last checked 2026-10-06.