Appearance
Backing up and restoring reportd
reportd backup writes everything reportd keeps to one archive; reportd restore puts it back. Both read the same config as the server (--config, or REPORTD_CONFIG, or config.yaml), so they find the stores wherever the config puts them.
sh
reportd backup --out reportd-2026-10-22.tar.gz
reportd backup --verify reportd-2026-10-22.tar.gz
reportd restore --from reportd-2026-10-22.tar.gzWhat a backup holds
| Part | From | In the archive |
|---|---|---|
| Templates | templateStore | templates/… (file store: drafts, edit logs, published copies, history, folders, jobs, assistant proposals and conversations, and the trash in .trash/), or templates.db (SQLite, copied with VACUUM INTO, trash included) |
| Reports | reportStore (file driver) | reports/…: index.json and the rendered files. --skip reports leaves them out. |
| Environments | sourceControl (git driver) | repo/…: the bare repository, every env/* and promote/* branch |
| Users and settings | dataDir, and on state.driver: postgres the database | data/auth.json (users, roles, API key hashes, MFA factors with sealed TOTP secrets; no sessions), audit.log and the rotated logs in audit/, approvals.json, jobs/, and the licence's files: installation-id, licence-history, licence-clock and licence (licence.md), usage/, the usage records and daily totals (admin.md). the administration site's settings.yaml (secrets in it are only secret: references), branding/ (the branding and its logo, favicon and background), font-sources.json (the font sources; a token only as a secret: reference) and template-sources.json (the template sources, likewise; template-sources.md). On postgres, auth.json and usage/ are read through the database, and so are the settings, as the layer reportd reads (settings.yaml with the schema's settings over it), the branding and the font and template sources; a restore puts them back there. Imported fonts are library files, in the repository. The template sources' clones, in dataDir/cache/, are left out: reportd clones them again. |
| Presets | dataDir from before format 7 | data/presets/, trash included, until the migration moves them into libraries (migrations.md). The backup reportd takes before migrating holds them, so restoring it brings them back, and the next start moves them again. |
| Manifest | manifest.json, last: the archive's format, each part's driver and format, and the size and SHA-256 of every file |
Left out on purpose:
- Sessions. They are bearer tokens. After a restore everyone signs in again; API keys keep working.
- The config and
secrets.dir. They can hold secrets; keep them in your secret manager or configuration management. The manifest records the drivers, so a restore can check it is going into the same kind of setup. - The viewer's link key (
dataDir/viewer-link.key), which reportd makes on first start whenviewer.linkSecretisn't set and signs report links with. A restore doesn't touch it, so restoring in place keeps links working. On a new machine reportd makes a new key, and links made before the move say they don't work; copy the file across (mode 0600) before the first start to keep them, or setviewer.linkSecret: the file holds the secret as text, so it can move intosecrets.dirassecret:file:viewer-link.key. - The MFA key (
dataDir/mfa.key), which reportd makes on first start whenauth.mfaKeyisn't set and seals the users' TOTP secrets with (mfa.md). A backup alone therefore can't make codes. A restore doesn't touch the file, so restoring in place keeps codes working. On a new machine, set the sameauth.mfaKey, or copy the file across (mode 0600) before the first start. Otherwise codes from authenticator apps fail there: users sign in with a recovery code or a passkey, or an administrator resets their MFA (DELETE /v1/users/{name}/mfa, orreportd mfa reset <user>while reportd is stopped). - The SAML SP key pair (
dataDir/saml-sp.pem), which reportd makes when SAML providers are configured andauth.saml.keyisn't set (sso.md). It signs reportd's sign-in requests and decrypts encrypted assertions, and each IdP pins its certificate. A restore doesn't touch it, so restoring in place keeps SAML sign-in working. On a new machine, copy the file across (mode 0600) before the first start, or setauth.saml.keyto the same key: the file is PEM, so it can move intosecrets.dirassecret:file:saml-sp.pem. Otherwise reportd makes a new key, and each IdP must import reportd's metadata again before SAML sign-ins work. - The viewer's spill folder, which reportd empties at every start.
- The short-lived sign-in state on
state.driver: postgres: MFA tickets, flow keys, the SAML replay cache, rate limit buckets and viewer sessions. They last minutes, and a restore makes everyone sign in again anyway.
The archive is readable only by its owner (mode 0600) and holds password hashes, API key hashes, recovery code hashes and every template: store it like a database dump, encrypted and off the machine.
While reportd runs
You can back up a running reportd. Each file reportd writes is replaced in one step, the SQLite store is copied with VACUUM INTO, report index entries are copied before the reports they name, and git refs before their objects, so the archive is crash-consistent: it holds what reportd would find after a power cut, which it recovers from (for instance, jobs that were running go back in the queue). It is not a snapshot of one instant across stores: a template published while the backup runs may be in the git part and not yet in the template store's history. For a backup that is exactly consistent, stop reportd first.
Schedule backups with cron, a systemd timer or Task Scheduler, for example nightly:
sh
reportd backup --config /etc/reportd/config.yaml --out /backups/reportd-$(date +%F).tar.gzRestoring
Stop reportd first: restore refuses while something answers on the config's listen address. It then:
- Reads the whole archive and checks every file against the manifest. A damaged, incomplete or altered archive, or one written by a newer reportd, is refused before anything is written.
- Checks the archive matches the config: the same template store driver (a SQLite backup doesn't go into a file store), a file report store if the archive has reports, and git source control if it has a repository.
- Refuses if any target already holds data, unless
--forceis given.--forcemoves each one aside to<path>.before-restore-<time>first; nothing is deleted. - Writes the files.
To move reportd to a new machine, install it, write its config (drivers as before; paths can differ), restore, and start it.
The trash comes back as it was in the archive, with each item's deletion date (trash.md). The sweep at the next start purges whatever is older than trash.retentionDays by then, so restoring an old backup can make room at once. A template restored with the backup that was purged since is back in the trash, and can be restored from there. Purged templates whose published versions you erased from git history are in older backups too: remove those when the data must go.
On state.driver postgres
The users, keys and roles are in the database (state.md). reportd backup reads them through it and writes them as data/auth.json, as a file install's backup has them; dataDir has no auth.json of its own. reportd restore writes them into the config's schema, which must have no users: it checks that before it writes anything, and --force doesn't change it. Restore into a new schema, or drop the old one first. The usage records and totals are read from the database into data/usage/ the same way, and restored into it rather than into dataDir. The audit_events table every node writes to isn't in the archive; the node's own audit log files are.
The database's own backups are yours: pg_dump --schema=<schema>, snapshots or point-in-time recovery. They hold the sessions too, as hashes.
reportd backup --verify <file> runs step 1 alone: use it to check archives after copying them.