Backups and restore drills
backupDeclare how much data you can lose and how fast you must restore, check your provider can meet it, and prove it with timed restore drills.
backup-restore-evidenceFree in your app, Apache-2.0Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.
Stability The promise an interface ships with.
- Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
- Beta Usable in production; may change before it becomes stable.
- Experimental May change or go away; needs an explicit opt-in in main.yaml.
backup turns "we have backups" into proof: declare your RPO and RTO, let mainsdk check test your provider against them, and record timed restore drills as signed evidence.
At a glance
backup:
rpo: 15m
rto: 1h
keep: 35d
drill: { every: 30d, to: scratch }import { runDrill, signDrill, drillKey } from "mainsdk/backup";
const result = await runDrill({ driver, target, profile, declared, at }); // restores into scratch, never production
const evidence = await signDrill(result, await drillKey());Drivers
6 drivers. Select one to see its line in main.yaml.
backup:
driver: localChange this one line to swap providers; your calls stay the same.Built-in and local drivers integration
Codes
| Code | Where it appears | Meaning |
|---|---|---|
MAIN-BACKUP-0001 | Checkmainsdk check, before deploy | The provider's best RPO (PITR step, else snapshot gap) is above backup.rpo. Fix: turn on PITR or raise backup.rpo |
MAIN-BACKUP-0002 | Checkmainsdk check, before deploy | The provider keeps restore points for less than backup.keep. Fix: raise the provider retention or lower backup.keep |
MAIN-BACKUP-0003 | Checkmainsdk check, before deploy | The last drill took longer than backup.rto. Fix: shrink the restore path or raise backup.rto to what a drill measured |
MAIN-BACKUP-0006 | Checkmainsdk check, before deploy | backup.rpo, backup.rto, backup.keep or backup.drill is missing or not a duration. Fix: set durations such as 15m, 1h, 35d |
MAIN-BACKUP-0004 | RuntimeError responses and logs | See the spec |
MAIN-BACKUP-0005 | RuntimeError responses and logs | See the spec |
Features
Nothing here has shipped yet. Each feature lands in its phase; the spec ticket builds it.
| Feature | Phase | Milestone | Spec ticket |
|---|---|---|---|
| Backup and point-in-time restore in the db and files contracts | Phase 3 | 1.2 | backup-restore-evidence#backup-capability |
| Provider backup settings read | Phase 3 | 1.2 | backup-restore-evidence#provider-readers |
| RPO, RTO and retention check | Phase 3 | 1.2 | backup-restore-evidence#rpo-rto-check |
| Timed restore drill into scratch | Phase 3 | 1.2 | backup-restore-evidence#drill-restore |
| Signed drill evidence | Phase 3 | 1.2 | backup-restore-evidence#drill-evidence |
Quick start
Declare your targets in main.yaml, then run a drill.
db:
driver: postgres
backup:
rpo: 15m
rto: 1h
keep: 35d
drill: { every: 30d, to: scratch }npx mainsdk drill restore --at "2027-07-01T10:00:00Z" --to scratch restored orders-db (Neon branch) in 6m 12s · RTO 1h · RPO 15m (PITR window 7d)
evidence signed: .mainsdk/evidence/drill-2027-07-14.jsonIn code, runDrill, signDrill, verifyDrill, backupIssues, drillControls and backupBadge from mainsdk/backup are the calls behind the command. A driver's optional snapshot() only asks your provider for a snapshot.
Configuration
Every key below lives under backup:.
backup:
rpo: 15m # duration: the most data you can lose
rto: 1h # duration: the longest time a restore may take
keep: 35d # duration: how long the provider keeps backups
drill:
every: 30d # how often a drill should pass
to: scratch # where drills restore; never production| Key | Type | What it does |
|---|---|---|
rpo | duration | Required. Checked against the provider's point-in-time window and snapshot frequency |
rto | duration | Required. A drill that takes longer fails RTO |
keep | duration | Retention you expect the provider to hold |
drill.every | duration | Drill cadence; the trust badge turns amber when no drill passed in 30 days |
drill.to | string | The restore target for drills, scratch in every example; never production |
The backup driver follows your data drivers: each db and files driver declares its backup capabilities (point-in-time window, snapshot frequency, off-site copy, object lock) in its capability profile. How you select a separate backup driver, such as pgBackRest or AWS Backup, is not specified yet.
Overlays by environment
Production usually needs tighter targets than staging. Put the difference in an environment overlay:
backup:
rpo: 5m
rto: 30mSee main.yaml for overlay order.
What you get
- A check that your database and file providers can meet the RPO, RTO and retention you declared.
- Timed restore drills that restore into a scratch environment, never production, and delete it afterwards.
- Signed drill evidence your auditors can verify, and a trust badge that turns amber when no drill passed in 30 days.
- No backups run or stored by MainSDK: your provider does that, and
backupexposes its backup and point-in-time restore through one interface.
The calls, all from mainsdk/backup:
| Call | What it does |
|---|---|
runDrill({ driver, target, profile, at }) | Restores the point at into scratch, times it and checks the rows, then deletes the target |
signDrill(result, key) | Signs a drill result as evidence |
verifyDrill(evidence, trusted) | Checks the signature and the trusted keys |
backupIssues(profile, declared, drill) | The findings behind MAIN-BACKUP-0001 to 0003: where the provider or the last drill misses your targets |
drillControls({ standard, evidence, now }) | The control results (A1.2, A1.3, CC7.5; 8.13, 5.30; PCI 12.10.1) that assure reads |
backupBadge({ evidence, now }) | The badge state: current, or amber when no drill passed in 30 days |
Examples
Check your provider meets your targets
mainsdk check reads the provider's backup settings and fails with the gap when they cannot meet your RPO and RTO.
backup:
rpo: 5m
rto: 1hnpx mainsdk check --jsonWith a provider that takes daily snapshots and has no point-in-time recovery, the check fails with a MAIN-BACKUP- finding that names the provider setting and the change needed, such as turning on point-in-time recovery.
Run a restore drill in CI
Drills are free to run locally and in your own CI. A drill restores into a new branch or database, times it, verifies row counts or checksums, and tears the scratch environment down.
name: restore-drill
on:
schedule:
- cron: "0 4 1 * *"
jobs:
drill:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- run: pnpm install --frozen-lockfile
- run: npx mainsdk drill restore --at "$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ)" --to scratchIf the restore takes 72 minutes against rto: 1h, the drill fails RTO and the evidence records the measured time. How a drill authenticates to your provider in CI is not specified yet.
Attach drill evidence to an audit
Each drill is signed evidence that mainsdk assure includes for SOC 2 (A1.2, A1.3, CC7.5), ISO 27001 (8.13, 5.30) and PCI DSS.
npx mainsdk assure --standard soc2 --jsonAfter a passing drill, A1.3 passes with the drill's signed evidence attached, and its signature verifies.
Errors and checks
When the provider's backup settings cannot meet the declared RPO or RTO, mainsdk check fails with a finding that names the setting to change: MAIN-BACKUP-0001 (RPO), MAIN-BACKUP-0002 (retention), MAIN-BACKUP-0003 (last drill over RTO) or MAIN-BACKUP-0006 (a target missing or not a duration). At run time, MAIN-BACKUP-0004 means a drill was asked to restore outside scratch, and MAIN-BACKUP-0005 means the provider returned no settings. Fix the named provider setting, or relax the target.
A failed drill (RTO missed, checksum mismatch) is recorded in the evidence file with the measured values. The launch report shows the trust badge amber with "restore drill overdue" when the last passing drill is older than 30 days.
Swapping drivers
Backups follow your data driver. Change the db driver and the backup capabilities come from the new driver's profile; your backup: block and drills stay the same.
db:
driver: neon # was postgres; point-in-time recovery comes from NeonTesting
Point-in-time windows and restores need a real provider, so the drill itself is your test: run mainsdk drill restore on a schedule in CI, as in the example above. Each db and files driver's declared backup capabilities are checked against a test project by its conformance suite; see the Testing kit.
Security and privacy
- MainSDK never holds backup copies or pays for storage; the provider does. A "backup now" call only asks the provider for a snapshot.
- Drills never restore into production, and the scratch environment is deleted at the end.
- Backups are erased for one person by deleting their key, through
keysandprivacy. - Drills are free locally and in your CI.
FAQ
Does MainSDK back up my database? No. Your provider does. MainSDK checks its settings, runs drills and keeps the evidence.
Why did my badge turn amber? No restore drill passed in the last 30 days. Run mainsdk drill restore.
Can a drill touch production? No. Drills restore only into a new branch or scratch database.
Next
- Data and records: the stores your backups cover.
- Encryption keys: crypto-erasure for backups.
- The check: how findings like
MAIN-BACKUP-are reported.
Was this page helpful?