heroku → aws · 2026 migration playbook

Heroku to AWS — the architecture, the cutover, and who runs it for you.

Heroku is the easiest place to ship a v1 and one of the most expensive places to run it at scale. This is the senior-engineer playbook for moving off Heroku and onto AWS: the target architecture (dynos → ECS/Fargate or App Runner, Heroku Postgres → RDS/Aurora, add-ons → AWS-native), the exact cutover plan with the downtime window, buildpacks → containers, the real before/after cost, and the gotchas that bite teams. A MAP-funded AWS partner can run the whole thing — often at little-to-no cost to you.

typical cost cut
40–70%
cutover downtime
0–15 min
project length
3–8 weeks
cost to you (MAP)
$0–low
TL;DR
  • Teams leave Heroku for three reasons: cost (a Standard-2X dyno is ~$50/mo for ~1GB RAM — a fraction of that on Fargate or EC2, and the gap widens with every dyno and add-on), platform limits (Postgres connection caps, the hard 30-second router timeout, coarse autoscaling), and the add-on tax (managed Redis, logging, and metrics marked up over the underlying AWS service).
  • The target architecture: web/worker dynos → ECS on Fargate (or App Runner for the closest Heroku-like experience), Heroku Postgres → RDS or Aurora PostgreSQL, Heroku Redis → ElastiCache, Heroku Scheduler → EventBridge Scheduler, Pipelines → CodePipeline/CodeBuild (or keep GitHub Actions). Buildpacks become a Dockerfile; config vars become Secrets Manager + SSM Parameter Store.
  • The cutover is the risky part, and it is solvable: AWS DMS keeps RDS continuously synced (CDC) with Heroku Postgres while you test, then a short maintenance/DNS switch (typically 0–15 min) flips traffic. CloudRoute routes you to a vetted AWS partner who runs it end-to-end — and when the workload qualifies for the AWS Migration Acceleration Program (MAP), AWS funds the migration and the customer pays little-to-nothing.
the trigger

IWhy teams leave Heroku — cost, limits, and the add-on tax

Nobody migrates off Heroku because they dislike it. They migrate because the thing that made Heroku great at v1 — total abstraction at a flat per-dyno price — becomes the thing that caps their margin and their architecture at scale.

The first and loudest reason is cost. Heroku prices by the dyno — a small, fixed slice of compute (a Standard-2X is ~1GB RAM for ~$50/month; a Performance-L ~14GB for ~$500). Against raw AWS compute the convenience looks expensive: a Fargate task with 1 vCPU and 2GB runs ~$35–$40/month at 24/7 (less if it scales down off-hours), and equivalent always-on EC2 with a Savings Plan can be a third of the Heroku price for the same headroom. The gap is not 10–20% — for a dozen dynos plus add-ons, teams routinely land 40–70% lower, and the savings compound as they grow.

The second reason is platform limits. Heroku Postgres caps concurrent connections (Standard ~120, Premier up to 500, the largest plans ~1,000), so a connection-per-request app hits the ceiling fast and is forced into PgBouncer or the Heroku pooler. The router enforces a hard 30-second request timeout you cannot raise. Dynos cycle (restart) at least daily — fine for stateless web, a real constraint for stateful work or long jobs. Autoscaling is coarse and Performance-tier only. None of these are bugs — they are the price of the abstraction, and exactly the walls a scaling team keeps hitting.

The third reason is the add-on tax. Heroku's managed Redis, Postgres, logging, and metrics add-ons each carry a markup over the underlying infrastructure: Heroku Data for Redis premium tiers cost multiples of the equivalent ElastiCache node, and logging/APM billed through Heroku is typically priced above going direct. On AWS you buy the underlying service (ElastiCache, RDS, CloudWatch, OpenSearch) at list price — often at a committed-use discount on top.

The honest counterpoint: Heroku's abstraction has real value, and AWS asks you to own more — a landing zone, IAM, networking, container images, deploy pipelines. That operational surface is precisely why most teams do not run this migration alone. The rest of this page is the architecture and the plan; the CloudRoute angle is that a vetted AWS partner runs it for you, and AWS often funds it.

the margin math

