Restore a backup to a different provider
Restore a backup to a different provider
Most backup products can give you your data back on the platform you are already on.
That is the easy half, and it is the half that is useless in the situation people actually
fear: the account is suspended, the region is down, the provider has changed its terms, or
the card on file expired while you were on holiday.
A backup taken here restores to any PostgreSQL or MySQL database, on any host, in any
account. The provider it came from does not have to be reachable — or to still exist.
Restores are never metered. Not per restore, not per GB, not after a plan lapses. This
is deliberate: the moment you need your data back is the worst possible moment to meet a
billing wall.
What a restore needs
Two things:
- A completed backup run. Only completed runs are restorable — a run that failed or is
still in progress cannot be, and is not offered.
- A connection URL for an empty target database.
postgresql://user:password@host:5432/database
mysql://user:password@host:3306/database
That is the whole list. You do not need access to the source provider, its console, its
API, or the account it was billed to.
The target must be empty
A restore refuses a target that already contains tables, on both engines. This is a
guard, not a limitation — it exists because the alternative is loading a dump over a live
database, and there is no way to undo that.
So: create a fresh, empty database on the new host and point the restore at that. If you
are restoring over something intentionally, drop and recreate the database first, and be
certain, because you are removing the only other copy of whatever was there.
Postgres:
CREATE DATABASE app_restored;
MySQL:
CREATE DATABASE app_restored CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
Restore to the same engine the backup came from
A backup is a dump of a specific engine, so a PostgreSQL backup restores to PostgreSQL and
a MySQL backup restores to MySQL. Moving between engines is a conversion, not a restore —
see Convert MySQL to PostgreSQL.
Match the major version where you can, or restore to a newer one. Restoring into an
older major version than the source can fail on syntax the older server has never seen.
Extensions, on the new host
If the source used Postgres extensions — pgcrypto, postgis, citext, uuid-ossp —
install them on the target before restoring. A table whose column is typed by an
extension cannot be created on a database that does not have it, and the restore stops
there rather than producing a partial database and calling it done.
If you would rather not use our UI at all
You do not have to. Download the backup and load it yourself — the download is gzipped
plain SQL, which every Postgres and MySQL install can already read:
gunzip < backup.sql.gz | psql "$TARGET_URL"
gunzip < backup.sql.gz | mysql -h host -u user -p database
That is the point of the format. There is exactly one download shape, .sql.gz, and it was
chosen because it is universally loadable without our tooling. A backup you can only
restore through the vendor that took it is a backup with a hostage clause in it.
Downloads are decrypted copies and are transient — they expire automatically. The
durable, encrypted backup in your bucket is unaffected by a download expiring; take another
whenever you need one.
Verify, then cut over
When the restore finishes, check it yourself before repointing anything:
-- Postgres
SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public';
-- MySQL
SELECT count(*) FROM information_schema.tables WHERE table_schema = DATABASE();
Then count the table you care about most and open its newest row. A restore verifies
structurally; only you know what the data should say.
Doing this before you need it
The restore path is the one thing in a backup product that must not be first exercised
during an incident. Restore a backup into a throwaway database once, deliberately, while
nothing is wrong. It costs nothing — restores are never metered — and it converts "we have
backups" into "we have restored one".