Restore Guide
Overview
backupctl extracts backup files from restic snapshots — it does not automatically import data into your database. This is a deliberate design decision: automatic imports risk overwriting production data, and every database engine has its own import nuances that require operator judgment.
The restore process depends on whether your backup is encrypted or unencrypted:
| Backup Type | Steps |
|---|---|
| Unencrypted | Restore from restic → Import to database |
| GPG-encrypted | Restore from restic → Decrypt with GPG → Import to database |
Command notation
All commands below use the backupctl CLI shortcut (installed via scripts/install-cli.sh). If you haven't installed it, prefix commands with docker exec backupctl node dist/cli.js instead.
Example: docker exec backupctl node dist/cli.js snapshots myproject instead of backupctl snapshots myproject
The --guide flag prints a config-aware restore guide tailored to your project's exact setup (database type, encryption status, GPG recipient).
Quick Restore
Unencrypted backup:
# Restore latest snapshot
backupctl restore myproject latest /tmp/restore
# Restore + decompress + show import guide
backupctl restore myproject latest /tmp/restore --only db --decompress --guideEncrypted backup:
# Step 1: Restore from restic (gets the .dump.gpg file)
backupctl restore myproject latest /tmp/restore --only db
# Step 2: Copy .gpg file to a machine that has the private key
docker cp backupctl:/tmp/restore/myproject_backup_20260320.dump.gpg ./
# Step 3: Decrypt with your private GPG key
gpg --decrypt myproject_backup_20260320.dump.gpg > myproject_backup_20260320.dump
# Step 4: Import to database
pg_restore -h localhost -p 5432 -U myuser -d mydb myproject_backup_20260320.dumpStep-by-Step: Unencrypted Backup
1. Find the snapshot
backupctl snapshots myproject --last 5Output:
Snapshots for myproject:
ID Time Tags
────────────────────────────────────────────────────────────────────────
35ba0d0439 2026-03-20T02:00:27Z project:myproject, db:postgres
aa4e3c4dae 2026-03-19T02:00:37Z project:myproject, db:postgresNote the snapshot ID (e.g., 35ba0d0439).
2. Restore from restic
backupctl restore myproject 35ba0d0439 /tmp/restore3. Check restored files
docker exec backupctl ls -la /tmp/restore/You should see a .dump file (PostgreSQL custom format, already compressed internally).
4. Import to database
# PostgreSQL
pg_restore -h <HOST> -p <PORT> -U <USER> -d <DBNAME> /tmp/restore/myproject_backup_20260320.dump
# MySQL
mysql -h <HOST> -P <PORT> -u <USER> -p <DBNAME> < /tmp/restore/myproject_backup_20260320.sql
# MongoDB
mongorestore --host <HOST> --port <PORT> -u <USER> -d <DBNAME> --gzip --archive=/tmp/restore/myproject_backup_20260320.archive5. Clean up
docker exec backupctl rm -rf /tmp/restoreStep-by-Step: Encrypted Backup (GPG)
When GPG encryption is enabled, the backup flow produces a .dump.gpg file instead of a plain .dump. The file is encrypted with the public key configured in your project. You need the corresponding private key to decrypt it.
Important
Never store the GPG private key on the backup server. The private key should remain on a secure machine (your workstation, a hardware key, or a dedicated restore server). This ensures that even if the backup server is compromised, the attacker cannot decrypt your backups.
1. Find the snapshot
backupctl snapshots myproject --last 52. Restore the encrypted dump from restic
backupctl restore myproject aa4e3c4dae /tmp/restore --only db3. Copy the encrypted file to your local machine
The .dump.gpg file needs to be decrypted on a machine that has the GPG private key:
# Copy from the container to the Docker host
docker cp backupctl:/tmp/restore/data/backups/myproject/myproject_backup_20260320.dump.gpg /tmp/
# If accessing a remote server, copy to your local machine
scp user@server:/tmp/myproject_backup_20260320.dump.gpg ./4. Decrypt with GPG
On the machine with the private key:
gpg --decrypt myproject_backup_20260320.dump.gpg > myproject_backup_20260320.dumpExpected output:
gpg: encrypted with rsa4096 key, ID CF7D15E776A1FD1E, created 2026-03-18
"Your Name <your@email.com>"5. Verify the decrypted dump
Before importing, verify the dump is valid and not corrupted:
# PostgreSQL: list table of contents
pg_restore --list myproject_backup_20260320.dump | head -20
# Check file size is reasonable
ls -lh myproject_backup_20260320.dump6. Verify integrity (optional but recommended)
Compare the SHA-256 checksum of the decrypted dump against the original (if you still have the pre-encryption .dump on the server):
# On the server
docker exec backupctl sha256sum /data/backups/myproject/myproject_backup_20260320.dump
# On your local machine
shasum -a 256 myproject_backup_20260320.dumpBoth checksums should be identical, confirming zero corruption through the encrypt → restic → restore → decrypt chain.
7. Import to database
pg_restore -h localhost -p 5432 -U myuser -d mydb myproject_backup_20260320.dump8. Clean up
# On your local machine
rm myproject_backup_20260320.dump myproject_backup_20260320.dump.gpg
# On the server
docker exec backupctl rm -rf /tmp/restoreUsing --guide for Config-Aware Instructions
The --guide flag generates restore instructions tailored to your project's configuration. It reads the project config and adjusts the steps based on whether encryption is enabled, which database type is configured, and which GPG recipient to use.
backupctl restore myproject latest /tmp/restore --guideExample output for an encrypted PostgreSQL project:
Restore Guide for myproject (postgres — mydb)
════════════════════════════════════════════════════════════
Step 1: Restore snapshot from Restic
backupctl restore myproject <SNAPSHOT_ID> <OUTPUT_PATH>
To find available snapshots:
backupctl snapshots myproject
Step 2: Decrypt the dump (GPG-encrypted)
gpg --decrypt <file>.dump.gpg > <file>.dump
Recipient: your@email.com
⚠ The private key must be available in your GPG keyring
Step 3: Restore to database
pg_restore -h <HOST> -p <PORT> -U <USER> -d mydb <file>.dump
────────────────────────────────────────────────────────────
Tip: Connection details are in your projects.yml config.
Tip: Never store the GPG private key on the backup server.Example output for an unencrypted project:
Restore Guide for myproject (postgres — mydb)
════════════════════════════════════════════════════════════
Step 1: Restore snapshot from Restic
backupctl restore myproject <SNAPSHOT_ID> <OUTPUT_PATH>
To find available snapshots:
backupctl snapshots myproject
Step 2: Restore to database
pg_restore -h <HOST> -p <PORT> -U <USER> -d mydb <file>.dump
────────────────────────────────────────────────────────────
Tip: Connection details are in your projects.yml config.Restore Options
--only db
Restore only the database dump file, skipping asset directories:
backupctl restore myproject latest /tmp/restore --only db--only assets
Restore only asset files, skipping the database dump:
backupctl restore myproject latest /tmp/restore --only assets--decompress
Automatically decompress (and decrypt, if applicable) files after restore:
backupctl restore myproject latest /tmp/restore --decompressCombine all options
backupctl restore myproject latest /tmp/restore --only db --decompress --guideDatabase Import Commands
PostgreSQL
Custom format (.dump) — the default:
# Restore into existing database (drops and recreates objects)
pg_restore -h localhost -p 5432 -U myuser -d mydb --clean --if-exists restored.dump
# Restore into a fresh database
pg_restore -h localhost -p 5432 -U myuser -d mydb --create restored.dump
# List contents without importing (useful for verification)
pg_restore --list restored.dumpMySQL
# Decompress first
gunzip restored.sql.gz
# Import
mysql -h localhost -P 3306 -u myuser -p mydb < restored.sql
# For large databases, disable FK checks for speed
mysql -h localhost -P 3306 -u myuser -p mydb \
-e "SET FOREIGN_KEY_CHECKS=0; SOURCE restored.sql; SET FOREIGN_KEY_CHECKS=1;"MongoDB
# From archive
mongorestore --host localhost --port 27017 -u myuser -d mydb \
--gzip --archive=restored.archive
# From directory
mongorestore --host localhost --port 27017 -u myuser -d mydb \
--gzip restored_directory/
# Drop existing data before restoring
mongorestore --host localhost --port 27017 -u myuser -d mydb \
--gzip --drop --archive=restored.archiveSelective Restore (Combined vs Separate Snapshots)
Combined mode (default)
Both database dump and assets in one snapshot. Use --only to filter:
backupctl restore myproject latest /tmp/restore --only db
backupctl restore myproject latest /tmp/restore --only assetsSeparate mode
Database and assets are in separate snapshots. Filter by tag using restic directly:
# List only DB snapshots
backupctl restic myproject snapshots --tag db
# List only asset snapshots
backupctl restic myproject snapshots --tag assetsThen restore the specific snapshot by ID.
Direct Restic Restore
For advanced scenarios beyond what the restore command provides:
# Restore with path filters
backupctl restic myproject restore <snapshot-id> \
--target /tmp/restore --include "/data/backups/myproject"
# Dump a single file to stdout
backupctl restic myproject dump latest \
/data/backups/myproject/myproject_backup_20260320.dump > restored.dump
# Diff two snapshots
backupctl restic myproject diff abc123 def456Safety Checklist
Before importing a restored dump into any database:
- Verify the target — confirm you are connected to the correct database host and name
- Back up the current state — take a fresh dump before overwriting anything
- Restore to staging first — if not urgent, verify in a staging environment first
- Check dump integrity — for PostgreSQL:
pg_restore --list restored.dump - Plan for downtime — large imports take time, schedule a maintenance window
- Communicate — notify your team before performing a production restore
Getting Help
- Restore not working? — Check Troubleshooting for common restore issues
- Need exact command syntax? — CLI Reference has full
restore,snapshots, andresticdetails - Quick commands — Cheatsheet for copy-paste restore commands
- Still stuck? — Report an issue on GitHub