A seed/Series-A app commonly runs ~10 dynos + Heroku Postgres Standard + Heroku Redis + logging/metrics add-ons for $1,800–$3,500/month. The equivalent on AWS — Fargate (or App Runner) + RDS/Aurora + ElastiCache + CloudWatch, with a Compute Savings Plan — commonly lands at $700–$1,400/month. That recovered margin is what pays for the migration (and with MAP, AWS pays for it instead).

where things land

IIThe target architecture: what each Heroku piece becomes on AWS

There is no single "AWS version of Heroku." There are two sensible compute targets, and the right one depends on how much operational control you want versus how close to the Heroku experience you want to stay. The data and supporting services map almost one-to-one.

For compute, the two realistic targets are AWS App Runner and Amazon ECS on Fargate. App Runner is the closest thing AWS has to Heroku: point it at a container image and it builds, deploys, serves HTTPS, and autoscales on request volume — the fastest path off Heroku for a straightforward web service. ECS on Fargate is the workhorse: serverless containers with full control over networking (VPC, subnets, security groups), task sizing, service autoscaling, an Application Load Balancer, and clean separation of web and worker services. Teams migrating because they outgrew Heroku usually want Fargate — the control App Runner abstracts away is the very thing they outgrew Heroku for. (The callout below makes the choice concrete; EKS is overkill straight off Heroku unless you already run it.)

