---
id: platform/postgres-transfer
title: Rehearse a Postgres database transfer
description: Run a disposable source-to-destination logical dump/restore, verify grants and sequences, then practice writer freeze and cutover.
keywords: [bex, postgres, migration, pg_dump, pg_restore, transfer, cutover]
last_updated: 2026-09-23
---

Move application data with a rehearsed logical transfer before you change
writers or DNS. Bex [Postgres](./postgres.md) connections preserve the TLS
settings returned at provision time — do not weaken verification to make a
migration command connect.

This page points at a downloadable two-database rehearsal you can run on local
disposable Postgres 16. It is not a hosted migration service and must never
target a customer database.

## What the sample rehearses

The [`postgres-transfer`](/examples/postgres-transfer/README.md) example:

1. Creates **owned** source and empty destination databases with related seed
   data and a least-privilege `transfer_app` role
2. Runs `pg_dump` / `pg_restore` (`--no-owner --no-acl`) with bounded lifetimes
   and refuses to promote a failed archive
3. Verifies rows, relationships, sequences, and that the app role can read/write
   while DDL stays denied
4. Shows why writes after an early dump are missing, stops the **sample** source
   writer, completes a final transfer, then enables destination writes once

Stopping the sample writer is not proof that every production writer is frozen.
After destination writes begin, switching traffic back to the source leaves
unreconciled data — decide how to reconcile before you need that path.

## Run the rehearsal

```bash
curl -fsSLO https://bex.co/examples/postgres-transfer-latest.zip
curl -fsSLO https://bex.co/examples/postgres-transfer-latest.zip.sha256
shasum -a 256 -c postgres-transfer-latest.zip.sha256
unzip postgres-transfer-latest.zip
cd postgres-transfer
npm install
export SOURCE_DATABASE_URL=postgres://transfer_admin:transfer_admin@127.0.0.1:55434/transfer_source_fixture
export TARGET_DATABASE_URL=postgres://transfer_admin:transfer_admin@127.0.0.1:55434/transfer_dest_fixture
export APP_DATABASE_URL=postgres://transfer_app:transfer_app@127.0.0.1:55434/transfer_dest_fixture
npm run setup
npm run rehearse
npm run cutover
npm test
npm run teardown -- --container
```

Requires Docker (`postgres:16-alpine`). Client tools come from the container when
`pg_dump` / `pg_restore` are not on `PATH`. Archives stay under `.rehearsal/` and
are not published with the downloadable tree.

## How this maps to a real migration

Use the same sequence described in [Migrate from Render](./migrate-from-render.md)
(and linked from [Migrate from Heroku](./migrate-from-heroku.md)): empty
destination, compatible majors, explicit role/grant recreation after
`--no-owner --no-acl`, write freeze of **all** producers, final restore, then
enable destination writers once.

## Related

- [Migrate from Render](./migrate-from-render.md)
- [Migrate from Heroku](./migrate-from-heroku.md)
- [Postgres](./postgres.md)
- [Postgres schema rehearsal](./postgres-schema-rollout.md)
- [Recovery — Postgres](./recovery-postgres.md)
