Skip to content

From Fly.io

Tested with: Fly preset: offline fixtures (private-host detection, checklist) · zb migrate 0.1

Fly Postgres apps live on your organization’s private network. Databasezy cannot join it, so you either run the dump from your machine through fly proxy, or give the database a public address.

Fly.io migration at a glance, from the migration source catalogue
Status Coming soon · phase 2
Target engines PostgreSQL
How we read it Connection string, Local tools (zb migrate --from local)
Continuous sync Yes, for PostgreSQL
Provider preset fly
  • A Databasezy PostgreSQL instance, or use --create.
  • flyctl logged in to the organization, and pg_dump installed for local mode.

Use the credentials Fly printed when the Postgres app was created (or the app’s DATABASE_URL secret in the attached application). Hosts such as <app>.internal and <app>.flycast only resolve inside Fly.

  • Unmanaged Fly Postgres is not public by default; run zb migrate --from local through fly proxy.

The preset blocks .internal and .flycast hosts (private_endpoint, fly.private_network) and suggests the two paths below.

Local mode through fly proxy (copy only): zb runs pg_dump on your machine against the proxy and uploads the dump. The connection comes from the standard libpq variables.

shell
fly proxy 15432:5432 -a my-pg-app &
PGHOST=localhost PGPORT=15432 PGUSER=postgres PGPASSWORD=pw \
zb migrate --from local --engine postgres --db app --create

Public IP (copy or sync): allocate an address with fly ips allocate-v4 -a my-pg-app and migrate from the fly.dev host as below. Release the address after cutover.

  1. Open the target PostgreSQL instance (create one first if you need to) and choose the Migrate in tab.
  2. Under Source, pick Connection string and paste the URL. The host is recognised as Fly.io; you can also pick it from the provider list.
  3. Choose Copy and sync to keep the target current until cutover, or Copy for a one-off copy.
  4. Run Preflight and work through the checklist. Blocking items must be fixed before the copy starts.
  5. Start the copy and follow Copy and Verify. Mismatches are listed per table or collection.
  6. When the sync lag is low, stop writes and choose Cut over. The Report step keeps the timings and verification results.

For copy and sync, the source needs wal_level=logical and a role with REPLICATION; preflight checks both. After cutover, update the application’s secret (Fly platform guide). For Upstash-for-Fly Redis, use the rediss:// URL from fly redis status with --engine valkey; the Upstash guide applies.

Verification runs automatically after the copy and compares source and target: row counts per table, sequence or AUTO_INCREMENT values, the number of indexes and a schema hash. A missing table or a short row count fails the migration and lists what differs. Check the result with zb migrate status <mig_id> or the Verify step in the portal, then run your application’s own smoke tests against the new instance before you switch traffic.

With copy and sync, the target keeps receiving changes after the copy. When zb migrate status reports ready_for_cutover (two lag samples in a row under 5 seconds):

  1. Stop writes to the source: maintenance mode, scale writers to zero, or revoke the application user.
  2. Run zb migrate cutover <mig_id> (or Cut over in the portal). The agent drains the last changes, copies sequence values (PostgreSQL), verifies again and stops replication. If the target cannot catch up in time, the cutover is abandoned and sync keeps running; nothing changes on either side.
  3. Point the application at the Databasezy connection string and resume writes. zb migrate status shows the measured downtime.

Schema changes are not replicated, so freeze migrations between the copy and cutover. Details: what happens at cutover.

A migration never deletes anything on the source. Until you decommission it, rolling back means pointing the application back at the source.

  • Before cutover: cancel with zb migrate cancel <mig_id>. The publication, replication slot or replication channel the migration created is removed.
  • After cutover: writes made on Databasezy are not on the source. For PostgreSQL, start the migration with --reverse-sync so the old database keeps receiving them after cutover and stays a valid fallback. Without it, copy the new data back with a migration in the other direction before you switch.

Preflight blocks the host with fly.private_network

Section titled “Preflight blocks the host with fly.private_network”

Use local mode through fly proxy, or allocate a public IP.

Check that the source accepts connections from the egress addresses preflight prints, that the host is the public one and that the password has not been rotated. zb migrate --dry-run shows the exact URL that will be used after any rewrites.

Each item has a machine code and a fix. Nothing is written to the target until every blocking item is resolved; warnings do not block.

The source uses an extension Databasezy does not ship. Drop it on the source if it is unused, or exclude the objects that depend on it with --exclude.

Sync is blocked with pg.wal_level or pg.replica_identity

Section titled “Sync is blocked with pg.wal_level or pg.replica_identity”

Logical replication needs wal_level=logical on the source and a primary key (or REPLICA IDENTITY FULL) on every published table. Preflight lists the tables.

For anything else, zb migrate status <mig_id> shows the failing step and its message. How migrations work describes every step.