Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
346 changes: 346 additions & 0 deletions getting-started/migrate-from-heroku.html.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,346 @@
---
title: Migrate from Heroku
layout: docs
nav: firecracker
toc: true
redirect_from: /docs/rails/getting-started/migrate-from-heroku/
---

<figure class="flex justify-center">
<img src="/static/images/migrate.png"
alt="Illustration by Annie Ruygt of a bird carrying an app to a balloon who is welcoming the app to join his group"
class="w-full max-w-lg mx-auto">
</figure>

This guide will walk you through migrating your Heroku app to Fly.io. Just follow the steps in order.

**What this guide covers:**
- Web apps in any language (Node.js, Python, Ruby, Go, etc.)
- Postgres databases
- Redis
- Background workers
- Environment variables
- Custom domains

**Time required:** Most apps can be migrated in under 30 minutes.

Unlike Heroku's dyno-based tiers, Fly.io uses pay-as-you-go pricing - you only pay for the compute, storage, and bandwidth you actually use. There are no fixed plan tiers to choose from. See [fly.io/pricing](https://fly.io/pricing) for current rates.

## TL;DR - The Fast Path

For experienced developers, here's the entire migration in 7 commands:

```bash
# 1. Export secrets (excluding Heroku-managed vars)
heroku config -s -a YOUR_HEROKU_APP | grep -v '^DATABASE_URL\|^REDIS_URL\|^HEROKU_' > secrets.txt
# ⚠️ Don't commit secrets.txt to git!

# 2. Create Fly app (generates Dockerfile automatically)
cd your-app-directory
fly launch --no-deploy

# 3. Create Managed Postgres and import data
fly mpg create --name your-db --region iad
# ⚠️ Replace CLUSTER_ID below with the cluster ID from the output above.
# If you missed it, run: fly mpg list
fly mpg proxy CLUSTER_ID --local-port 15432 &
pg_dump --no-owner --no-acl "$(heroku config:get DATABASE_URL -a YOUR_HEROKU_APP)" | \
PGPASSWORD=YOUR_FLY_DB_PASSWORD psql -h localhost -p 15432 -U fly-user -d fly-db
fly mpg attach CLUSTER_ID -a your-fly-app

# 4. Import secrets
cat secrets.txt | fly secrets import

# 5. Deploy
fly deploy

# 6. Verify
curl https://your-fly-app.fly.dev
```

For a more detailed explanation, read on.

## Prerequisites

Install both CLIs:

```bash
# Install Fly CLI
curl -fsSL https://fly.io/install.sh | sh

# Sign up / log in to Fly
fly auth signup # or: fly auth login

# Make sure you're logged into Heroku
heroku login
```

## Step 1: Export Your Heroku Configuration

First, let's capture everything about your Heroku app.

```bash
# Set your Heroku app name
export HEROKU_APP="your-app-name"

# Export environment variables (excluding Heroku-managed ones)
heroku config -s -a $HEROKU_APP | grep -v '^HEROKU_' | grep -v '^DATABASE_URL' | grep -v '^REDIS_URL' > heroku-env.txt
# ⚠️ This file contains secrets - don't commit it to git!

# See what add-ons you have
heroku addons -a $HEROKU_APP

# Check your current process types
heroku ps -a $HEROKU_APP
```

Take note of:
- Your add-ons (Postgres, Redis, etc.)
- Your dyno types (web, worker, etc.)

## Step 2: Prepare Your App

Make sure you have your app's source code locally:

```bash
# If you don't have it locally, clone from Heroku
heroku git:clone -a $HEROKU_APP
cd $HEROKU_APP
```

### Check for a Dockerfile

Fly works best with a Dockerfile. If you don't have one, that's fine - `fly launch` will generate one for you based on your app type.

### Check your Procfile

Your Heroku `Procfile` maps directly to Fly processes. A typical Procfile:

```procfile
web: npm start
worker: npm run worker
release: npm run db:migrate
```

## Step 3: Launch on Fly

From your app directory:

```bash
fly launch
```

This will:
1. Detect your app type (Node, Python, Rails, etc.)
2. Generate a Dockerfile if needed
3. Create a `fly.toml` configuration file
4. Ask if you want to provision a Postgres database

**When prompted:**
- Choose a name for your app (or accept the generated one)
- Select your preferred region
- Say **Yes** to Postgres if you need a database (choose "Managed Postgres" for the fully-managed option)
- Say **No** to deploy now - we'll do that after setting up secrets

## Step 4: Set Up Your Database

If you have Heroku Postgres, create a Managed Postgres cluster on Fly:

```bash
fly mpg create --name your-app-db --region iad
```

Save the connection details it outputs! You'll see something like:
```
Connection string: postgresql://fly-user:PASSWORD@pgbouncer.CLUSTER_ID.flympg.net/fly-db
```

Note the cluster ID (the `CLUSTER_ID` part) - you'll need it for the next few commands. If you lose it, you can always find it with `fly mpg list`.

To import your data, use `fly mpg proxy` to create a local tunnel, then `pg_dump` and `psql`:

```bash
# Terminal 1: Start a proxy to your Fly database (replace CLUSTER_ID with your cluster ID)
fly mpg proxy CLUSTER_ID --local-port 15432

# Terminal 2: Dump from Heroku and restore to Fly
export HEROKU_DB=$(heroku config:get DATABASE_URL -a $HEROKU_APP)

# Dump the database (plain SQL format)
pg_dump --no-owner --no-acl "$HEROKU_DB" > heroku-dump.sql

# Restore to Fly (via the proxy)
PGPASSWORD=YOUR_PASSWORD psql -h localhost -p 15432 -U fly-user -d fly-db -f heroku-dump.sql
```

