Restore without backupd
Decrypt and restore any backupd backup with your own age key, zstd and each engine's standard tools. No backupd account needed.
Every backup is a standard dump, compressed with zstd and encrypted with age. Each file is encrypted to backupd's key for your workspace, so restores and restore tests work in the app, and to every key you add in Settings → Encryption. With your key and free tools you can restore a backup on your own machine, without backupd.
Add a key
In Settings → Encryption, click Add key:
-
Generate in browser: the key pair is created on your device. The secret key (
AGE-SECRET-KEY-1…) is shown once and can be saved with Download key file as a.agekeyfile. Only the public key (age1…) is sent to backupd. -
Use an existing key: paste a public key. To make one with the age tools:
age-keygen -o backupd.agekey # writes the secret key, prints the public key age-keygen -y backupd.agekey # prints the public key again
New backups are encrypted to the key from then on. See Encryption keys.
Get the file
- From the app: open a backup and click Download encrypted. You get a
.zst.agefile. - From your own bucket (Pro): backups are copied there byte for byte. See Your own bucket for the file layout. Each file has a
.manifest.agefile next to it with its table counts and checksums.
Install the tools
You need age, zstd, and the client tools for your engine: pg_restore and psql, mysql, mongorestore, or the Convex CLI (npx convex).
Restore
Decrypt with your key, decompress, and pipe the dump into the engine's restore tool. Nothing is written to disk in between, except for Convex.
Commands per engine
The examples use backupd.agekey for your secret key file and app.….zst.age for the downloaded backup.
age -d -i backupd.agekey app.pgdump.zst.age | zstd -d \
| pg_restore --no-owner --single-transaction -d "$TARGET_URL"# Backups are made with pg_dump 18, which sets transaction_timeout.
# Servers before 17 reject that setting, so filter it out and restore with psql.
age -d -i backupd.agekey app.pgdump.zst.age | zstd -d \
| pg_restore --no-owner -f - | grep -v '^SET transaction_timeout' \
| psql --single-transaction -v ON_ERROR_STOP=1 "$TARGET_URL"age -d -i backupd.agekey app.sql.zst.age | zstd -d \
| mysql --defaults-extra-file=target.cnf target_dbage -d -i backupd.agekey app.archive.zst.age | zstd -d \
| mongorestore --archive --uri "$TARGET_URI"
# Full-instance (replica set) backups include the oplog: add --oplogReplay.age -d -i backupd.agekey app.zip.zst.age | zstd -d > snapshot.zip
npx convex import --replace-all snapshot.zipFor MySQL, target.cnf is an option file with the target's connection details, so the password stays off the command line:
[client]
host=db.example.com
user=restore
password=choose-a-strong-password
ssl-mode=REQUIREDCheck a file before restoring
Each backup's manifest records the SHA-256 of the dump. Compare it with the dump you decrypt:
age -d -i backupd.agekey app.pgdump.zst.age.manifest.age | jq .plaintextSha256
age -d -i backupd.agekey app.pgdump.zst.age | zstd -d | sha256sumThe two values should match.
Revoking a key
Revoking a key stops new backups from being encrypted to it. Files already written keep it in their header, and their encryption can't be changed without rewriting them. To fully retire a key, revoke it and delete any copies that key should no longer open.