For the relational database, Heroku Postgres becomes Amazon RDS for PostgreSQL (the like-for-like managed Postgres, simplest mental model) or Aurora PostgreSQL (AWS's cloud-native engine — a little more per hour, but with faster failover, autoscaling storage, up to 15 read replicas, and automatic capacity scaling in Serverless v2). A common pattern is to land on RDS first to minimize change at cutover, then evaluate Aurora once stable. Because both speak PostgreSQL, your app code and ORM do not change.

Heroku Data for Redis becomes Amazon ElastiCache (Redis OSS or Valkey) — same protocol, so cache, queues (Sidekiq, Bull, Celery), and rate-limiters keep working with a connection-string change. Heroku Scheduler becomes EventBridge Scheduler triggering an ECS task or Lambda; worker dynos become a separate ECS service or queued Fargate tasks. File uploads stay on S3 (you were already there, since the dyno filesystem is ephemeral). Outbound email (SendGrid/Mailgun) can move to Amazon SES or stay with the vendor billed directly.

Heroku Pipelines, review apps, and the build/release flow map to CodePipeline + CodeBuild (+ optionally CodeDeploy), or you keep GitHub Actions and have it push images to ECR and update the ECS service. Heroku Connect (Salesforce sync) has no drop-in replacement — it becomes AWS AppFlow or a scheduled Salesforce-API job, so flag it early as one of the few pieces that needs real design. The supporting glue (config vars, buildpacks, logging) is covered next, where the hands-on work lives.

App Runner vs Fargate — pick based on control

App Runner for one or a few stateless web services when you want the git-push simplicity Heroku gave you (least new operational surface). ECS on Fargate when you need VPC networking control, coordinated web + worker services, fine-grained autoscaling, or private connectivity to RDS/ElastiCache — the most common landing spot for "we outgrew Heroku" teams. Skip EKS unless you already run Kubernetes.

the lookup table

IIIHeroku → AWS service map (the principles behind the table)

The full row-by-row mapping — every Heroku building block, its AWS equivalent, the cost direction, and the effort — lives in the comparison table further down. Two principles make reading that table safe.

First, anything that speaks a standard protocol moves with a connection-string change and no application rewrite — PostgreSQL (Heroku Postgres → RDS/Aurora), Redis (Heroku Data for Redis → ElastiCache), and S3 (usually already in place). These are the "trivial" rows.

Second, anything Heroku-proprietary needs a one-time translation into an AWS-native primitive: buildpacks + Procfile → a Dockerfile, config vars → Secrets Manager + SSM Parameter Store, Heroku Scheduler → EventBridge Scheduler, Pipelines → CodePipeline/CodeBuild, custom domains/SSL → Route 53 + ACM, Heroku Connect → AppFlow. Done once and documented, that translation is the work a migration partner does — the "Medium/High effort" rows in the table.

the hands-on part

IVBuildpacks → containers, and config vars → Secrets Manager

The single most hands-on task in a Heroku→AWS move is turning the buildpack-and-Procfile model into a container image. It is mechanical, but it is where teams without container experience slow down — so here is exactly what it involves.

On Heroku a buildpack inspects your repo, installs the runtime, and produces a slug; your Procfile declares process types. AWS does not run buildpacks (outside App Runner's managed source builds), so you produce the same result with a Dockerfile — for most stacks a dozen lines: a slim base image, copy + install the dependency manifest (npm ci / bundle install / pip install / composer install), copy the app, set the start command. Each process type becomes a container command or a separate ECS service: web is the load-balanced service, worker is its own, and Heroku's "release" phase (migrations on deploy) becomes a one-off ECS run-task or a CodeBuild step. A Dockerfile is the portable choice most partners standardize on, though App Runner can build from source.

Config vars are the other translation. On Heroku `heroku config` is a flat bag of env vars from `DATABASE_URL` to API keys. On AWS you split it by sensitivity: real secrets (DB credentials, API keys, signing secrets) into AWS Secrets Manager, non-sensitive config (feature flags, public URLs, log levels) into SSM Parameter Store. Both are referenced from the ECS task definition (or App Runner config) so the container sees them as environment variables exactly as on Heroku — your code does not change. The migration step is a one-time export, a sort into secret-vs-config, and a load into Secrets Manager / Parameter Store, which a partner scripts so nothing is missed or leaked into an image layer.

Two Heroku conveniences to consciously replace: ephemeral-filesystem assumptions (already forced you onto S3, so usually fine) and automatic `DATABASE_URL`/`REDIS_URL` injection (now you point those at your RDS and ElastiCache endpoints, ideally over private VPC networking). Neither is hard; both are easy to forget — which is why they live on the migration checklist, not in someone's memory.

the risky 20 minutes

VThe cutover plan: DMS, DNS, and a short downtime window

Everything else can be built and tested without touching production. The database cutover is the one moment that touches live data — where a plan earns its keep. Done right, user-visible downtime is zero to fifteen minutes.

The core technique is to keep the new RDS/Aurora database continuously in sync with Heroku Postgres while you test, so that at cutover you switch to an already-current copy rather than copying a cold database under time pressure. AWS Database Migration Service (DMS) does exactly this: a full load into RDS, then a switch into change data capture (CDC) mode that streams every subsequent insert/update/delete in near-real time. Because both ends are PostgreSQL, this is a homogeneous migration — no Schema Conversion Tool needed; the schema moves as-is via a `pg_dump --schema-only` restore. Very small databases can skip DMS for a single `pg_dump | pg_restore` inside the window; DMS earns its place once a cold dump/restore would blow past an acceptable downtime window.

With CDC running and the new stack smoke-tested against the synced database, the cutover is short and scripted: maintenance mode → let DMS drain the last few seconds → flip `DATABASE_URL` to RDS and switch traffic to the new App Runner/Fargate service → update the Route 53 record (TTL lowered ahead of time) → verify writes land in RDS → exit maintenance. Because the data was already synced, the window is dominated by DNS propagation and verification, not data transfer — commonly 0–15 minutes of write-downtime during a low-traffic period.

The rollback plan is non-negotiable and simple: keep Heroku running until you are confident, and if something is wrong, switch `DATABASE_URL` and DNS back. The clean window is before RDS has accepted production writes Heroku hasn't seen — once committed, rolling back means reconciling those writes — so most teams keep the rollback window short (minutes to hours) and watch closely. Lowering DNS TTL to 60 seconds a day ahead makes both the switch and the rollback fast.

why DMS over a cold dump

A cold pg_dump | pg_restore makes your downtime window = export + transfer + import of the whole database: 20–60 minutes for 5GB, unacceptable at 50GB+. DMS with CDC moves the bulk load before the window and only drains the last few seconds during it — so downtime stays in single-digit minutes regardless of database size.

the runbook

VIThe step-by-step migration (assess → land → cut over → optimize)

Here is the end-to-end sequence a partner runs, mapped to the AWS MAP phases (Assess → Mobilize → Migrate). For a typical Heroku app this is a 3–8 week project — most of it parallelizable, none of it touching production until the final cutover.

  • 1 — Assess (days 1–5) — Inventory every dyno, add-on, config var, scheduled job, and the database size + connection pattern. Decide App Runner vs Fargate and RDS vs Aurora; produce the target architecture and a cost before/after model. Under MAP this Assess/TCO phase is typically free.
  • 2 — Build the landing zone — Stand up the AWS account structure, VPC, subnets, security groups, IAM roles, ECR, and Secrets Manager / Parameter Store. (Reuses the AWS Landing Zone and AWS DevOps foundations in the DevOps cluster.)
  • 3 — Containerize — Write the Dockerfile(s), translate each Procfile process type into a container command or ECS service, and confirm the image builds and boots locally and in CodeBuild.
  • 4 — Provision data + cache — Create the RDS/Aurora instance and the ElastiCache cluster in the VPC, restore the schema, and wire EventBridge Scheduler for the Heroku Scheduler cron jobs.
  • 5 — Start replication — Run a DMS full load from Heroku Postgres into RDS, then switch to CDC so the new database tracks production while you test. Validate row counts and known records.
  • 6 — Deploy + smoke-test — Deploy the App Runner/Fargate service against the synced database (no public traffic yet) and run the full test suite + manual smoke tests, including background jobs and scheduled tasks.
  • 7 — Cut over — Maintenance mode → drain CDC → flip DATABASE_URL → update Route 53 (low TTL pre-set) → verify writes land in RDS → exit maintenance. Target window: 0–15 minutes.
  • 8 — Optimize + decommission — Apply a Compute Savings Plan, right-size Fargate and RDS, set autoscaling, confirm CloudWatch alarms and backups. Keep Heroku as rollback for a short window, then cancel the dynos and add-ons — that cancellation is when the savings become real.
real numbers

VIICost before and after — a representative mid-size app

Cost is the reason most teams start this, so here is a concrete model rather than a hand-wave. Numbers are 2026 list-price ranges for a mid-size production app; yours vary with traffic and how aggressively you right-size and commit.

Take a typical Series-A SaaS: ~10 dynos across web and worker, a Heroku Postgres Standard/Premier plan, Heroku Redis, and the usual logging + metrics add-ons. On Heroku that commonly totals $1,800–$3,500/month and scales roughly linearly — every new dyno and plan bump adds a fixed chunk.

The same workload on AWS: web + workers on Fargate (right-sized, with off-hours scale-down) or App Runner, RDS/Aurora PostgreSQL, an ElastiCache node, CloudWatch, plus modest data-transfer and S3. At list price that is often $900–$1,600/month; layer a one- or three-year Compute Savings Plan and committed database capacity on top and steady-state lands closer to $700–$1,400/month. That is the 40–70% reduction teams cite — and unlike Heroku, the AWS bill bends down as you commit and right-size rather than only stepping up.

The cost the table cannot show is the migration itself: engineering time, dual-running Heroku + AWS for a few weeks, and the learning curve of owning the infrastructure. This is exactly where the CloudRoute model changes the arithmetic. When the workload qualifies for the AWS Migration Acceleration Program, AWS funds the assessment and credits a large share of the migration/modernization cost — the partner is paid through MAP — so you capture the 40–70% ongoing savings without paying the usual migration bill. When MAP does not apply, it is a vetted-partner engagement that de-risks the cutover: you pay for the work, but you are not improvising the riskiest 20 minutes of your year.

what bites teams

VIIIGotchas: the things that turn a clean migration messy

Most Heroku→AWS migrations that go badly go badly for a small, repeatable set of reasons. Naming them up front is the cheapest insurance there is.

  • Connection limits move, they don't vanish — A bigger RDS instance buys headroom, but the real fix for the Heroku connection ceiling is RDS Proxy (or PgBouncer) — a connection-per-request app on autoscaling Fargate can open more connections than one Postgres handles. Plan pooling into the architecture, not after the incident.
  • Cron must be recreated explicitly — Heroku Scheduler runs at fixed clock times; EventBridge Scheduler is more precise but every job has to be recreated by hand. Jobs silently not firing after cutover is the classic "we forgot one" failure — inventory them in the Assess phase.
  • The 30-second router timeout hid slow requests — Heroku killed any request over 30 seconds, masking slow endpoints. Behind an ALB you can raise that — but a 28-second request was already a problem. Cutover is the moment to move long work to a queue + worker.
  • SSL / connection-string drift — Heroku injects DATABASE_URL with specific SSL parameters; RDS/Aurora want their own SSL mode and CA bundle. Many "won't connect after cutover" issues are just `sslmode` and certificate config — verify the string against RDS before the window. (Also audit for code that writes to local disk: it worked on one dyno by luck and breaks across Fargate tasks — stateful data must use S3 or the database.)
  • Heroku Connect + pipelines have no drop-in — Salesforce sync (Heroku Connect) and review-app/pipeline workflows need deliberate redesign (AppFlow / scheduled jobs; CodePipeline or GitHub Actions). Teams that treat these as "just another swap" lose days — scope them explicitly.
  • Forgetting to decommission Heroku — Savings are not real until the dynos and add-ons are cancelled. Keep Heroku as rollback for a defined short window — then actually turn it off. Paying both bills "just in case" for months erases a chunk of year-one savings.
service-by-service

Heroku service → AWS equivalent (cost delta + effort)

The definitive lookup: every Heroku building block, where it lands on AWS, the rough cost direction, and the engineering effort. "Trivial" rows are connection-string or config swaps; "Medium/High" rows are the real work — and the work a partner does for you.

HerokuAWS equivalentWhat changesCost directionEffort
Web dynoApp Runner, or ECS/Fargate + ALBBuildpack → Dockerfile; router → ALBDown 40–70%Medium
Worker dynoECS/Fargate service or queued tasksProcess type → separate serviceDown 40–70%Medium
Heroku PostgresRDS for PostgreSQL / Aurora PostgreSQLConnection string + cutover via DMSDown 30–60%High (cutover)
Heroku Data for RedisElastiCache (Redis OSS / Valkey)Connection string onlyDown 50–70%Trivial
Heroku SchedulerEventBridge Scheduler → ECS/LambdaRecreate each job explicitlyDown (near $0)Low
Config varsSecrets Manager + SSM Parameter StoreSplit secret vs config; inject to taskNegligibleLow
Buildpacks + ProcfileDockerfile + ECRExplicit container buildNegligibleMedium
Pipelines / Review AppsCodePipeline + CodeBuild (or GitHub Actions)Rebuild the CI/CD flowDown / neutralMedium
Log drains + metricsCloudWatch Logs + Container InsightsReconfigure logging sinkDown vs add-onsLow
File uploads / S3 add-onAmazon S3Usually already on S3DownTrivial
Email add-onsAmazon SES (or vendor direct)Swap SMTP/API configDownLow
Heroku Connect (Salesforce)AWS AppFlow / scheduled API jobRedesign the syncVariesHigh
Custom domain + SSLRoute 53 + ACM (free TLS)DNS + cert on ALB/App RunnerDown (free certs)Low
Cost directions are representative 2026 ranges and depend on right-sizing and commitments (Savings Plans, committed DB capacity). The high-effort rows — Postgres cutover, buildpack containerization, Heroku Connect — are exactly what a MAP-funded partner handles end-to-end.
want this run for you — and funded?
Get matched with an AWS partner who runs your Heroku migration (often MAP-funded)
Start in 3 minutes →
a recent match

A Heroku→AWS migration CloudRoute routed — anonymized

inquiry · series-a b2b saas (rails), Austin
Series-A B2B SaaS on a Ruby on Rails monolith: ~14 dynos (web + Sidekiq workers), Heroku Postgres Premier-2, Heroku Data for Redis, log + APM add-ons. Heroku bill ~$3,100/month and climbing; hitting the 500-connection ceiling and the 30-second router timeout on report endpoints.

Situation: Margin pressure ahead of a Series-B raise, plus two hard technical walls: Postgres connections maxing out at peak and long report requests dying at the 30s router timeout. The two-person platform team had no container/AWS experience and could not afford a multi-month DIY migration or a risky big-bang database cutover.

What CloudRoute did: Routed within 24 hours to an AWS Advanced-tier partner with a Rails + Heroku track record, who ran the MAP Assess phase (free) and filed the work as a MAP engagement. Target: ECS on Fargate (web + a dedicated Sidekiq service) behind an ALB, Aurora PostgreSQL with RDS Proxy for pooling, ElastiCache for Redis, EventBridge Scheduler for cron, config vars split into Secrets Manager + Parameter Store, and GitHub Actions → ECR → ECS for deploys. Postgres moved via AWS DMS (full load + CDC); long report requests were refactored onto a Sidekiq queue.

Outcome: Cutover ran in a Sunday-night window with ~11 minutes of write-downtime — DMS had Aurora fully in sync, so the switch was DNS + verification, not data transfer. Steady-state AWS bill landed at ~$1,150/month (a ~63% cut); connection ceiling and report timeouts both gone; project ran ~6 weeks. Because the workload qualified for MAP, AWS funded the assessment and credited the migration cost — out-of-pocket migration cost was effectively $0, and CloudRoute's commission was paid by the partner from MAP funding.

project length: ~6 weeks · cutover downtime: ~11 min · monthly spend: $3,100 → $1,150 (−63%) · migration cost to customer: ~$0 (MAP-funded)

faq

Common questions

How long does a Heroku to AWS migration take?
For a typical Heroku app — a handful of web and worker dynos, Postgres, Redis, and a few add-ons — a partner-run migration is usually 3–8 weeks end-to-end. Most of that is building and testing the landing zone, containers, and synced database in parallel, without touching production; only the final cutover (a single short maintenance window) touches live traffic.
How much downtime is there at cutover?
Typically 0–15 minutes of write-downtime, run during a low-traffic period. The trick is AWS Database Migration Service (DMS): it bulk-loads your Heroku Postgres data into RDS/Aurora ahead of time, then streams ongoing changes via change data capture. At cutover you only drain the last few seconds, flip the connection string, and update DNS — so the window is dominated by DNS propagation and verification, not copying data, regardless of database size.
Will my Heroku Postgres and Redis migrate without code changes?
Largely yes. Heroku Postgres → Amazon RDS or Aurora PostgreSQL is a homogeneous PostgreSQL move, so schema, queries, and ORM are unchanged — you swap DATABASE_URL and migrate the data with DMS. Heroku Data for Redis → Amazon ElastiCache is the same protocol, so your cache and Sidekiq/Bull/Celery queues keep working with a connection-string change. The code you touch is the container build (buildpack → Dockerfile) and config-var handling, not your application logic.
How much will I actually save moving off Heroku?
Representative mid-size apps cut their monthly bill by 40–70%. A workload running ~$1,800–$3,500/month on Heroku (dynos + Heroku Postgres + Redis + add-ons) commonly lands at $700–$1,400/month on AWS once you right-size Fargate/RDS and apply a Compute Savings Plan. The savings come from buying AWS services at list/committed price instead of Heroku's per-dyno pricing and add-on markups — and the AWS bill bends down further as you commit, rather than only stepping up.
What does AWS MAP funding mean for the cost of the migration?
The AWS Migration Acceleration Program (MAP) is how the migration itself can cost little to nothing. For qualifying migrations — generally those with a meaningful projected AWS spend afterward — AWS funds the Assess phase (TCO/readiness, typically free) and credits a large share of the migration/modernization cost, paying the partner through MAP rather than you. When a workload does not qualify, CloudRoute still routes you to a vetted partner who runs the cutover at a fixed scope.
What about Heroku Pipelines, Scheduler, and Heroku Connect?
Heroku Scheduler (cron) becomes Amazon EventBridge Scheduler triggering an ECS task or Lambda — but every job must be recreated explicitly. Heroku Pipelines / Review Apps / Releases map to CodePipeline + CodeBuild + ECR, or you keep GitHub Actions pushing images to ECR. Heroku Connect (Salesforce sync) is the one piece with no drop-in: it becomes AWS AppFlow or a scheduled API integration and needs real design, so scope it explicitly rather than treating it as a simple swap.
Can I roll back if the cutover goes wrong?
Yes, if you plan for it. Keep Heroku running and do not decommission the dynos or add-ons until you are confident on AWS; if something is wrong, switch DATABASE_URL and DNS back. Lower DNS TTL to ~60 seconds the day before so both the switch and the rollback are fast. The clean rollback window is before production writes have landed in RDS that Heroku hasn't seen, so most teams keep it short (minutes to hours) and watch closely.

Move off Heroku — onto AWS, run by a partner, often funded by AWS.

CloudRoute routes you to a vetted AWS partner who plans and runs the whole Heroku→AWS migration — containers, RDS/Aurora, ElastiCache, the DMS cutover, the cost optimization. Qualifying migrations are MAP-funded, so you capture the 40–70% ongoing savings without paying the usual migration bill.

matched within< 24h
typical cost cut40–70%
migration cost (MAP)$0–low
Heroku to AWS — migration architecture, cutover & cost (2026) · CloudRoute