You'll see some errors about Heroku-specific extensions (pg_stat_statements, event triggers) - these are safe to ignore. Your actual data will import successfully.

Attach the database to your app:

```bash
# Replace CLUSTER_ID with the cluster ID from fly mpg create (or run fly mpg list)
fly mpg attach CLUSTER_ID -a your-app-name
```

This automatically sets `DATABASE_URL` as a secret on your app.

## Step 5: Set Up Redis (if needed)

If you use Heroku Redis or Redis Cloud:

```bash
fly redis create
```

Follow the prompts. This uses Upstash Redis which is fully managed.

After creation, attach it to your app:

```bash
# The create command outputs a REDIS_URL - set it as a secret
fly secrets set REDIS_URL="redis://default:xxxxx@fly-your-redis.upstash.io:6379" -a your-app-name
```

**Note:** Fly Redis doesn't support importing existing data. For most use cases (caching, sessions), this is fine - the cache will warm up naturally.

## Step 6: Import Environment Variables

Remember that `heroku-env.txt` file we created? Let's import it:

```bash
# Import all your Heroku environment variables
cat heroku-env.txt | fly secrets import -a your-app-name
```

Double-check that secrets were imported:

```bash
fly secrets list -a your-app-name
```

## Step 7: Configure Processes (Web + Workers)

If your app has multiple process types (web + worker), edit your `fly.toml`:

```toml
[processes]
web = "npm start"
worker = "npm run worker"

# The web process gets the HTTP service
[http_service]
internal_port = 8080
processes = ["web"]
```

For a release command (like database migrations), add:

```toml
[deploy]
release_command = "npm run db:migrate"
```

## Step 8: Deploy

```bash
fly deploy
```

Watch the deployment. If something fails, check the logs:

```bash
fly logs
```

## Step 9: Verify Your App

```bash
# Open your app in a browser
fly apps open

# Check the status
fly status

# View live logs
fly logs
```

## Step 10: Migrate Your Domain

If you have a custom domain on Heroku:

```bash
# Add your domain to Fly
fly certs add yourdomain.com -a your-app-name

# Get the IP addresses to point your DNS to
fly ips list -a your-app-name
```

Update your DNS:
- For apex domains (example.com): Create an A record pointing to the IPv4 address
- For subdomains (www.example.com): Create a CNAME pointing to `your-app-name.fly.dev`

Once DNS propagates, Fly automatically provisions an SSL certificate.

## Quick Reference: Command Mapping

| Heroku | Fly |
|--------|-----|
| `heroku apps` | `fly apps list` |
| `heroku logs -t` | `fly logs` |
| `heroku ps` | `fly status` |
| `heroku config` | `fly secrets list` |
| `heroku config:set KEY=value` | `fly secrets set KEY=value` |
| `heroku run bash` | `fly ssh console` |
| `heroku run rails console` | `fly ssh console -C "rails console"` |
| `heroku pg:psql` | `fly mpg connect CLUSTER_ID` |
| `heroku restart` | `fly apps restart` |
| `heroku scale web=2` | `fly scale count web=2` |

## Common Add-on Replacements

| Heroku Add-on | Fly Equivalent |
|---------------|----------------|
| Heroku Postgres | `fly mpg create` (Managed Postgres) |
| Heroku Redis / Redis Cloud | `fly redis create` (Upstash) |
| Heroku Scheduler | Use cron in your app, or a separate "cron" process |
| Papertrail | `fly logs` built-in, or add a log drain |
| SendGrid | Keep using SendGrid (just use API key) |
| New Relic | `fly extensions sentry` or keep using New Relic |
| S3 / Bucketeer | `fly storage create` (Tigris) or keep using S3 |
| Elasticsearch | Deploy Elasticsearch as a Fly app, or use a hosted service |
| Memcached | Use Redis instead (`fly redis create`) |

## Multiple Environments (Staging/Production)

Fly doesn't have "pipelines" like Heroku. Instead, create separate apps:

```bash
# Production
fly launch --name myapp

# Staging
fly launch --name myapp-staging
```

For CI/CD, use GitHub Actions with the [Fly GitHub Action](/docs/launch/continuous-deployment-with-github-actions/).


## Shutting Down Heroku

Once everything is working on Fly:

1. Update your DNS to point to Fly
2. Wait for DNS propagation (check with `dig yourdomain.com`)
3. Monitor both apps for a day to ensure no traffic goes to Heroku
4. Scale down Heroku dynos: `heroku ps:scale web=0 worker=0 -a $HEROKU_APP`
5. Cancel Heroku add-ons
6. Delete the Heroku app: `heroku apps:destroy -a $HEROKU_APP`

## Getting Help

- [Fly Community](https://community.fly.io)
- [Fly Docs](https://fly.io/docs)
- [Fly Status](https://status.fly.io)

1 change: 1 addition & 0 deletions partials/_firecracker_nav.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
{ text: "Deep Dive Demo App", path: "/docs/deep-dive/" },
{ text: "Choose a Language or Framework", path: "/docs/getting-started/get-started-by-framework/" },
{ text: "Fly.io Essentials", path: "/docs/getting-started/essentials/" },
{ text: "Migrate from Heroku", path: "/docs/getting-started/migrate-from-heroku/" },
{ text: "Troubleshoot Deployments", path: "/docs/getting-started/troubleshooting/" }
]
},
Expand Down
Loading
Loading