# Restoring a Backup

> Restore your database from a local backup, an offsite copy, or an uploaded dump file.

There are three ways to get data back into an app's database, all on the app's **Database** tab:

1. **From a local backup** - expand a backup that still has its file on the server and click the restore icon on its **Server** copy.
2. **From an offsite copy** - expand a backup with an offsite copy (marked **Server + Offsite** or **Offsite only**) and click the restore icon on its **Offsite** copy. Potions downloads the archive from your bucket onto the server first (and verifies its checksum), then restores it. Works even if the local file was pruned long ago.
3. **From an uploaded dump file** - drag a dump from your machine into the **Import Database** box. Handy for migrating into Potions or pulling in a copy of production data from elsewhere.

![An expanded backup row showing its Server and Offsite copies, each with restore, download, and delete actions](https://media.potions.io/docs/backup-row-restore-actions.png)

All three paths run the same battle-tested restore pipeline and require you to **type the database name to confirm** - a restore replaces everything in the database, and there's no undo. (A [SQLite](/docs/sqlite) app has no database name; you type the app's name instead.)

Not available for external databases: Potions doesn't restore into a database it doesn't manage, so use your provider's tools. See [External Databases](/docs/external-databases).

## What Happens During a Restore

The restore output streams live to the page, so you can watch each step:

1. Your app is **stopped** so nothing writes mid-restore.
2. Open database connections are terminated.
3. The schema is dropped and recreated - the database is now empty.
4. The dump is restored with `pg_restore` or `psql`, depending on its format.
5. The result is verified and `ANALYZE` runs so the query planner has fresh statistics.
6. Your app is **restarted** - this happens even if the restore fails, so a bad dump doesn't leave you down.

Expect a short outage for the duration - seconds for small databases, longer for big ones.

## Supported Dump Formats

| Format | Extension | Created with |
| --- | --- | --- |
| Custom (recommended) | `.dump`, `.backup` | `pg_dump -Fc dbname > dbname.dump` |
| Plain SQL | `.sql` | `pg_dump dbname > dbname.sql` |
| Gzipped SQL | `.sql.gz` | `pg_dump dbname \| gzip > dbname.sql.gz` |

Potions' own backups are gzipped SQL, so anything created by [automated backups](/docs/automated-backups) restores without ceremony. Uploaded files are capped at 500 MB - for anything bigger, restore from an offsite copy instead (no size limit, and the transfer happens server-to-bucket rather than through your browser).

### SQLite apps

A SQLite app restores a file, not a dump, so the steps above become: snapshot the current file, stop the app and its replication service, build the new file, check it with `PRAGMA quick_check`, swap it in, restart the app and start a new replica generation. Accepted uploads:

| Format | Extension | Created with |
| --- | --- | --- |
| Database file | `.sqlite3`, `.db`, `.sqlite`, optionally `.gz` | Potions' own snapshots, a copy of the file, or `VACUUM INTO` |
| SQL script | `.sql`, `.sql.gz` | `sqlite3 app.sqlite3 .dump` |

A lone `-wal` or `-shm` file is refused. For restoring to a moment in time rather than to a snapshot, see [SQLite and Litestream](/docs/sqlite#restore-to-a-point-in-time).

## Restoring From Offsite

Offsite restores add two safety steps before the pipeline above: the archive is downloaded onto your server with a presigned URL (after a free-disk-space check), and its **SHA-256 checksum** is verified against the one recorded at upload time. A corrupted or tampered file is rejected before it ever touches your database.

This is also your disaster-recovery path: if a server is lost, create a new one, deploy your app, and restore the offsite copy into it. See [Offsite Backups](/docs/offsite-backups) for setup.

## Troubleshooting

- **The restore buttons are greyed out** - another operation is in flight. Restores can't start while a backup, import, or reset is running; wait for it to finish.
- **"The backup destination was removed"** - the offsite copy's destination was disconnected, so Potions can no longer fetch it. The archive is still in your bucket: reconnect the destination, or download the file yourself and use the upload path.
- **"Downloaded file failed checksum verification"** - the transfer was corrupted or the object was modified in the bucket. Retry; if it persists, the object itself is damaged - fall back to an older copy.
- **The restore log shows errors** - exit-code warnings about ownership or privileges are normal and harmless (dumps from other machines reference roles that don't exist on yours). Real failures - syntax errors, truncated files - mark the restore failed; check the log, then restore an older backup.
