First: Zeabur has built-in backups, but they may not be enough
According to Zeabur’s docs, you open a database service’s Backup tab, click Backup, and after a few minutes the file is ready to download. This needs a paid plan (Dev Plan and above), and backups are kept for 7 days before being deleted automatically.
| Database | Format of Zeabur’s built-in backup | Restore with |
|---|---|---|
| PostgreSQL | .sql from pg_dumpall, covering every database in the service | psql |
| MySQL / MariaDB | .sql, covering all non-system databases | mysql |
| MongoDB | Multiple .bson files | mongorestore |
Our advice: download the built-in backup, then take a second one yourself with the commands below. The reason is in the PostgreSQL section: the built-in format fails when you move to a managed database.
Where to find the connection details
According to Zeabur’s PostgreSQL template, a database service’s Instruction tab has a Public (External) section with the external Connection String and a connect command. The host looks like hkg1.clusters.zeabur.com, and the port is a number assigned by Zeabur.
Hostnames ending in zeabur.internal only work inside Zeabur; you cannot reach them from your own machine.
All commands below run in Docker, so you don’t need database tools installed locally.
PostgreSQL
1. Check the server version first
docker run --rm postgres:17 psql "postgresql://USER:PASS@HOST:PORT/DB" -tAc "show server_version"
pg_dump must be the same version as the server or newer. Backing up a version 16 server with pg_dump 15 aborts immediately:
pg_dump: error: aborting because of server version mismatch
pg_dump: detail: server version: 16.15 ...; pg_dump version: 15.19 ...
So replace the image tag below (postgres:16) with your server’s major version or newer.
2. Back up (custom format recommended)
docker run --rm -v "$PWD:/out" postgres:16 \
pg_dump "postgresql://USER:PASS@HOST:PORT/DB" -Fc -f /out/backup.dump
3. Restore into a managed database such as Neon or Supabase
A managed database does not give you a superuser account, and tables on Zeabur are owned by root. A plain restore fails with:
pg_restore: error: could not execute query: ERROR: role "root" does not exist
Adding --no-owner --no-acl avoids this. In our test: zero errors, correct row counts, and newly inserted ids continued the original sequence:
docker run --rm -v "$PWD:/out" postgres:17 \
pg_restore --no-owner --no-acl -d "NEW_DATABASE_CONNECTION_STRING" /out/backup.dump
Restoring Zeabur’s built-in data.sql
Zeabur’s docs say to unzip the backup and run:
psql -h HOST -p PORT -U USER postgres < data.sql
Our results with the same format (pg_dumpall):
- Into a self-managed PostgreSQL (you are superuser): works, data complete
- Into a managed database (not superuser): fails with
ERROR: permission denied to alter role. Usepg_dump -Fcabove instead
Security note:
data.sqlstarts with these two lines:
CREATE ROLE root;ALTER ROLE root WITH SUPERUSER ... PASSWORD 'SCRAM-SHA-256$...';In other words, after the restore the database password is back to your old Zeabur password. Given Zeabur’s security incidents, change it right away:
ALTER ROLE root WITH PASSWORD 'a-new-long-password';Zeabur’s docs also mention that to keep your current password, you can delete the
CREATE ROLEandALTER ROLElines fromdata.sqlbefore restoring.
Verify the backup (5 minutes, strongly recommended)
Start an empty local database, restore the backup into it, and check the tables and row counts:
docker network create restore-net
docker run -d --name restore-test --network restore-net -e POSTGRES_PASSWORD=x postgres:17
# wait a few seconds for the database to start
docker run --rm --network restore-net -v "$PWD:/out" postgres:17 \
pg_restore --no-owner --no-acl -d "postgresql://postgres:x@restore-test:5432/postgres" /out/backup.dump
docker exec restore-test psql -U postgres -tAc "select count(*) from your_table"
# clean up afterwards
docker rm -f restore-test && docker network rm restore-net
Why not publish a port with
-p 5433:5432? That is how our first test was written, and another local PostgreSQL happened to be listening on port 5433 of the test machine, so the backup was restored into that database instead. With a Docker network, the test database never exposes a port, so you can’t connect to the wrong one.
MySQL / MariaDB
# Backup: --single-transaction avoids locking tables; --routines --triggers includes stored procedures and triggers
docker run --rm -v "$PWD:/out" mysql:8 sh -c \
'mysqldump -h HOST -P PORT -u USER -pPASS --single-transaction --routines --triggers DB > /out/backup.sql'
# Restore
docker run --rm -v "$PWD:/out" mysql:8 sh -c 'mysql -h NEW_HOST -P NEW_PORT -u USER -pPASS DB < /out/backup.sql'
Tested: data, one stored procedure and one trigger all came across intact.
MongoDB
docker run --rm -v "$PWD:/out" mongo:7 \
mongodump --uri "mongodb://USER:PASS@HOST:PORT/?authSource=admin" --archive=/out/backup.archive --gzip
docker run --rm -v "$PWD:/out" mongo:7 \
mongorestore --uri "NEW_CONNECTION_STRING" --archive=/out/backup.archive --gzip
Test output: 50 document(s) restored successfully. 0 document(s) failed to restore.
Redis
Most people only use Redis as a cache, and then you don’t need to migrate it; the new Redis rebuilds the cache on its own. If you really store data in Redis (queues, leaderboards):
docker run --rm -v "$PWD:/out" redis:7 redis-cli -h HOST -p PORT -a PASS --rdb /out/dump.rdb
On success it prints Transfer finished with success. Put dump.rdb in the new Redis data directory (/data in the official image) and start it; it loads automatically. In our test the key count matched.
After restoring
- Switch to the new connection string (
DATABASE_URLor similar) and change the database password - Compare row counts on a few important tables
- Turn on daily automatic backups on the new platform, and actually download one to make sure you can