Migrating from Heroku Postgres to Fly Postgres: A Complete Guide

Hi everyone,

We recently successfully migrated away from Heroku Postgres to Fly Postgres. Here are the steps, start to finish:

  • Setup
  • Migrating Database
  • Updating Connection Options
  • Updating Deployment Migrations
  • Accessing locally
  • Setting up pg_stats
  • Setting up grafana
  • Setting up pg-hero


To create your postgres database, run the following command. Make sure you update all values appropriately. Keep the initial cluster at 1

fly pg create --name your_db --organization your-org --region your-region --vm-size your-vm-size --volume-size your-volume-size --initial-cluster-size 1

Once the operation is complete, attach this new database, under a different environment variable like FLY_DATABASE_URL to your running fly app.

The reason you’re setting up a different environment variable is because you don’t want it to affect your current app, but you still want the extras this command runs.

fly pg attach will create a new user, set up roles and return a database url when complete.

Make sure you save that database url!

fly pg attach --variable-name FLY_DATABASE_URL --database-name your-db -a your-app --postgres-app your-db

Next, the migration will require your user to have CREATE DB access since it’ll try to recreate the Heroku db name. You can decide to rename the database later.

# Connect to psql
fly pg connect --app your-app

# add CREATEDB permissions
alter user your-user WITH CREATEDB; # don't forget semi

# This confirms that your user has the role:

Migrating Database

Fly has a simple app you can run that will migrate your database over directly: postgres-migrator

Setting up the migrator app instructions are straight forward. There’s a PR as of 3/3/21 with some updated instructions.

You’ll have to set 2 environment variables:

  • SOURCE_DATABASE_URL your old Heroku postgres database
  • TARGET_DATABASE_URL your new Fly postgres database. You can leave off the database name and the dump will create it for you.

Once this is provisioned, you should be able to run the migration script:

fly ssh console -C "migrate" -a postgres-migration

The script can take anywhere from a couple minutes to hours depending on the size of your database. It’ll emit “All Done!” when it’s done. If you have less than 10gb of data, it should only take a couple minutes.

You might run into some errors. Most of the time, they should be ok. Make sure you look through the logs and confirm you don’t need to make a change.

If you have certain extension errors you need to install, we didn’t have to do that so you’re on your own.

Updating Connection Options

Since Heroku Postgres has a publicly-accessible URL, there have always been extra SSL settings you had to add to your ORM config or connection string.

If you had ?sslmode=true&pgsslmode=require in your connection string, remove it. There shouldn’t be any extra params.

If you had extra config params like ssl: true, rejectUnauthorized: false make sure you remove those too.

Fly Postgres doesn’t need SSL since its shielded from the public internet, which means all the fancy things Heroku requires us to do are no longer necessary.

Make sure you have a PR or deployment ready with these changes. We did a lot of testing before hand on staging databases.

Updating Deployment Migrations

We previously ran migrations by connecting to the publicly accessible database url and running the migrations command over the wire. We updated our deployment script by adding a deploy param to our fly.toml file:

  release_command = "./node_modules/typeorm/cli.js migration:run"

Accessing Locally

If you’re like me, you use something like Postico or another Postgres GUI. Similarly to your ORM config, you’ve had to add special settings and params to your URL to be able to connect. Those no longer work.

In order to connect to your Postgres database, you’ll have to start a fly proxy:

fly proxy 5432 -a <postgres-app-name>

If you like to use psql, you can use fly’s connect command:

fly postgres connect -a <postgres-app-name>

For the connection url, make sure you replace the hostname, something like @top2.internal.localhost.yourapp (something like that) and replace it with @localhost

The @internal.localhost hostname is internal to Fly and isn’t necessary when proxying.

You can read about other connection options here

Setting up pg-stats

pg_stats is where the metrics from Heroku Postgres came from. I’ll admit these commands look a little scary, but trust me, it all worked out fine!

Stolon is tool to manage high availability Postgres. You can learn more about it here https://github.com/sorintlab/stolon. I’ve never used or heard of this before so don’t feel bad if you haven’t either.

The breakdown of the commands are as follows:

  • Connect to your-db
  • export vars
  • Update stolon to include pg_stat_statements
  • Restart your postgres database (instant)
  • Verify that pg_stat_statements shows up under pgParameters
  • Run connect in the same console to connect to psql
  • Add the extension
fly ssh console -a your-db

export $(cat /data/.env | xargs)

stolonctl update --patch "{ \"pgParameters\" : {\"shared_preload_libraries\": \"pg_stat_statements\" } }"

# restarts postgres
su stolon -c "pg_ctl -D /data/postgres restart"

# verifies the pg params
stolonctl spec

CREATE EXTENSION pg_stat_statements

With this, you’ll be able to collect some really great metrics with Grafana and pg-hero

Setting up Grafana

Grafana is a cool-looking Dashboard. Fly has a 1st class integration via Prometheus. The metrics are fun to look at. Don’t go over board or you might under resource your app like I accidently did!

Setting up Grafana is straight forward, just follow these instructions provided by [fly/grafana](https://git

There’s a Postgres Dashboard @jerome sent to me. It was over Slack so I created a gist. Credit goes to him, not me!

Click “Raw” on the Gist, then go to the + in Grafana and import via URL. Viola, you have metrics.

You can learn more about Fly, Prometheus & Grafana here

Setting up pg-hero

pg-hero is a performance dashboard for Postgres. It’ll show you any connections, long running queries, suggestions & more

  • Create a fly.toml with the code snippet below
  • Set DATABASE_URL, PGHERO_USERNAME, PGHERO_PASSWORD as environment variables via fly secrets set
app = "pg-hero"

kill_signal = "SIGINT"
kill_timeout = 5
processes = []

  image = "ankane/pghero"

  PORT = "8080"

  allowed_public_ports = []
  auto_rollback = true

  http_checks = []
  internal_port = 8080
  processes = ["app"]
  protocol = "tcp"
  script_checks = []

    hard_limit = 25
    soft_limit = 20
    type = "connections"

    handlers = ["http"]
    port = 80

    handlers = ["tls", "http"]
    port = 443

    grace_period = "1s"
    interval = "15s"
    restart_limit = 0
    timeout = "2s"

You’ll also have to expose pg_stats to your pg-hero instance:

fly pg connect --app <pg-app-name>

grant pg_read_all_stats TO <whatever-user-pg-hero-is-using-to-connect>;

Don’t forget the semi!

Big thanks to @kurt, @shaun, @jerome, @dan.wetherald for helping us get here! This was a huge under-taking and everyone has been so helpful!