Move a Heroku Postgres database to RDS with pg_dump and pg_restore, check extensions and versions first, and cut over with a short write freeze.
Heroku Postgres is the part of a Heroku app that is hardest to move, because the web dynos can be redeployed anywhere in an afternoon while the database holds state that has to arrive complete and consistent. Amazon RDS for PostgreSQL is the usual target when the rest of the app is moving to AWS.
This guide covers the database move on its own: the pre-flight checks that decide whether the restore succeeds, the dump and restore commands, the cutover, and the queries to run before switching traffic. For moving the whole app, dynos, workers, Scheduler and config vars included, see How to migrate from Heroku to AWS.
Most failed restores trace back to three things, and all of them can be checked against the Heroku database before RDS exists.
Version. Run heroku pg:info -a your-app and note the PostgreSQL version. The RDS instance needs the same major version or a newer one, and the pg_restore binary you use must be at least as new as the server that produced the dump.
Extensions. List what the Heroku database uses:
-- run with: heroku pg:psql -a your-app
SELECT extname, extversion FROM pg_extension ORDER BY extname;
Compare the list with what RDS allows, either in the RDS extension list for your version or with SHOW rds.extensions; on the new instance. Common ones such as pgcrypto, uuid-ossp, citext, pg_trgm, hstore and postgis are available.
Ownership. Heroku databases are owned by a generated user that does not exist on RDS. Restore with --no-owner --no-acl so objects end up owned by the role you restore as, and grant access afterwards.
Also note the database size from pg:info. It decides whether a laptop is fine for the transfer or whether the dump should run from an EC2 instance in the RDS region.
Encore combines an open source infrastructure SDK with a platform that provisions and deploys into your own AWS or GCP account. The database is declared in application code, and Encore provisions RDS for it in your AWS account, in private subnets that only the app's compute can reach, with a database role per service. The Heroku data can arrive through either of the two options below.
Declare the database with the Heroku schema as the first migration. A schema-only dump gives you that file:
Review the file and remove anything specific to Heroku, such as COMMENT ON EXTENSION lines or grants to the Heroku user. Then declare the database in the service, in Go with sqldb.NewDatabase or in TypeScript with SQLDatabase:
package orders
import "encore.dev/storage/sqldb"
var db = sqldb.NewDatabase("orders", sqldb.DatabaseConfig{
Migrations: "./migrations",
})
encore run starts Postgres locally and applies the bootstrap migration, which is a quick test that the schema file is valid before it reaches AWS. Connect your AWS account in the Encore dashboard, create an environment on AWS and deploy. Encore creates an RDS for PostgreSQL instance, by default on PostgreSQL 18.3 with Multi-AZ and seven days of backups as listed in the AWS infrastructure docs, and applies the migration.
After the deploy has created the schema, restore the data alone. encore db conn-uri prints a temporary connection string for a cloud environment:
If you want to control the instance settings up front, create RDS for PostgreSQL in a VPC, restore the full dump into it, and choose Import Existing Database when you create the Encore environment, as described in Import an existing RDS or Aurora database. Encore adds the security group rule, sets up VPC peering if the database lives in another VPC, creates its roles through the master user, and never deletes the instance. Add the same bootstrap migration to the repo and mark it as applied on the imported database, so new environments get the schema while production is left as it is:
CREATE TABLE IF NOT EXISTS schema_migrations (version bigint NOT NULL, dirty boolean NOT NULL);
INSERT INTO schema_migrations (version, dirty) VALUES (1, false);
Either way, the next schema change is a new file such as 2_add_refunds.up.sql, applied on deploy. Preview environments get their own database built from the same migrations, which gives a place to test the next change against the migrated schema. The Heroku migration guide covers the rest of the app.
Best fit: teams moving the whole Heroku app to their own AWS account who want RDS, the network around it and the database roles set up from the code that uses the database.
Without Encore, you create the RDS instance, a DB subnet group in private subnets, a security group that admits the app and the machine doing the restore, and a parameter group if you need non-default settings. The transfer follows Heroku's import and export guide:
For larger databases, skip the backup file and dump in directory format with parallel jobs from an EC2 instance in the RDS region, then restore with the same parallelism:
After the restore, create an application role with only the privileges the app needs instead of connecting as the master user, store its password in Secrets Manager, and point your migration tool at the new database.
Best fit: teams with an existing Terraform setup for VPCs and RDS who want every setting in their own infrastructure code.
AWS Database Migration Service can copy the data and then keep replicating changes until you switch, which shrinks the write freeze to the final switch. Change capture depends on logical replication on the source, and Heroku manages the server settings and roles, so confirm that your Heroku Postgres plan allows it before planning around this route. DMS also does not copy secondary indexes, sequences' current values or most constraints the way pg_restore does, so the schema still comes from a schema-only dump.
Best fit: large databases where a dump and restore would freeze writes for longer than the business accepts, on a plan where the source supports the replication DMS needs.
A rehearsal against a copy tells you how long each step takes, and the production run is then:
heroku maintenance:on -a your-app and scale workers to zero, so nothing writes during the dump.Keep the Heroku database running and untouched for a few days, so rolling back means switching the connection string back.
Compare exact row counts for the tables that matter on both sides:
SELECT 'users' AS t, count(*) FROM users
UNION ALL SELECT 'orders', count(*) FROM orders
UNION ALL SELECT 'payments', count(*) FROM payments;
Check that sequences continued from the right values, since a sequence behind its table produces duplicate key errors on the first insert:
SELECT max(id) FROM orders;
SELECT last_value FROM orders_id_seq;
Then run ANALYZE; on RDS so the planner has statistics, because a freshly restored database has none and the first queries can pick slow plans.
Pick Encore when the app is moving to your own AWS account and you want RDS, its network and per-service roles created from the code, with new environments built from the same migrations. Pick RDS by hand when an infrastructure team already manages RDS in Terraform, and AWS DMS when the database is large enough that a dump and restore would freeze writes for too long.
Create an RDS for PostgreSQL instance on the same or a newer major version, check that every extension the Heroku database uses is available on RDS, take a backup with heroku pg:backups:capture, download it and restore it with pg_restore --no-owner --no-acl. For the final cutover, put the Heroku app in maintenance mode, take a fresh backup, restore it, verify row counts and point the app at RDS.
The write freeze lasts as long as the final dump plus the restore. Small databases of a few gigabytes usually move in minutes. For tens or hundreds of gigabytes, run pg_dump in directory format with parallel jobs from an EC2 instance in the RDS region, and rehearse the full run once to measure it before the production cutover.
Either let Encore provision RDS for the environment, commit the Heroku schema as the first migration and restore the data with pg_restore --data-only into the connection string from encore db conn-uri, or restore into an RDS instance you created and choose Import Existing Database when you create the Encore environment. In both cases Encore runs later migrations and gives each service its own database role.
Usually the connection string and TLS settings are the only changes. RDS requires a client that trusts the RDS certificate authority if you enforce verified TLS, and the database host is no longer public, so the app has to run inside the VPC or reach it through a peered network.
Deploy to your cloud on AWS and Google Cloud
without managing infrastructure
Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.