Backups and restore
Plugboard takes encrypted, scheduled backups of itself. Getting them off the machine, and having restored one, is yours.
The one rule
Section titled “The one rule”SECRETS_MASTER_KEY must be backed up somewhere that is not the server it
protects.
Connector credentials and the backups themselves are encrypted with it. A database restored without that key cannot decrypt its own secrets. Every integration would need to be reconfigured from scratch, assuming you still have the original credentials, and if you do not, some of them are gone.
Put a copy in a password manager. Not in a file next to the backup. Not in the same cloud account. Somewhere a total loss of the server does not also lose.
In-app backups
Section titled “In-app backups”Admin, Backups (/admin/backups), gated by the module.backups module and
the backup.manage permission.
From there you can:
- Schedule backups to run automatically, with a retention policy.
- Run one on demand.
- Verify a backup’s integrity, which reads it back and checks it decrypts and parses.
- Export a portable per-tenant copy, for moving or handing over an institution’s data.
If a scheduled backup fails, an alert is raised and, if email is configured, the
backup.failed message is sent. That message cannot be switched off.
Backups are encrypted, so they can be handed to your existing backup tooling and off-site storage without a second encryption layer and without worrying about who can read them at rest.
Full detail on the screen itself: backups in the app.
What a backup contains
Section titled “What a backup contains”| Included | Not included |
|---|---|
| Every database table for the tenant | The SECRETS_MASTER_KEY itself |
| Encrypted connector credentials | Uploaded files in object storage, if you use external S3 |
| Configuration, roles, workflow, templates | The application binaries |
| Audit log, within retention |
If you use external object storage for logos, photos and exports, back that bucket separately. If you are on the portable bundle, uploads live inside the install folder and are covered by copying it.
Getting them off the machine
Section titled “Getting them off the machine”Pick one and actually implement it. A backup that has only ever existed on the machine it protects is not a backup.
The whole folder, for the portable bundle. Everything is inside the install
directory, so a nightly file-level copy of C:\Plugboard or /opt/plugboard to
another host covers it. Stop the service first, or use a filesystem snapshot, so
the database files are consistent.
pg_dump, for a source or Docker install:
docker compose -f docker-compose.prod.yml exec postgres \ pg_dump -U plugboard -Fc plugboard > backups/plugboard-$(date +%F).dumpThen ship backups/ somewhere else with whatever you already use: rsync,
Veeam, restic, a cloud sync agent.
Your existing platform backup. If the machine is a VM in a hypervisor you already back up, that is a legitimate answer, provided you have tested restoring one and starting it.
Restoring
Section titled “Restoring”From a pg_dump archive
Section titled “From a pg_dump archive”# Stop the application first so nothing writes during the restore.docker compose -f docker-compose.prod.yml stop api web worker
docker compose -f docker-compose.prod.yml exec -T postgres \ pg_restore -U plugboard -d plugboard --clean --if-exists < backups/plugboard-2026-08-01.dump
docker compose -f docker-compose.prod.yml start api web workerRestore into a database whose schema matches the version of the application you are about to run. Restoring an older schema under a newer image will fail at startup, which is better than starting and behaving strangely.
The SECRETS_MASTER_KEY has to match
Section titled “The SECRETS_MASTER_KEY has to match”Set SECRETS_MASTER_KEY in .env to the value that was in use when the backup
was taken, before starting the application. If it does not match, the application
starts, the data is all there, and every connector fails to authenticate with a
decryption error. That symptom is confusing enough to be worth stating in
advance.
From a portable bundle folder
Section titled “From a portable bundle folder”Stop the service, replace the folder, start the service. The .env inside it
carries the matching key, which is exactly why the folder needs to be treated as
sensitive.
Prove it works
Section titled “Prove it works”CI runs a restore drill against PostgreSQL 16 on every push, so the product’s own backup format is exercised continuously. That proves the format. It does not prove your regime.
Once, before go-live:
- Take a backup.
- Restore it onto a different machine, or into a second database on the same one.
- Point a spare copy of the application at it, with the same
SECRETS_MASTER_KEY. - Sign in. Open a submission. Open Admin, Connectors and confirm a connector still tests successfully, which is what proves the secrets decrypted.
- Write down how long the whole thing took. That number is your real recovery time, and it is usually larger than people guess.
Then put a reminder in the calendar to do it again once a year, or after any change to how backups are taken.
Recovery objectives
Section titled “Recovery objectives”What you can promise depends on what you have configured, not on the software.
| Determined by | |
|---|---|
| Recovery point (how much you can lose) | Backup frequency. Nightly means up to a day |
| Recovery time (how long to be back) | How fast you can get the backup to a machine and start it |
For managed deployments, the objectives we commit to are in your agreement and backups are taken and tested for you. See support, restores and exits.
Retention
Section titled “Retention”Backup retention is set on the Backups screen. Audit log retention is separate and set under audit and retention, because the two answer to different policies: one is operational, the other is often a legal minimum.
What to do when a backup fails
Section titled “What to do when a backup fails”The alert tells you which run failed. The usual causes, in order of how often they turn out to be the answer:
- Disk full. Check free space on the volume holding
backups/. - Retention not pruning, so the disk filled. Check the retention policy is actually set to something.
- Permissions, after somebody changed the account the service runs as.
- The database was unreachable at that moment, usually because it was restarting.
Run one on demand from the Backups screen once you have fixed it, rather than waiting for the schedule to prove the fix.