Moving from Heroku to Amazon Web Services (AWS) usually starts when the team needs more control, lower unit costs, stricter networking, or infrastructure patterns that Heroku no longer fits. The pressure often shows up at an awkward time: the product is growing, releases cannot slow down, and the original platform choices are now tied into deployments, databases, background jobs, secrets, logs, and incident response.
A good migration plan is less about picking AWS services first and more about mapping what Heroku currently does for you. Heroku hides a lot of operational detail. AWS gives you more control, but that control becomes risk if you do not make each responsibility explicit before cutover.
Start by mapping what Heroku is currently handling
Before choosing Elastic Container Service (ECS), Elastic Kubernetes Service (EKS), App Runner, or another compute target, document the current Heroku setup as it actually behaves in production.
At minimum, map these areas:
- Applications and process types: web dynos, worker dynos, schedulers, release tasks, one-off jobs, and admin commands.
- Runtime and build behavior: buildpacks, environment variables, language versions, dependency installation, asset compilation, and startup commands.
- Data services: Postgres, Redis, object storage, search, queues, third-party add-ons, backups, and retention needs.
- Networking: public endpoints, custom domains, Transport Layer Security (TLS), allowed inbound traffic, outbound dependencies, and any IP allowlists.
- Deployment workflow: Git-based deploys, review apps, pipeline promotion, rollback habits, database migration steps, and release approvals.
- Observability: logs, metrics, alerts, uptime checks, error tracking, tracing, and who gets paged when something breaks.
- Operational jobs: cron tasks, queue consumers, maintenance scripts, data exports, and manual commands engineers run during incidents.
This inventory prevents the common mistake of treating the migration as “containerize the app and run it somewhere.” Heroku is often acting as build system, process manager, secrets store, router, logging source, add-on marketplace, and release coordinator. AWS can cover all of that, but not automatically.
Choose the AWS target based on your operating model
The right AWS landing zone depends on how much infrastructure your team is ready to own. A small team with no platform engineer should be careful about jumping straight into a large Kubernetes setup unless there is a clear reason.
Common compute options
- AWS App Runner: A managed option for containerized web services. It can fit simpler apps where the team wants less infrastructure surface area.
- Amazon ECS with Fargate: A practical default for many Heroku migrations. You run containers without managing servers, and you can model web and worker processes clearly.
- Amazon EKS: Managed Kubernetes. It can make sense if you already have Kubernetes experience, multiple services, platform standards, or portability requirements. It also adds operational overhead.
- Elastic Beanstalk: A higher-level AWS platform for applications. It can work for teams that want AWS-native hosting without building every layer directly.
Use decision criteria, not preference. Ask:
- Who will own deployments, scaling, networking, and incident response?
- Do you need Kubernetes-specific APIs, controllers, or internal platform patterns?
- How many services and worker types do you expect to run in the next year?
- How much customization do you need around virtual private cloud (VPC) networking, identity, and traffic routing?
- Can your current continuous integration and continuous delivery (CI/CD) system deploy to the target cleanly?
If the team is migrating one or two Heroku apps, ECS with Fargate is often easier to operate than EKS. If the company already has platform engineers and multiple services moving toward Kubernetes, EKS may be a reasonable standard. The wrong answer is choosing the most flexible platform without budgeting for the people, testing, and maintenance it requires.
Plan the data migration before the app migration
Application containers are usually the easy part. Data is where outages, rollback problems, and hidden dependencies appear.
For a Heroku Postgres to Amazon Relational Database Service (RDS) migration, plan around these questions:
- How much downtime can the product tolerate? A small internal tool may tolerate a maintenance window. A customer-facing app may need replication and a shorter cutover.
- What is the rollback path? Once writes go to the new database, rolling back to Heroku is harder unless you planned for it.
- Are extensions, versions, and parameters compatible? Check database version, extensions, connection limits, timeouts, and maintenance settings before migration day.
- How will background workers behave during cutover? Workers can keep writing to old systems if they are not stopped or redirected in the right order.
- What owns backups after the move? Heroku backup habits do not automatically become AWS backup policies.
The same thinking applies to Redis, queues, file storage, and search. If the current app relies on Heroku add-ons, identify the AWS equivalent or third-party service early. For example, moving from an add-on Redis instance to Amazon ElastiCache changes networking, authentication, metrics, backup behavior, and access patterns.
Do not wait until cutover week to test database restores. A backup that has never been restored is an assumption, not a recovery plan.
Rebuild deployment and configuration deliberately
Heroku’s deployment flow is simple by design. AWS deployments usually require more explicit choices. You need to decide how code becomes an artifact, where that artifact lives, how it gets promoted, and how failures roll back.
A solid AWS deployment path usually includes:
- Build: Create a container image or deployable package in CI.
- Store: Push the artifact to a registry such as Amazon Elastic Container Registry (ECR), if you are using containers.
- Configure: Inject environment-specific settings through a managed secret and configuration system.
- Deploy: Update the service using a controlled deployment method.
- Verify: Run smoke tests, health checks, and basic user-flow checks.
- Rollback: Keep a known path to return to the previous version.
Pay close attention to Heroku-specific behavior you may have taken for granted:
- Release phase commands: If Heroku runs database migrations before promoting a release, recreate that ordering safely.
- Environment variables: Move secrets into AWS Secrets Manager, Systems Manager Parameter Store, or another controlled secrets system. Avoid plain-text values in pipeline logs.
- Review apps: If engineers depend on temporary environments for pull requests, decide whether you will rebuild that pattern or replace it.
- Rollbacks: A container rollback does not always roll back a database migration. Treat schema changes as part of the release design.
This is a good time to introduce infrastructure as code (IaC) with Terraform, AWS CloudFormation, or the AWS Cloud Development Kit (CDK). Keep the first version focused. Define the core network, compute, database, secrets, and deployment resources. Avoid turning the migration into a complete platform rewrite unless the current system is already blocking the move.
Design the cutover as a controlled production change
The riskiest part of the migration is usually not building AWS infrastructure. It is switching real users and real writes to the new environment.
Create a cutover plan that includes timing, owners, checks, and rollback criteria. A useful plan covers:
- Pre-cutover freeze: Decide whether to pause deployments, schema changes, or high-risk product changes before migration.
- Data sync: Confirm the latest data has moved and that replication lag, if used, is acceptable.
- Worker control: Stop or drain workers in the right order so jobs do not process twice or write to the wrong system.
- Domain Name System (DNS): Lower time to live (TTL) ahead of the change if you need faster traffic switching.
- Health checks: Verify application health, database connectivity, queue processing, login, payments, uploads, and other core flows.
- Rollback trigger: Define what failure means before emotions take over. For example, failed writes, login failures, sustained elevated error rates, or broken worker processing.
- Communication: Decide who is in the migration room, who updates leadership, and who talks to support or customer-facing teams.
For many teams, a staged approach is safer than one large switch. You might first run the AWS environment with production-like data, then route internal traffic, then move a small percentage of production traffic, then complete the DNS change. The exact pattern depends on the app, but the principle is the same: reduce unknowns before the final switch.
Watch for the operational gaps Heroku used to cover
After the move, the team owns more of the runtime. That can be a good trade, but only if you close the basic operational gaps before customers find them.
Common gaps include:
- Logs without retention or search: Heroku logs are convenient. In AWS, decide where logs go, how long you keep them, and how engineers search them during incidents.
- Metrics without useful alerts: CPU and memory are not enough. Track request errors, latency, queue depth, database connections, failed jobs, and saturation points.
- No clear on-call path: If nobody owns production alerts, the migration has only moved the risk.
- Open networking: Security groups, private subnets, load balancers, and database access need intentional rules. Avoid broad access because it is faster during setup.
- Cost drift: AWS can be efficient, but idle load balancers, oversized databases, NAT gateway traffic, logging volume, and overprovisioned environments can surprise teams.
- Manual production changes: Console fixes may be useful in an incident, but they should be captured back into code.
Make the first post-migration week part of the plan. Review incidents, slow queries, deployment friction, alert noise, and unexpected costs. Fix the sharp edges while the migration context is still fresh.
Takeaway
A Heroku to AWS migration works best when you treat Heroku as a bundle of operational responsibilities, not just a hosting target. Map the current system, choose an AWS runtime your team can operate, plan data movement carefully, rebuild deployments with rollback in mind, and run cutover like a production incident you are trying to prevent.
If you are planning the move now, start with a service and dependency inventory. That document will expose most of the real migration work before you commit to architecture, timeline, or cutover date.




