Administering
Backup, restore and upgrading
A backup of a running instance that is actually consistent, a restore that checks everything before it writes anything, and an upgrade path with a way back.
The one rule
data.db
SQLite runs in write-ahead-log mode. The newest commits sit in data.db-wal until a checkpoint folds them in, and the editor saves every couple of seconds while anyone is editing. A copy of data.db alone is a consistent database of before every save still in the log: it opens, looks fine, and is missing recent work. A copy of the whole folder spans whatever happens while it runs, and a checkpoint in the middle can build a database that never existed.
Overpass's tests reproduce both failures. On an 80 MB database with a continuous writer, 4 of 23 plain copies were torn or behind the last commit, while all 23 snapshots from draughtsman backup were whole and current. A copy taken with the server stopped is fine, and so is draughtsman backup, which does not need the server stopped.
Back up (SQLite, the default)
draughtsman backup --out /srv/backups/ # draughtsman-backup-<time>.zip (+ .sha256) in that folder
draughtsman backup --out /srv/backups/nightly.zip # an exact file name; never overwrites
docker exec draughtsman draughtsman backup --out /data/backups/ # in the Docker image
Run against this documentation's instance while the server was up and the editor open (paths shortened):
Backup written: /srv/draughtsman/backups/draughtsman-backup-20261001041514.zip
6 files, 0.0 MiB; 12 documents (1 in the bin), 3 users, 0 assets; schema 20260930233553_AddDocumentBin
sha256 90f8152c7432943c365ac7cb0c21e7c115f70a974c21682470637c1dfae95517 (also in /srv/draughtsman/backups/draughtsman-backup-20261001041514.zip.sha256)
The key ring (keys/) and AI settings (ai/) are not in it; add --include-secrets if they must survive a restore.
Exit code 0 is done, 1 refused or failed (nothing changed), 2 usage. The files are owner-only. Scheduling is yours (cron, a systemd timer, Task Scheduler, a CI job): run the command, keep the .sha256, and copy both off the machine. Check a bundle without restoring it with shasum -a 256 -c <file>.zip.sha256. Test a restore into a scratch folder at least once, before you need it.
In Docker, a backup written to /data/backups/ is on the same volume as the data, so it protects against a mistake and not against losing the volume. Mount a second folder for it, or copy it out straight away.
What is in a backup
| In the bundle | Not in it, by default |
|---|---|
| The database: every diagram (including those in the bin), versions and checkpoints, the audit trail, users (with Argon2id hashes), sessions, token hashes, the security log | keys/, the key ring that encrypts the stored AI key |
Uploaded images (assets/) | ai/ and email/, the stored AI and email settings including the encrypted API key and SMTP password |
draughtsman.yaml and the licence file | backups/ and logs/, and old pre-migration and pre-restore copies |
| Branding, organisation themes | |
manifest.json: version, time, provider, the newest applied migration, row counts, and the size and SHA-256 of every file |
- A restored instance has a new key ring and no AI key. The administrator enters the key again under Admin, AI. Sessions and tokens live in the database and carry over. Add
--include-secretsto keep the ring and the stored key; the file is then named…-SENSITIVE.zipand says so inside. Treat it like a password vault, and keep any key-encryption key in your secret store, not in the bundle. - Password-reset links never travel in a backup: the snapshot leaves that table's rows out, so a restore cannot revive a link someone was sent.
- The bundle holds password hashes. It is written owner-only; keep it access-controlled and copy it off the machine.
Restore
Stop the server, then restore into an empty data folder:
draughtsman restore /srv/backups/draughtsman-backup-20261001041514.zip --DRAUGHTSMAN_DATA=/var/lib/draughtsman
Restored 12 documents (1 in the bin), 3 users and 0 assets into /srv/draughtsman/data3 (backup made 2026-10-01 04:15:14Z by Draughtsman 0.1.0).
The bundle has no key ring or AI settings: start the server, then re-enter the AI API key under Admin > AI if one was configured. Sign-in sessions and API tokens, which live in the database, carry over.
Start the server against this folder. A backup from an older version is migrated on that first start, with its usual pre-migration copy.
It verifies everything before it writes anything: the bundle's checksum, the manifest, every file's size and SHA-256, that no unlisted or unsafe path is in the zip, that this version knows the bundle's schema, and that the database passes SQLite's integrity check and holds the manifest's document count. A bad bundle is refused and the data folder is left untouched. Into a folder that is not empty it refuses, and exits 1:
Restore refused: The data folder /srv/draughtsman/data3 is not empty (branding, data.db, themes). Nothing was changed. Restore into an empty folder (and point the server at it), or re-run with --overwrite to set the current database and files aside in a pre-restore-<time> folder first.
--overwrite moves the current database and files into pre-restore-<time>/ first, so nothing is deleted. A bundle from an older version restores fine and is migrated on first start. A bundle from a newer version is refused with the reason. The folder restore creates is not made private; create it first with chmod 700. In Docker:
docker run --rm -v /srv/draughtsman-data:/data -v /srv/backups:/backups:ro draughtsman:0.1.0 restore /backups/<bundle>.zip
We restored this documentation's backup into a fresh folder, started a server on it, signed in as the restored administrator and opened the restored diagrams.
Postgres
Draughtsman does not pretend to back Postgres up. On a Postgres instance draughtsman backup prints the pg_dump command to run, with the password left out, and exits 1:
This instance stores its documents in Postgres, which Draughtsman cannot back up for you (a consistent dump needs pg_dump and your credentials).
No backup was made. Run this, with the password in PGPASSWORD or ~/.pgpass (it is not printed here):
pg_dump --format=custom --no-owner --host 127.0.0.1 --port 55499 --username drafter --dbname diagrams --file draughtsman-20261001001152.dump
Then copy the rest of the data folder (assets/, branding/, themes/, draughtsman.yaml, licence.key; keys/ only if you accept a sensitive copy).
That output is from Overpass's check against a Postgres 17 container, where a dump restored with pg_restore --clean --if-exists --no-owner into an empty database. draughtsman.yaml holds the Postgres password, so protect it. Take a snapshot before every upgrade: when an upgrade has migrations to apply to an existing Postgres database, the server logs a warning with the command first; it cannot take the snapshot itself.
Upgrade
The whole procedure, with real output, the automatic rollback, the refusal of a newer database and the honest limits, is on Upgrading safely. In short:
- Back up (
draughtsman backup, orpg_dump), and read the changelog. - Run
draughtsman config checkwith the new program. It changes nothing and says what the upgrade would do. - Stop the old version, replace the program and not the data (unpack the new archive over or beside the old; for Docker, a new tag against the same volume), and start it.
- On SQLite the server first copies
data.dbtodata.db.pre-migration-<stamp>, proves the copy, migrates, checks the result and, if anything is wrong, puts the copy back and refuses to start. The newest three copies are kept. On Postgres it warns and goes ahead: thepg_dumpis your safety net. - Check: sign in, open a diagram, look at Status and the log for warnings.
Settings in draughtsman.yaml are not rewritten by an upgrade; a setting the new version adds takes its default. draughtsman.example.yaml in the new archive lists everything.
Going back
An older version refuses to open a newer database (it logs what it does not know, changes nothing, serves nothing and exits with code 3). To go back: stop the new version, restore what you took before the upgrade (draughtsman restore <bundle> --overwrite, or for SQLite put the data.db.pre-migration-<stamp> copy back after deleting data.db-wal and data.db-shm, or for Postgres empty the schema and restore the dump into it), and start the older version. A write-ahead log left behind by the migrated database would otherwise be replayed onto the restored file and silently return it to the migrated schema. Anything saved after the upgrade is lost in a downgrade. If the new version re-protected a stored key, copy ai/settings.json and email/settings.json back from backups/secrets-<time>/ first (details).
What the server keeps, and for how long
| Setting | Default | What it keeps |
|---|---|---|
retention.preMigrationCopies | 3 | Pre-migration copies of the SQLite database |
retention.checkpointsPerDocument | 50 | Automatic checkpoints per diagram; named versions are never pruned |
retention.expiredSessionDays | 7 | Ended sessions |
retention.binDays | 30 | Deleted diagrams in the bin; zero or less keeps them until someone deletes them for good |
assets.quotaBytes | 5 GiB | Total uploaded images; an upload past it is refused with 507 |
A daily pass, first run two minutes after start, does the pruning. Security events are kept indefinitely; there is no pruning job for them yet. An administrator can also download GET /api/admin/export, every diagram as JSON and DSL, as a way out in open formats (see Import and export); it is not a backup.