Heroku announced in February 2026 that it is transitioning to a sustaining engineering model. Enterprise Account contracts are no longer offered to new customers. If you are on Heroku and scaling, the writing is on the wall.
We have migrated 10 applications from Heroku to Kubernetes over the past two years. Some were simple Rails apps. Others were multi-service architectures with PostgreSQL, Redis, and background workers. This is what we learned.
Why Teams Leave Heroku
Every migration we have done came down to one or more of these:
Cost. Heroku is frequently at least double the price of equivalent AWS resources. A production SaaS application we migrated saved 52% on on-demand AWS pricing and 68% with reserved instances. Heroku’s Eco plan starts at $5/dyno/month, but a real production app with multiple dynos, Heroku Postgres, Redis, and add-ons costs $500-2,000+/month for workloads that run on $150-600/month of Kubernetes infrastructure.
Scaling limitations. Heroku’s autoscaling is basic — it scales horizontally by adding dynos but cannot handle complex autoscaling policies. Kubernetes offers HPA, VPA, and cluster autoscaling with fine-grained control over resource allocation.
Architectural flexibility. Heroku works well for monoliths. When you need custom networking, service mesh, advanced security policies, or multi-region deployments, you hit walls fast.
Vendor lock-in. Heroku’s buildpacks, add-on ecosystem, and proprietary routing layer create deep dependencies. Moving to containers and Kubernetes gives you portability across AWS, Azure, and GCP.
The 5-Step Migration Process
Step 1: Inventory Everything (Day 1)
Before writing a single Dockerfile, audit your Heroku environment completely:
Heroku Inventory Checklist:
├── Apps
│ ├── Web dynos (type, count, size)
│ ├── Worker dynos (type, count, size)
│ └── Procfile commands
├── Add-ons
│ ├── Database (Heroku Postgres plan, size, connections)
│ ├── Cache (Heroku Redis plan, size)
│ ├── Search (Elasticsearch/Solr)
│ ├── Monitoring (New Relic, Scout, etc.)
│ └── Other (Sendgrid, Papertrail, etc.)
├── Config Vars
│ ├── Application secrets
│ ├── API keys
│ └── Database URLs
├── Deployment
│ ├── Git push or CI/CD pipeline?
│ ├── Review apps?
│ └── Pipelines (staging → production)?
└── DNS / SSL
├── Custom domains
└── SSL certificates
This inventory determines your migration complexity. A simple web app with Postgres takes 1-2 days. A multi-service architecture with 5+ add-ons takes 1-2 weeks.
Step 2: Containerise Your Application (Day 2-3)
Convert your Heroku Procfile into a Dockerfile. Heroku apps are already designed to run as processes, so this is usually straightforward:
Heroku Procfile:
web: bundle exec puma -C config/puma.rb
worker: bundle exec sidekiq -C config/sidekiq.yml
Dockerfile:
FROM ruby:3.3-slim
WORKDIR /app
# Install dependencies
COPY Gemfile Gemfile.lock ./
RUN bundle install --without development test
# Copy application
COPY . .
# Precompile assets
RUN bundle exec rails assets:precompile
EXPOSE 3000
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]
Key differences from Heroku:
- You manage your own base image and system dependencies
- Environment variables come from Kubernetes ConfigMaps and Secrets instead of Heroku Config Vars
- Each Procfile entry becomes a separate Kubernetes Deployment
- Port binding must be explicit (Heroku injects
$PORTautomatically)
Step 3: Set Up Kubernetes Infrastructure (Day 3-5)
We recommend managed Kubernetes — EKS, AKS, or GKE — unless you have a strong reason for self-managed. Use Terraform to provision:
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 20.0"
cluster_name = "my-app-production"
cluster_version = "1.31"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
eks_managed_node_groups = {
general = {
instance_types = ["t3.large"]
min_size = 2
max_size = 10
desired_size = 3
}
}
}
What you need beyond the cluster:
- Ingress controller — NGINX or AWS ALB Ingress for HTTP routing (replaces Heroku’s router)
- Cert-manager — automated TLS certificates (replaces Heroku ACM)
- External DNS — automated DNS management
- Container registry — ECR, GCR, or Docker Hub for your images
Step 4: Migrate the Database (Day 5-7)
This is the hardest part. Every one of our 10 migrations confirmed it.
For Heroku Postgres → AWS RDS:
- Create an RDS instance with matching specs
- Use
pg_dumpandpg_restorefor small databases (< 50GB) - For larger databases, use AWS DMS for continuous replication with minimal downtime
- Test the restored database thoroughly before switching
# Simple migration for smaller databases
heroku pg:backups:capture -a my-app
heroku pg:backups:download -a my-app
# Restore to RDS
pg_restore --verbose --clean --no-acl --no-owner \
-h my-rds-endpoint.amazonaws.com \
-U myuser -d mydb latest.dump
Downtime considerations:
- Small databases (< 10GB): 15-30 minutes downtime with dump/restore
- Medium databases (10-100GB): Use DMS for near-zero downtime
- Large databases (100GB+): Plan for a maintenance window or use logical replication
For Heroku Redis → ElastiCache:
Redis is typically ephemeral cache data, so migration is simpler — deploy ElastiCache, update the connection string, and let the cache rebuild.
Step 5: Deploy and Cut Over (Day 7-10)
Write Kubernetes manifests for each process in your Procfile:
# web-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels:
app: my-app
component: web
template:
metadata:
labels:
app: my-app
component: web
spec:
containers:
- name: web
image: 123456789.dkr.ecr.eu-west-1.amazonaws.com/my-app:latest
ports:
- containerPort: 3000
envFrom:
- secretRef:
name: app-secrets
- configMapRef:
name: app-config
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
memory: 1Gi
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
---
# worker-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: worker
spec:
replicas: 2
selector:
matchLabels:
app: my-app
component: worker
template:
metadata:
labels:
app: my-app
component: worker
spec:
containers:
- name: worker
image: 123456789.dkr.ecr.eu-west-1.amazonaws.com/my-app:latest
command: ["bundle", "exec", "sidekiq", "-C", "config/sidekiq.yml"]
envFrom:
- secretRef:
name: app-secrets
Cutover plan:
- Run both Heroku and Kubernetes in parallel for 24-48 hours
- Route a small percentage of traffic to Kubernetes using DNS weighted routing
- Monitor error rates, latency, and resource usage
- Gradually shift 100% of traffic to Kubernetes
- Keep Heroku running for 1 week as a rollback option
- Decommission Heroku
Cost Comparison: Real Numbers
From one of our migrations — a mid-size SaaS application:
| Component | Heroku (Monthly) | Kubernetes/AWS (Monthly) | Savings |
|---|---|---|---|
| Web (3 dynos) | $150 (Standard-2x) | $65 (t3.large shared) | 57% |
| Worker (2 dynos) | $100 (Standard-2x) | $45 (t3.large shared) | 55% |
| PostgreSQL | $200 (Standard-0) | $85 (db.t3.medium RDS) | 58% |
| Redis | $30 (Premium-0) | $15 (cache.t3.micro) | 50% |
| Monitoring | $50 (New Relic add-on) | $0 (Prometheus) | 100% |
| SSL/DNS | $0 (included) | $0 (cert-manager) | — |
| Total | $530/mo | $210/mo | 60% |
The savings increase as you scale. At 10x the load, Heroku costs scale linearly while Kubernetes costs scale sub-linearly thanks to bin-packing and autoscaling.
The 3 Migrations That Got Complicated
Migration #4: Heroku CI with Review Apps
The client relied heavily on Heroku Review Apps — automatic preview environments for every pull request. Replicating this in Kubernetes required setting up ArgoCD with ApplicationSets that create temporary namespaces per PR. It works well but took an extra week to set up.
Migration #7: 200GB PostgreSQL Database
A 200GB Heroku Postgres database could not tolerate more than 5 minutes of downtime. We used AWS DMS with continuous replication, ran both databases in sync for 48 hours, then cut over during a 3-minute maintenance window at 2 AM.
Migration #9: 15 Heroku Add-ons
The client used 15 different Heroku add-ons — Papertrail, Sendgrid, New Relic, Memcachier, Bonsai Elasticsearch, and more. Each add-on needed a Kubernetes-native replacement. This turned a 1-week migration into a 3-week project.
What You Gain (Beyond Cost Savings)
After migrating, every client gained:
- Autoscaling — HPA and Karpenter scale based on actual CPU/memory/custom metrics
- Observability — Prometheus + Grafana provide deeper visibility than any Heroku add-on
- Security — RBAC, network policies, and secrets management with fine-grained control
- Multi-environment — namespaces for dev, staging, production without separate Heroku pipelines
- Portability — move between cloud providers without rewriting your deployment
Should You Migrate?
Migrate if:
- Monthly Heroku bill exceeds $500 and growing
- You need custom networking, security policies, or multi-region
- Your team has or is willing to build Kubernetes knowledge
- You are scaling beyond what Heroku’s autoscaling handles well
Stay on Heroku if:
- You have a small team (1-3 developers) and no dedicated ops
- Your app is simple (1 web dyno, 1 database) and costs < $100/month
- Developer velocity matters more than infrastructure cost right now
- You do not want to manage infrastructure at all
Consider a middle ground:
- Railway, Render, or Fly.io offer Heroku-like simplicity with better pricing
- These work well for teams that want to leave Heroku but are not ready for Kubernetes
Ready to Migrate Off Heroku?
We have migrated 10 Heroku applications to Kubernetes — from simple Rails apps to multi-service architectures with 200GB databases and 15+ add-ons.
Our Kubernetes consulting team handles the full migration:
- Migration assessment — inventory your Heroku setup and estimate timeline and cost savings
- Infrastructure setup — provision EKS, AKS, or GKE with Terraform
- Database migration — zero-downtime PostgreSQL migration using DMS or logical replication
- CI/CD pipeline — replace Heroku Pipelines with GitHub Actions or ArgoCD
- Monitoring — set up Prometheus and Grafana to replace Heroku add-ons
We typically deliver Heroku-to-Kubernetes migrations in 2-4 weeks with 50-70% cost savings.