Migrate from Heroku
This guide runs you through how to migrate a basic Elixir application off of Heroku and onto Fly.io. It assumes you’re running the following services on Heroku:
- Postgres database
- Custom domain
- Background worker, like Oban
If your application is running with more services, additional work may be needed to migrate your application off Heroku.
The steps below run you through the process of migrating your Phoenix app from Heroku to Fly.
From the root of the Elixir app you’re running on Heroku, run
fly launch and select the options to provision a new Postgres database.
When we run
fly launch from the newly-created project directory, the launcher will:
- Ask you to select a deployment region
- Set secrets required by Phoenix (
SECRET_KEY_BASE, for example)
- Run the Phoenix deployment setup task
- Optionally setup a Postgres instance in your selected region
- Deploy the application in your selected region
cd <app_name> fly launch
Creating app in /Users/me/<app_name> Scanning source code Detected a Phoenix app ? App Name (leave blank to use an auto-generated name): <app_name> ? Select organization: flyio (flyio) ? Select region: mad (Madrid, Spain) Created app <app_name> in organization soupedup Set secrets on <app_name>: SECRET_KEY_BASE Installing dependencies Running Docker release generator Wrote config file fly.toml ? Would you like to setup a Postgres database now? Yes Postgres cluster <app_name>-db created Username: postgres Password: <password> Hostname: <app_name>-db.internal Proxy Port: 5432 PG Port: 5433 Save your credentials in a secure place, you will not be able to see them again! Monitoring Deployment 1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 2 total, 2 passing] --> v0 deployed successfully Connect to postgres Any app within the flyio organization can connect to postgres using the above credentials and the hostname "<app_name>-db.internal." For example: postgres://postgres:password@<app_name>-db.internal:5432 See the postgres docs for more information on next steps, managing postgres, connecting from outside fly: https://fly.io/docs/reference/postgres/ Postgres cluster <app_name>-db is now attached to <app_name> Would you like to deploy now? Yes Deploying <app_name> ==> Validating app configuration --> Validating app configuration done Services TCP 80/443 ⇢ 8080 Remote builder fly-builder-little-glitter-8329 ready ...
That’s it! Run
fly apps open to see your deployed app in action.
Try a few other commands:
fly logs- Tail your application logs
fly status- App deployment details
fly status -a <app_name>-db- Database deployment details
fly deploy- Deploy the application after making changes
When that’s done, view your app in a browser:
fly apps open
There’s still work to be done to move more Heroku stuff over, so don’t worry if the app doesn’t boot right away. There’s a few commands that you’ll find useful to configure your environment:
fly logs- Read error messages and stack traces emitted by your application.
- Connect via IEx
To see all of your Heroku env vars and secrets, run:
heroku config -s | grep -v -e "DATABASE_URL" | fly secrets import
This command exports the Heroku secrets, excludes
DATABASE_URL and imports them into Fly.
Verify your Heroku secrets are in Fly.
fly secrets list NAME DIGEST CREATED AT DATABASE_URL 24e455edbfcf1247a642cdae30e14872 14m29s ago LANG 95a7bb7a8d0ee402edde95bb78ef95c7 1m24s ago MIX_ENV fd89784e59c72499525556f80289b2c7 1m26s ago SECRET_KEY_BASE 5afb43c2ddbba6c02ffa7e2834689692 1m22s ago
Any new data created by your Heroku app during this database migration won’t be moved over to Fly.io. Consider taking your Heroku application offline or place in read-only mode if you want to be confident that this migration will move over 100% of your Heroku data to Fly.io.
HEROKU_DATABASE_URL variable in your Fly.io environment.
fly secrets set HEROKU_DATABASE_URL=$(heroku config:get DATABASE_URL)
Alright, lets start the transfer remotely on the Fly.io instance.
fly ssh console
Then from the remote Fly SSH console transfer the database.
pg_dump -Fc --no-acl --no-owner -d $HEROKU_DATABASE_URL | pg_restore --verbose --clean --no-acl --no-owner -d $DATABASE_URL
You may need to upgrade your Heroku database to match the version of the source Fly.io database. Refer to Heroku’s Upgrading the Version of a Heroku Postgres Database for instructions on how to upgrade, then try the command above again.
After the database transfers unset the
fly secrets unset HEROKU_DATABASE_URL
Then launch your Heroku app to see if its running.
fly apps open
If you have a Redis server, there’s a good chance you need to set that up.
After you finish deploying your application to Fly.io and have tested it extensively, read through the Custom Domain docs and point your domain at it.
Old habits die hard, especially good habits like deploying frequently to production. Below is a quick overview of the differences you’ll notice initially between Fly.io and Heroku.
flyctl commands are a bit different than Heroku, but you’ll get use to them after a few days.
|Tail log files||
Check out the flyctl docs for a more extensive inventory of flyctl commands.
By default Heroku deployments are kicked off via the
git push heroku command. Fly.io works a bit differently by kicking of deployments via
fly deploy—git isn’t needed to deploy to Fly.io. The advantage to this approach is your git history will be clean and not full of commits like
git push heroku -am "make app work" or
git push heroku -m "ok it will really work this time".
To achieve the desired
git push behavior, we recommend setting up
fly deploy as the final command in your continuous integration pipeline, as outlined for GitHub in the Continuous Deployment with Fly.io and GitHub Actions docs.
Heroku’s default deployment technique is via
git push heroku. Fly.io doesn’t require a git commit, just run
fly deploy and the files on your local workstation will be deployed.
Fly.io can be configured to deploy on git commits with the following techniques with a GitHub Action.
Fly.io and Heroku have different Postgres database offerings. The most important distinction to understand about using Fly.io is that it automates provisioning, maintenance, and snapshot tasks for your Postgres database, but it does not manage it. If you run out of disk space, RAM, or other resources on your Fly Postgres instances, you’ll have to scale those virtual machines from the Fly CLI.
Contrast that with Heroku, which fully manages your database and includes an extensive suite of tools to provision, backup, snapshot, fork, patch, upgrade, and scale up/down your database resources.
The good news for people who want a highly managed Postgres database is they can continue hosting it at Heroku and point their Fly.io instances to it!
One command is all it takes to point Fly Apps at your Heroku managed database.
fly secrets set DATABASE_URL=$(heroku config:get DATABASE_URL)
This is a great way to get comfortable with Fly.io if you prefer a managed database provider. In the future if you decide you want to migrate your data to Fly, you can do so pretty easily with a few commands.
The most important thing you’ll want to be comfortable with using Fly.io’s database offering is backing up and restoring your database.
As your application grows, you’ll probably first scale disk and RAM resources, then scale out with multiple replicas. Common maintenance tasks will include upgrading Postgres as new versions are released with new features and security updates.
See here for a more comprehensive guide for what’s required when running your Postgres databases on Fly.io.
Heroku and Fly.io have very different pricing structures. You’ll want to read through the details on Fly.io’s pricing page before launching to production. The sections below serve as a rough comparison between Heroku’s and Fly.io’s plans as of August 2022.
Please do your own comparison of plans before switching from Heroku to Fly.io. The examples below are illustrative estimates between two very different offerings, which focuses on the costs of app & database servers. It does not represent the final costs of each plan. Also, the prices below may not be immediately updated if Fly.io or Heroku change prices.
Heroku will not offer free plans as of November 28, 2022.
Fly.io offers free usage for up to 3 full time VMs with 256MB of RAM, which is enough to run a Elixir app and Postgres database to get a feel for how Fly.io works.
Heroku’s Hobby tier is limited to 10,000 rows of data, which gets exceeded pretty quickly requiring the purchase of additional rows of data.
|App Dyno||512MB RAM||$7/mo|
Fly.io pricing is metered for the resources you use. Database is billed by the amount of RAM and disk space used, not by rows. The closest equivalent to the Heroku Hobby tier on Fly.io looks like this:
|App Server||1GB RAM||~$5.70/mo|
|Database Server||256MB RAM / 10Gb disk||~$3.44/mo|
There’s too many variables to compare Fly.io’s and Heroku’s pricing for larger Elixir applications depending on your needs, so you’ll definitely want to do your homework before migrating everything to Fly.io. This comparison focuses narrowly on the costs of app & database resources, and excludes other factors such as bandwidth costs, bundled support, etc.
|App Dyno||2.5GB RAM||$250/mo||8||$2,000/mo|
|Database||61GB RAM / 1TB disk||$2,500/mo||1||$2,500/mo|
Here’s roughly the equivalent resources on Fly:
|App Server||4GB RAM / 2X CPU||~$62.00/mo||8||~$496/mo|
|Database Server||64GB RAM / 500GB disk||~$633/mo||2||~$1,266/mo|
Again, the comparison isn’t realistic because it focuses only on application and database servers, but it does give you an idea of how the different cost structures scale on each platform. For example, Heroku’s database offering at this level is redundant, whereas Fly.io offers 2 database instances to achieve similar levels of redundancy.