How to Migrate from Heroku to AWS/Railway/Fly.io
How do I migrate from Heroku to AWS/Railway/Fly.io?
TL;DR
- Bottom line: Containerize your Heroku app with Docker, choose a target platform (AWS
ECS Fargate for full control, Railway for simplicity, Fly.io for edge deployment), migrate your database
with
pg_dump/pg_restore, and set up CI/CD. - Key tool/command:
docker build -t myapp . && docker push <registry>/myapp - Watch out for: Heroku's ephemeral filesystem — file writes fail on all platforms without S3/R2.
- Works with: Any Heroku app; targets: AWS ECS Fargate (platform v1.4.0+), Railway (Hobby/Pro), Fly.io (Machines).
Constraints
- AWS Fargate requires platform version 1.4.0+ (Linux) — all earlier versions were retired in 2024.
Always specify
platformVersion: "LATEST"in task definitions. [src7] - Heroku
DATABASE_URLincludessslmode=require— strip or remap SSL parameters when connecting to RDS, Railway, or Fly Postgres. [src1, src2] - Docker images must listen on
$PORT— all three platforms inject PORT at runtime; hardcoding a port causes health check failures. [src1, src4] - Never run
pg_restorewithout--no-owner --no-acl— Heroku role names don't exist on the target and will cause the restore to fail. [src2, src5] - Railway Hobby plan ($5/mo) is single-user only — teams must upgrade to Pro ($20/seat/mo). Egress and volume pricing were reduced in March 2025. [src3, src8]
- Fly.io persistent volumes are region-pinned — once created, a volume cannot be moved. Match database region to compute region. [src4]
Quick Reference
| Heroku Feature | AWS Equivalent | Railway | Fly.io |
|---|---|---|---|
| Web Dynos | ECS Fargate tasks | Railway services | Fly Machines |
| Worker Dynos | ECS tasks (separate) | Separate services | Process groups |
| Heroku Postgres | Amazon RDS | Railway Postgres | Fly Managed Postgres |
| Heroku Redis | ElastiCache | Railway Redis | Upstash Redis |
| Config Vars | SSM / Secrets Manager | Railway variables | fly secrets |
| Heroku Pipelines | CodePipeline + CodeBuild | Environments | fly deploy |
git push heroku |
Push to ECR → ECS | git push (auto) |
fly deploy |
| Procfile | ECS task definition | Procfile (native) | Procfile / fly.toml |
| Auto-scaling | ECS Service Auto Scaling | Automatic (vertical) | Machines auto-start/stop |
| Logging | CloudWatch Logs | Built-in dashboard | fly logs |
| SSL/TLS | ACM + ALB listener | Automatic (Let's Encrypt) | Automatic (Let's Encrypt) |
| Free tier | 12 months free + always-free | Hobby $5/mo ($5 credit) | Trial only (2 VM-hrs / 7 days) |
Decision Tree
START
+-- What matters most?
| +-- Full control + AWS -> AWS ECS Fargate [src1, src2]
| +-- Developer simplicity -> Railway [src3]
| +-- Global edge deploy -> Fly.io [src4]
+-- Team size?
| +-- Solo/small -> Railway or Fly.io (less ops)
| +-- DevOps team -> AWS (more powerful)
+-- Database size?
| +-- < 10 GB -> pg_dump/restore [src1]
| +-- > 10 GB -> AWS DMS live replication [src2]
+-- Downtime tolerance?
| +-- Zero downtime -> AWS DMS + blue-green deploy [src2, src6]
| +-- Maintenance window OK -> pg_dump/restore (any platform)
+-- DEFAULT -> Containerize first, then pick platform
Step-by-Step Guide
1. Containerize your app
Convert Procfile-based app to Docker. [src1, src5]
docker build -t myapp .
docker run -p 3000:3000 -e PORT=3000 myapp
2. Export Heroku configuration
Capture config vars, add-ons, and database info. [src1, src4]
heroku config -a myapp --shell > heroku_env.txt
heroku addons -a myapp
heroku pg:info -a myapp
3a. Migrate to AWS ECS Fargate
Full AWS ecosystem integration. Requires platform v1.4.0+. [src1, src2, src7]
# Push to ECR, create cluster, create service
aws ecr create-repository --repository-name myapp
docker push <account-id>.dkr.ecr.<region>.amazonaws.com/myapp:latest
aws ecs create-service --cluster myapp --service-name myapp \
--launch-type FARGATE --platform-version LATEST
3b. Migrate to Railway
Closest to Heroku's DX. Supports Procfile natively. [src3, src8]
railway init && railway link && railway up
3c. Migrate to Fly.io
Best for global edge. Fly.io retired its free Hobby/Launch/Scale plans in 2024 — new signups get a 2-VM-hour / 7-day trial, then pay-as-you-go. [src4]
fly launch && fly deploy
4. Migrate the database
Always use --no-owner --no-acl to avoid role mismatches. [src1, src2, src5]
pg_dump $(heroku config:get DATABASE_URL -a myapp) -Fc -f backup.dump
pg_restore -h target-host -d myapp --no-owner --no-acl backup.dump
5. Update DNS and cut over
Point domain to the new platform. Fly.io dedicated IPv4 costs $2/mo. [src1, src4]
# AWS: CNAME -> ALB
# Railway: railway domain add myapp.com
# Fly.io: fly certs add myapp.com
dig myapp.com # Verify propagation
Code Examples
Terraform: AWS ECS Fargate for Heroku migration
Full script: terraform-aws-ecs-fargate-infrastructure-for-herok.txt (59 lines)
resource "aws_ecs_task_definition" "app" {
family = "myapp"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = "256"
memory = "512"
container_definitions = jsonencode([{
name = "myapp"
image = "${aws_ecr_repository.app.repository_url}:latest"
portMappings = [{ containerPort = 3000 }]
}])
}
Bash: Complete Heroku-to-Railway migration
Full script: bash-complete-heroku-to-railway-migration-script.sh (32 lines)
#!/bin/bash
set -euo pipefail
HEROKU_APP="$1"
railway init && railway link
# ... (see full script)
GitHub Actions: CI/CD for ECS Fargate deployment
# Input: GitHub repo with Dockerfile
# Output: Auto-deploy to ECS on push to main
name: Deploy to ECS
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
- uses: aws-actions/amazon-ecr-login@v2
- run: docker build -t $ECR_REGISTRY/myapp:$GITHUB_SHA . && docker push ...
- uses: aws-actions/amazon-ecs-deploy-task-definition@v2
Anti-Patterns
Wrong: Storing files on local filesystem
// ❌ BAD — file disappears on restart
fs.writeFileSync('uploads/file.txt', data);
Correct: Use S3/R2 object storage
// ✅ GOOD — persistent across deploys [src1]
await s3.send(new PutObjectCommand({ Bucket: 'myapp', Key: 'file.txt', Body: data }));
Wrong: Hardcoding Heroku-specific env vars
# ❌ BAD — Heroku-managed variable won't exist
REDIS_URL="$HEROKU_REDIS_URL"
Correct: Platform-agnostic configuration
# ✅ GOOD — standard env vars, set per platform [src1, src3]
DATABASE_URL=$(aws ssm get-parameter --name /myapp/database-url --with-decryption --query Parameter.Value --output text)
Wrong: pg_restore without --no-owner
# ❌ BAD — Heroku roles don't exist on target
pg_restore -h rds-host -U postgres -d myapp backup.dump
# ERROR: role "uf3kxyz" does not exist
Correct: Strip ownership during restore
# ✅ GOOD — ignore Heroku-specific roles [src2, src5]
pg_restore -h rds-host -U postgres -d myapp --no-owner --no-acl backup.dump
Common Pitfalls
- Forgetting worker processes: Each Procfile process needs a separate service on the target platform. [src1]
- Database connection limits: Add RDS Proxy or PgBouncer — Heroku includes pooling by default. [src2, src5]
- SSL certificates: AWS requires ACM + ALB config; Railway and Fly.io auto-provision via Let's Encrypt. [src1]
- Scheduled jobs: Don't use cron inside containers — use platform-native schedulers (EventBridge, Railway cron, Fly Machines). [src1, src3]
- Log aggregation: AWS needs explicit CloudWatch setup. Set up log forwarding early. [src2]
- Build dependencies in runtime image: Use multi-stage Docker builds to keep images lean. [src5]
- Fly.io IPv4 billing: Dedicated public IPv4 costs $2/mo per app. Use shared IPv4 or IPv6 when possible. [src4]
- Railway egress costs: Reduced in March 2025, but high-bandwidth apps should monitor usage against included credits. [src8]
Diagnostic Commands
# Heroku: capture state before migration
heroku ps -a myapp && heroku config -a myapp --shell && heroku addons -a myapp
# AWS: verify
aws ecs describe-services --cluster myapp-cluster --services myapp-service
aws ecs list-tasks --cluster myapp-cluster
aws logs tail /ecs/myapp --follow
# Railway: verify
railway status && railway logs && railway domain list
# Fly.io: verify
fly status && fly logs && fly checks list
Version History & Compatibility
| Platform | Key Feature | Heroku Equivalent | Cost Model (2026) |
|---|---|---|---|
| AWS ECS Fargate | Serverless containers, full AWS ecosystem | Dynos + add-ons | Pay-per-vCPU-second (~50–70% cheaper at scale) |
| Railway | Git-push deploys, auto-scaling, project canvas | Almost identical to Heroku | Hobby $5/mo, Pro $20/seat/mo |
| Fly.io | Edge deployment, global distribution | Dynos + multi-region | Trial + pay-per-Machine-second |
| Render | Static sites + services | Heroku alternative | Free tier + pay-per-use |
When to Use / When Not to Use
| Migrate When | Don't Migrate When | Consider Instead |
|---|---|---|
| Costs exceed $500/mo | Simple app under $50/mo | Stay on Heroku Eco |
| Need auto-scaling beyond Heroku | No DevOps experience | Railway (Heroku-like) |
| Compliance requires specific cloud | Timeline < 2 weeks | Plan longer |
| Need VPC networking / private subnets | App is in prototype/MVP phase | Stay on Heroku or Railway |
| Need multi-region deployment | Single-region, low traffic | Any platform works |
Important Caveats
- Heroku entered "sustaining engineering" mode on 2026-02-06. Salesforce announced no new features and no new Enterprise contracts for new customers — engineering investment is shifting to Salesforce AI initiatives. Existing customers can continue and renew with no pricing/service changes, but the platform is no longer evolving. Teams planning infra for 2-5 years should factor this in. [src9]
- Heroku free tier removed Nov 2022 — Eco plan ($5/mo) replaced it. Fly.io also deprecated its Hobby/Launch/Scale free allowances in 2024; new signups now get a 2-VM-hour / 7-day trial before requiring a credit card. Railway's Hobby plan ($5/mo with $5 credit) remains the closest free-ish entry point. [src4, src8]
- AWS ECS Fargate: higher complexity but 50–70% cheaper at scale. Now supports custom container stop signals for graceful shutdown. [src2, src7]
- Railway scaling is vertical — add replicas manually for horizontal. Committed spend tiers available from $10,000/mo for dedicated hosts. [src3, src8]
- Fly.io: match database region to compute region. Volume snapshots became billable Jan 2026 ($0.08/GB/mo, first 10GB free). [src4]
- All platforms require explicit file storage migration — move from Heroku's ephemeral filesystem to S3, R2, or equivalent.