Backup, disaster recovery and continuity for self-hosted CMDash
Who this is for. Anyone running CMDash under a self-hosted license: on a Docker host (self-host.md) or in your own AWS account (self-host-aws.md). It sets out the backup and recovery policy that applies to your installation, and the commands that carry it out.
CMDash sets one disaster recovery and business continuity policy for its hosted service and for every licensed installation. On the hosted service CMDash carries it out. On a self-hosted installation you do, with the tools that ship with CMDash. This page is that policy, written for the operator of a self-hosted installation.
The policy
| Requirement | Minimum | How CMDash ships it |
|---|---|---|
| Back up the database, the object store, the configuration secrets and the license | Daily | Docker host: backup.sh and the cmdash-backup timer (daily at 03:15). Own AWS account: the stack's cmdash-backup-s3 timer (daily at 03:15), plus RDS automated backups on the production and GovCloud templates |
| Back up before every upgrade | Every upgrade | Run backup.sh first (Docker host). The in-app updater only applies releases that need no database migration; a release that does is an assisted upgrade, done after a backup |
| Keep restore points | At least 7 days of daily backups, and the backups taken before your last few upgrades | Docker host timer: 14 archives or 14 days (--keep-count 14 --keep-days 14). AWS template: BackupRetentionDays (default 30) |
| Encrypt every backup | Always, with a key you hold apart from the archives | Docker host: backup.sh refuses to write an unencrypted archive (age recipient or passphrase). AWS template: encrypted S3 bucket and encrypted RDS storage |
| Keep a copy off the host | Every backup, in a separate failure domain (another host, another bucket, another site) | Docker host: copy backups/ off the host yourself; the timer writes to local disk only. AWS template: the backups bucket is separate from the instance |
| Recovery point objective (RPO) | No more than 24 hours of data loss | Met by daily backups that run and are copied off the host |
| Recovery time objective (RTO) | Set your own from your business impact analysis, then prove it | The quarterly restore drill below measures it |
| Test a restore | At least once per quarter, and after any change to how you back up | The restore drill below |
| Write it down | Who runs a restore, where the keys are, and the date and result of each drill | Your own records, or the Continuity & recovery section of CMDash itself |
A backup that has never been restored is a belief, not a backup. The drill is part of the policy, not an extra.
What to back up
| Item | Why it matters | Docker host | Own AWS account |
|---|---|---|---|
| PostgreSQL database | All your data | In the backup.sh archive (pg_dump, checked with pg_restore --list before the archive is written) | postgres/<stamp>.dump in the backups bucket; RDS automated backups (production and GovCloud templates) |
| Object store | Evidence files, exports, reports | In the backup.sh archive (the MinIO bucket) | The objects bucket is versioned; it is not copied into the backups bucket |
cmdash.env and the generated secrets | CREDENTIAL_ENCRYPTION_KEY opens every stored integration credential. A database restored without it cannot use them | In the encrypted archive, and keep a second copy in your password manager | Eval template: copy app-secrets.env somewhere safe yourself. Production and GovCloud: held in AWS Secrets Manager |
License file (license.json) | The installation's entitlement | In the archive. Also keep the copy you were issued; while the installation is running, Download license.json on the portal's Licenses page retrieves the same file from it | license/<stamp>.tar.gz in the backups bucket |
| Backup encryption key and MAC key | Without them no backup can be read or trusted | Never with the archives. Offline or in your secrets manager, with at least two people able to reach it | Not applicable (AWS encrypts the buckets) |
| Proof-log ledger volume (if you use it) | A database restore must never roll it back | Excluded from backups on purpose; keep the volume itself on durable storage | Excluded on purpose |
Docker host
The commands below run in deploy/onprem/. The operator runbook that ships with CMDash
(docs/runbooks/onprem-install.md, sections 5, 6 and 9) has every option.
Set up the scheduled backup
-
Create the encryption key on a different machine and keep it there. With age:
age-keygen -o backup-identity.txt, then a MAC key:openssl rand -hex 32 > backup-mac.key. -
Put the public half on the host: set
CMDASH_BACKUP_AGE_RECIPIENT(orCMDASH_BACKUP_PASSPHRASE_FILE) incmdash.env. Without one, the timer fails every run by design and writes nothing. -
Install the timer:
cd deploy/onprem/systemd # Edit WorkingDirectory= and User= in cmdash-backup.service to match your install first. sudo cp cmdash-backup.service cmdash-backup.timer /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now cmdash-backup.timer systemctl list-timers cmdash-backup.timer # confirm it is armed -
Copy each new archive and its
.sha256and.macfiles frombackups/to off-host storage on the same daily cadence. Keep the MAC key away from that storage.
A backup missed while the host was off runs at the next boot (Persistent=true). A failed run fails the unit; check
it with systemctl status cmdash-backup and alert on it with your usual monitoring.
Restore
cd deploy/onprem
./restore.sh backups/cmdash-onprem-backup-<ts>.tar.gz.age --age-identity backup-identity.txt --mac-key-file backup-mac.key
# or, passphrase mode:
./restore.sh backups/cmdash-onprem-backup-<ts>.tar.gz.gpg --passphrase-file /root/.cmdash-backup-pass
Restore onto a stack that install.sh has already set up, running the same release the backup came from (or a newer
one). restore.sh checks the MAC and decrypts before it changes anything, so a wrong key or a tampered archive stops
early. It is destructive: it replaces the database and the object store, asks you to type RESTORE, re-applies the
schema and row-level security policies, and health-checks the api at the end. It writes the backed-up cmdash.env and
license to ./restored-secrets/ for you to review; it never overwrites your live configuration.
To rebuild a lost host: install CMDash on the new host with install.sh, put back cmdash.env and the license from
your secrets copy, then run restore.sh with the newest off-host archive.
Upgrades and rollback
- One-click update (Settings, Updates). The updater rolls the application one service at a time behind a health check and rolls back to your current version automatically if the new one fails it. It only offers releases that need no database migration.
- Manual upgrade. Back up, set
IMAGE_TAGto the new release incmdash.env, and re-runinstall.sh. Apply any SQL file the release notes name with./install.sh --pre-push-sql <file>. - Manual rollback. Set
IMAGE_TAGback to the previous release and re-runinstall.sh. If the release changed the database, restore the backup you took before the upgrade.
Your own AWS account
The CloudFormation stack runs the daily backup to its backups bucket and, on the production and GovCloud templates, keeps RDS automated backups for 7 days and a final snapshot when the stack is deleted. The restore steps, for both the eval and the RDS shapes, are in self-host-aws.md under "Backups", and upgrades and their rollback under "Upgrades". What remains yours:
- Copy the eval template's
app-secrets.envsomewhere safe. - Decide whether you need copies in a second region or account, and set them up (S3 replication, RDS snapshot copy).
- Run the restore drill.
The restore drill (once per quarter)
- On a scratch host or stack running the same release, start from a fresh install.
- Fetch the newest off-host archive and the decryption key from where each is kept. If one person cannot get both quickly, fix that first: that is the finding.
- Restore it, note the start and end times, and confirm: the health check passes, you can sign in, an integration that
stores a credential still connects, and the restored
cmdash.envcarries theCREDENTIAL_ENCRYPTION_KEYyou expect. - Run the restore again with a wrong key and confirm it refuses before changing anything.
- Record the date, the archive, who ran it and the measured time. That time is your tested RTO; compare it with your target.
- Delete the scratch install and
restored-secrets/.
CMDash's Continuity & recovery section can hold the record: a recovery register entry for CMDash with your declared RTO and RPO, and each drill's result as the tested value.
Who does what
| CMDash | You | |
|---|---|---|
| Backup and restore tooling, and its documentation | Yes | |
| Signed releases, upgrade path, automatic rollback of a failed one-click update | Yes | |
| Offline license verification, so an installation keeps running when CMDash's own services are unavailable | Yes | |
| Release notes that name any SQL or migration a release needs | Yes | |
| Running and scheduling backups, and alerting on a failed one | Yes | |
| Off-host and, if you need it, off-region copies | Yes | |
| Holding the encryption keys and the configuration secrets | Yes | |
| Quarterly restore drills and their records | Yes | |
| Your RTO and RPO targets, and your incident and continuity plans | Yes | |
| Host, network, operating system and storage availability | Yes | |
| Help with an assisted upgrade or a restore that fails | Yes, through support | Open the case |
Related
- self-host.md: installing on a Docker host.
- self-host-aws.md: installing in your own AWS account, its backups and upgrades.
deploy/onprem/README.md: the installer's reference, includingbackup.shandrestore.sh.docs/runbooks/onprem-install.md: the operator runbook (backup and restore, upgrade, scheduled backups).