backupd
Restoring

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 .agekey file. 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.age file.
  • 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.age file 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.

PostgreSQL 17 and later
age -d -i backupd.agekey app.pgdump.zst.age | zstd -d \
  | pg_restore --no-owner --single-transaction -d "$TARGET_URL"
PostgreSQL 9.2 to 16
# 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"
MySQL and MariaDB
age -d -i backupd.agekey app.sql.zst.age | zstd -d \
  | mysql --defaults-extra-file=target.cnf target_db
MongoDB
age -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.
Convex
age -d -i backupd.agekey app.zip.zst.age | zstd -d > snapshot.zip
npx convex import --replace-all snapshot.zip

For MySQL, target.cnf is an option file with the target's connection details, so the password stays off the command line:

target.cnf
[client]
host=db.example.com
user=restore
password=choose-a-strong-password
ssl-mode=REQUIRED

Check 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 | sha256sum

The 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.

On this page