Ruby on Rails runs beautifully on AWS — but the move (usually off Heroku) is more than a deploy: it is web + Sidekiq workers on ECS/Fargate or App Runner, Postgres on RDS/Aurora, Redis on ElastiCache for Sidekiq and Action Cable, Active Storage on S3, an ALB out front, Rails credentials in Secrets Manager, and a database cutover via DMS. This is the senior-engineer playbook for all of it — and a MAP-funded AWS partner can run the whole thing, often at little-to-no cost to you.
Most Rails teams that migrate to AWS are leaving Heroku (or Render, or a single big VPS) for the same three reasons: cost at scale, platform limits, and the need for real infrastructure control as the product and the team grow. Rails itself is not the problem — where and how it runs is.
The loudest reason is cost. Heroku prices Rails by the dyno, and a Rails monolith is rarely one dyno — it is web dynos plus Sidekiq worker dynos plus a clock/scheduler process, each a fixed monthly slice. Layer on Heroku Postgres and Heroku Data for Redis (both marked up over the underlying infrastructure) and a mid-size Rails app commonly runs $1,800–$3,500/month. The same workload on AWS — Fargate for web and Sidekiq, RDS/Aurora, ElastiCache, S3 + CloudFront, with a Compute Savings Plan — commonly lands 40–70% lower, and the gap widens with every worker and plan bump.
The second reason is platform limits that bite Rails specifically. Heroku Postgres caps connections (Standard ~120, the largest plans ~500–1,000), and Rails opens a pool per Puma worker and a pool per Sidekiq process — so a few web dynos and a busy Sidekiq fleet exhaust connections fast, forcing PgBouncer or the Heroku pooler. Heroku's hard 30-second router timeout kills slow report endpoints that should have been Sidekiq jobs. Dynos cycle daily, which is fine for stateless Puma but a constraint for long-running work. None of these are bugs; they are the cost of the abstraction, and exactly the walls a scaling Rails team hits.
The third reason is control. As a Rails app matures it wants things Heroku abstracts away: VPC networking so the database is never publicly reachable, fine-grained autoscaling on web and workers independently, private connectivity to RDS and ElastiCache, IAM-scoped access to S3, and a real deploy pipeline. AWS gives all of that — at the cost of owning it. That operational surface (landing zone, IAM, networking, container images, pipelines) is precisely why most teams do not run this migration alone.
The honest counterpoint: Heroku's `git push` simplicity has genuine value, and AWS asks a Rails team to own more. The rest of this page is the architecture and the runbook; the CloudRoute angle is that a vetted AWS partner runs it for you, and for qualifying workloads AWS funds it through MAP — so you capture the ongoing savings and the control without paying the usual migration bill or improvising the riskiest cutover of the year.
A typical Series-A Rails app — web (Puma) + Sidekiq workers + a clock process, Heroku Postgres Standard/Premier, Heroku Redis, log + APM add-ons — commonly runs $1,800–$3,500/month. The same workload on AWS — Fargate (web + Sidekiq services) + RDS/Aurora PostgreSQL + ElastiCache + S3/CloudFront + CloudWatch, with a Compute Savings Plan — commonly lands at $700–$1,400/month. That recovered margin pays for the migration — and with MAP, AWS pays for it instead.
There is no single "AWS version of Heroku," but a Rails app has a well-worn target shape. The compute choice is the one real decision; the data and supporting services map almost one-to-one, and your Rails code barely changes.
For compute, the two realistic targets are Amazon ECS on Fargate and AWS App Runner. ECS on Fargate is the workhorse and the most common landing spot for a Rails monolith: serverless containers, full VPC networking control, independent services for web and Sidekiq, an Application Load Balancer in front of Puma, and separate autoscaling policies (web scales on request count or CPU; Sidekiq scales on queue depth). App Runner is the closest thing to Heroku — point it at a container image and it builds, deploys, serves HTTPS, and autoscales — and it is a fine target for a small, mostly-web Rails app. But the moment you have a real Sidekiq fleet, Action Cable, and private database networking, Fargate is the right answer because that control is exactly what the team outgrew Heroku for. (EKS is overkill straight off Heroku unless you already run Kubernetes.)
The web tier is Puma running in a container, registered as an ECS service behind an ALB. The ALB terminates TLS (with a free ACM certificate), health-checks a lightweight Rails endpoint (e.g. `/up`, the built-in Rails health check), and load-balances across tasks. Rails runs with `RAILS_ENV=production`, `RAILS_LOG_TO_STDOUT=true` (so logs flow to CloudWatch), and `RAILS_SERVE_STATIC_FILES` left off because static assets are served by CloudFront, not Puma. Database migrations (`rails db:migrate`) run as a one-off ECS task or a pipeline step at release time — the AWS equivalent of Heroku's release phase.
For the relational database, PostgreSQL moves to Amazon RDS for PostgreSQL (like-for-like managed Postgres, simplest mental model) or Aurora PostgreSQL (AWS's cloud-native engine — slightly more per hour, but faster failover, autoscaling storage, up to 15 read replicas, and Serverless v2 capacity scaling). A common pattern is to land on RDS first to minimize change at cutover, then evaluate Aurora once stable. Either way it is still PostgreSQL, so ActiveRecord, your schema, and your queries are unchanged. Put RDS Proxy (or PgBouncer) in front from day one — a Rails app with multiple Puma workers and a Sidekiq fleet opens far more connections than a single Postgres instance wants to hold.
Redis becomes Amazon ElastiCache (Redis OSS or Valkey) — same protocol, so the three things Rails uses Redis for keep working with a connection-string change: Sidekiq queues, Action Cable's pub/sub backend, and the Rails cache store (`config.cache_store = :redis_cache_store`). Active Storage attachments live on Amazon S3 via the `aws-sdk-s3` gem and the `:amazon` service in `config/storage.yml`, fronted by CloudFront for delivery; if you were on Heroku you were already on S3 because the dyno filesystem is ephemeral, so this is usually a credentials-and-bucket swap. Scheduled jobs (the `whenever` gem, `sidekiq-cron`, or Heroku Scheduler) become either sidekiq-cron on the worker service or Amazon EventBridge Scheduler triggering an ECS task. Outbound email via Action Mailer moves to Amazon SES or stays with your existing provider.
App Runner for a small, mostly-web Rails app when you want git-push simplicity and minimal new operational surface. ECS on Fargate when you have a real Sidekiq fleet, Action Cable, independent web/worker autoscaling, or need private VPC connectivity to RDS and ElastiCache — the most common landing spot for "we outgrew Heroku" Rails teams. Skip EKS unless you already run Kubernetes.
The full row-by-row mapping — every Rails component, its AWS service, the cost direction, and the effort — lives in the comparison table further down. Two principles make that table safe to read.
First, anything that speaks a standard protocol moves with a connection-string or credential change and no Rails rewrite: PostgreSQL (→ RDS/Aurora), Redis for Sidekiq/Action Cable/cache (→ ElastiCache), and S3 for Active Storage (usually already in place). These are the "trivial" rows — ActiveRecord, Sidekiq, Action Cable, and Active Storage are all storage-agnostic by design.
Second, anything platform-proprietary needs a one-time translation into an AWS-native primitive: the buildpack + Procfile become a Dockerfile, Rails credentials + config vars become Secrets Manager + SSM Parameter Store, the asset pipeline (Propshaft/Sprockets) precompiles in the image and serves via CloudFront, scheduled jobs become sidekiq-cron or EventBridge Scheduler, and custom domains/SSL become Route 53 + ACM. Done once and documented, that translation is the work a migration partner does — the "Medium/High effort" rows in the table.
The single most hands-on task is turning the buildpack-and-Procfile model into a container image — and Rails has its own wrinkles (asset precompilation, the master key, the bootsnap cache). It is mechanical, but it is where teams without container experience slow down, so here is exactly what it involves.
Modern Rails (7.1+) ships a generated production `Dockerfile`, and it is a good starting point: a multi-stage build that installs gems with `bundle install`, runs `rails assets:precompile` at build time, then copies only the gems and the precompiled app into a slim runtime image so the final container has no build toolchain. Each process type from your `Procfile` becomes a separate container command or ECS service: the `web` line (`bundle exec puma`) is the load-balanced service, the `worker` line (`bundle exec sidekiq`) is its own service, and a `clock`/scheduler process (if you use `clockwork` or `whenever`) is either folded into sidekiq-cron or replaced by EventBridge Scheduler. Heroku's release phase — `rails db:migrate` on deploy — becomes a one-off ECS run-task or a CodeBuild step gated before the new web tasks go live.
Asset precompilation is the Rails-specific trap. On Heroku the buildpack runs `assets:precompile` for you; in a Dockerfile you must do it explicitly, and it needs a dummy `SECRET_KEY_BASE` at build time (Rails requires one to initialize, even though precompilation does not use real secrets) — `SECRET_KEY_BASE_DUMMY=1` in recent Rails handles this cleanly. Precompiled assets are fingerprinted and baked into the image, then served by CloudFront (origin = the ALB or an S3 bucket you sync them to), so Puma never serves static files in production. Get this wrong and you see missing CSS/JS after cutover — the classic "it worked locally" Rails-on-containers failure.
Rails secrets are the other translation, and Rails is opinionated here. Production Rails uses `config/credentials.yml.enc` (or environment-specific `config/credentials/production.yml.enc`) decrypted at boot by the `RAILS_MASTER_KEY`. On Heroku that master key is a config var; on AWS the master key becomes a secret in AWS Secrets Manager, injected into the ECS task definition so the container sees `RAILS_MASTER_KEY` exactly as before and decrypts credentials normally. Everything else splits by sensitivity: real secrets (`DATABASE_URL`, `REDIS_URL`, third-party API keys, `SECRET_KEY_BASE` if you set it explicitly) into Secrets Manager; non-sensitive config (feature flags, public URLs, `RAILS_LOG_LEVEL`, `RAILS_MAX_THREADS`) into SSM Parameter Store. Both are referenced from the task definition so Rails reads them as plain environment variables — your `config/` code does not change. The migration step is a one-time export of `heroku config`, a sort into secret-vs-config, and a load into Secrets Manager / Parameter Store, scripted by a partner so nothing leaks into an image layer or is missed.
Two Rails conveniences to consciously replace: the ephemeral filesystem (Active Storage on S3 already handles uploads, but check for any code writing to `tmp/` or `public/` and expecting it to persist across tasks — it will not, since Fargate tasks are independent) and automatic `DATABASE_URL`/`REDIS_URL` injection (now you point those at your RDS and ElastiCache endpoints over private VPC networking, with the right `sslmode`). Neither is hard; both are easy to forget — which is why they live on the migration checklist, not in someone's memory.
Rails apps lean on Redis-backed background processing and real-time features more than most stacks. All of it ports cleanly — the trick is giving each concern the right AWS shape rather than cramming it into one container.
Sidekiq becomes its own ECS/Fargate service, separate from web, pointed at the same ElastiCache Redis endpoint. Running it as a distinct service is the whole point of moving to Fargate: you scale workers on queue depth (a CloudWatch metric or a custom autoscaling policy) independently of web traffic, size them for memory-hungry jobs without overpaying the web tier, and deploy them on their own cadence. Active Job sits on top unchanged — your `perform_later` calls do not know or care that the queue now lives in ElastiCache instead of Heroku Redis. ElastiCache should be configured for persistence appropriate to your durability needs (Sidekiq jobs are typically acceptable to lose on a hard failure, but Sidekiq Pro/Enterprise reliability features and a replicated ElastiCache setup raise the floor).
Action Cable — Rails' WebSocket layer — needs two things on AWS: the Redis pub/sub backend (the same ElastiCache cluster, set in `config/cable.yml`) and an ALB configured to handle WebSocket upgrades (the Application Load Balancer supports WebSockets natively; just ensure idle timeouts are generous enough for long-lived connections). If you run Action Cable in-process with Puma it scales with the web service; high-volume real-time apps sometimes split it into its own ECS service so connection load does not compete with request handling. Either way the code is unchanged — only `cable.yml` points at ElastiCache.
Scheduled jobs are the piece that must be recreated explicitly, and the classic "we forgot one" failure. If you use `sidekiq-cron`, the schedule lives in Redis/config and rides along on the Sidekiq service with no extra AWS plumbing — the simplest path. If you use the `whenever` gem (which writes a crontab) or Heroku Scheduler, those do not exist on Fargate; the clean replacement is Amazon EventBridge Scheduler firing an ECS run-task (e.g. `rails runner` or a rake task) on a cron expression, or migrating those jobs into sidekiq-cron. Whichever you choose, inventory every scheduled job in the Assess phase — a nightly billing rake task that silently stops firing after cutover is the kind of bug nobody notices until the invoices are late.
The Rails cache store (`:redis_cache_store`) and any rate-limiting (Rack::Attack on Redis) point at the same ElastiCache cluster with a connection-string change. The only judgment call is whether Sidekiq, Action Cable, and the cache share one ElastiCache cluster (fine for most apps, simplest) or get separated for isolation at scale — a sizing decision a partner makes during the Assess phase based on your traffic and durability requirements.
For most Rails apps a single ElastiCache (Redis/Valkey) cluster serves Sidekiq queues, Action Cable pub/sub, and the Rails cache — simplest and cheapest. Separate clusters make sense only at scale or when durability needs diverge (e.g. you want the cache to be evictable but Sidekiq queues to be replicated/persisted). Either way, put Sidekiq on its own Fargate service so workers autoscale on queue depth independently of web.
Serving CSS, JavaScript, and images correctly is where a Rails-on-AWS migration most often shows a visible regression. The rule is simple: precompile at build time, let CloudFront serve the result, and never make Puma serve static files in production.
Whether your app uses Propshaft (the modern Rails default), Sprockets (the classic asset pipeline), or a JavaScript bundler via jsbundling-rails/cssbundling-rails (esbuild, Vite, Tailwind), the production move is the same: `rails assets:precompile` runs in the Docker build, producing fingerprinted, digest-stamped files in `public/assets`. Those files are immutable and cache-forever-safe because the digest changes whenever the content does — which is exactly what makes a CDN effective in front of them.
CloudFront sits in front of asset delivery, with one of two common origins. The simplest is CloudFront → ALB → Rails, with Rails configured so `config.public_file_server.enabled` serves precompiled assets only as a fallback and CloudFront caches them aggressively; this keeps everything in the image and is the least moving parts. The other pattern syncs `public/assets` to an S3 bucket at deploy time and points CloudFront at S3 for assets while the dynamic app stays behind the ALB — slightly more pipeline work, but it takes static-file serving entirely off your Fargate tasks. For Active Storage user uploads, CloudFront fronts the S3 bucket directly (with signed URLs or an origin access control), so image variants and downloads are served from the edge rather than streamed through Rails.
The two regressions to watch for: a missing or mismatched asset host (set `config.asset_host` to the CloudFront domain so `asset_path` helpers emit CDN URLs), and forgetting that `RAILS_SERVE_STATIC_FILES` / `config.public_file_server.enabled` interact with whether Rails will serve assets at all — get the combination wrong and you either double-serve through Puma (slow) or 404 the assets entirely (broken UI). A partner standardizes this so the first deploy looks identical to Heroku rather than shipping a stylesheet-less page to production.
Everything else — containers, the landing zone, Sidekiq, assets — can be built and tested without touching production. The PostgreSQL cutover is the one moment that touches live data, and 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 your current 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/Aurora, 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 (which also brings your `schema_migrations` table, so Rails knows exactly which migrations have run). 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 — including a full Sidekiq run and Action Cable connections — the cutover is short and scripted: enable Rails maintenance mode (or a maintenance page at the ALB) → let DMS drain the last few seconds → flip `DATABASE_URL` to RDS/Aurora and shift traffic to the new Fargate service → update the Route 53 record (TTL lowered ahead of time) → verify writes land in RDS and Sidekiq is draining its queues → exit maintenance. Because the data was already synced, the window is dominated by DNS propagation and verification, not data transfer — commonly 0–15 minutes during a low-traffic period. Pause Sidekiq during the window so jobs are not enqueued against one database and processed against another.
The rollback plan is non-negotiable and simple: keep the old environment (Heroku, the old VPS) 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 the old database 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. A Rails-specific check: confirm the PostgreSQL sequence values came across correctly (DMS handles this, but verify `id` sequences are ahead of the max row id) so you do not get primary-key collisions on the first writes after cutover.
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. Always verify row counts and sequence values before exiting maintenance.
Here is the end-to-end sequence a partner runs, mapped to the AWS MAP phases (Assess → Mobilize → Migrate). For a typical Rails app this is a 3–8 week project — most of it parallelizable, none of it touching production until the final cutover.
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 Rails app; yours vary with traffic, Sidekiq volume, and how aggressively you right-size and commit.
Take a typical Series-A Rails SaaS: web (Puma) across a few dynos, a Sidekiq worker fleet, a clock process, Heroku Postgres Standard/Premier, Heroku Redis, and the usual logging + APM add-ons. On Heroku that commonly totals $1,800–$3,500/month and scales roughly linearly — every new worker dyno and plan bump adds a fixed chunk, and a Sidekiq-heavy app adds workers fast.
The same workload on AWS: web + Sidekiq on Fargate (right-sized, with off-hours scale-down on web and queue-depth autoscaling on workers), RDS/Aurora PostgreSQL with RDS Proxy, an ElastiCache node, S3 + CloudFront for Active Storage and assets, CloudWatch, plus modest data transfer. 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 Rails 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 the old platform + 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 15 minutes of your year.
| Layer | Heroku (before) | AWS at list | AWS w/ Savings Plan | How |
|---|---|---|---|---|
| Web (Puma) | $500–$900 (web dynos) | $200–$400 (Fargate) | $130–$260 | Right-size + off-hours scale-down |
| Sidekiq workers | $500–$1,000 (worker dynos) | $180–$400 (Fargate) | $120–$260 | Autoscale on queue depth |
| PostgreSQL | $400–$900 (HP Standard/Premier) | $250–$550 (RDS/Aurora) | $170–$400 | Reserved/committed capacity + RDS Proxy |
| Redis | $200–$450 (Heroku Redis) | $70–$200 (ElastiCache) | $50–$150 | Single cluster, right-sized node |
| Storage + CDN | $0–$50 (S3 add-on) | $30–$120 (S3 + CloudFront) | $30–$120 | Active Storage on S3, edge delivery |
| Logs / APM | $200–$400 (add-ons) | $40–$120 (CloudWatch) | $40–$120 | CloudWatch Logs + Container Insights |
| Total | $1,800–$3,500 | $900–$1,600 | $700–$1,400 | ≈40–70% lower steady-state |
Most Rails→AWS migrations that go badly go badly for a small, repeatable set of Rails-specific reasons. Naming them up front is the cheapest insurance there is.
The definitive lookup: every part of a Ruby on Rails app, where it lands on AWS, the rough cost direction, and the engineering effort. "Trivial" rows are connection-string or credential swaps; "Medium/High" rows are the real work — and the work a partner does for you.
| Rails component | AWS service | What changes | Cost direction | Effort |
|---|---|---|---|---|
| Web (Puma) process | ECS/Fargate + ALB (or App Runner) | Buildpack → Dockerfile; router → ALB | Down 40–70% | Medium |
| Sidekiq workers | ECS/Fargate service (own service) | Worker dyno → separate autoscaling service | Down 40–70% | Medium |
| PostgreSQL (ActiveRecord) | RDS for PostgreSQL / Aurora PostgreSQL | Connection string + cutover via DMS | Down 30–60% | High (cutover) |
| Redis (Sidekiq / cache) | ElastiCache (Redis OSS / Valkey) | Connection string only | Down 50–70% | Trivial |
| Action Cable (WebSockets) | ElastiCache pub/sub + ALB WebSockets | cable.yml → ElastiCache; ALB upgrade config | Down | Low |
| Active Storage uploads | Amazon S3 + CloudFront | :amazon service in storage.yml; usually already S3 | Down | Trivial |
| Asset pipeline (Propshaft/Sprockets) | Precompile in image → CloudFront | assets:precompile at build; asset_host = CDN | Down | Medium |
| Rails credentials + master key | AWS Secrets Manager | RAILS_MASTER_KEY + secrets → task definition | Negligible | Low |
| Config vars (ENV) | Secrets Manager + SSM Parameter Store | Split secret vs config; inject to task | Negligible | Low |
| Buildpack + Procfile | Dockerfile + ECR | Explicit multi-stage container build | Negligible | Medium |
| Scheduled jobs (whenever/Scheduler) | sidekiq-cron or EventBridge Scheduler | Recreate each job explicitly | Down (near $0) | Low |
| db:migrate on deploy (release phase) | One-off ECS run-task / CodeBuild step | Release phase → gated migration task | Negligible | Low |
| Action Mailer (email) | Amazon SES (or provider direct) | Swap SMTP/API config | Down | Low |
| Logs + APM add-ons | CloudWatch Logs + Container Insights | RAILS_LOG_TO_STDOUT → CloudWatch sink | Down vs add-ons | Low |
| Custom domain + SSL | Route 53 + ACM (free TLS) | DNS + cert on ALB/App Runner | Down (free certs) | Low |
| CI/CD (Heroku Pipelines) | CodePipeline + CodeBuild (or GitHub Actions) | Build image → ECR → update ECS service | Down / neutral | Medium |
Situation: Margin pressure ahead of a Series-B raise, plus two hard technical walls: Postgres connections maxing out at peak (Puma pools × Sidekiq pools) and long CSV-export requests dying at the 30s router timeout. The three-person platform team had no container/AWS experience and could not afford a multi-month DIY migration or a risky big-bang database cutover with Sidekiq jobs in flight.
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 with three services (Puma web behind an ALB, a Sidekiq service autoscaling on queue depth, and a small Action Cable service), Aurora PostgreSQL with RDS Proxy for pooling, ElastiCache for Sidekiq + Action Cable + the Rails cache, S3 + CloudFront for Active Storage and precompiled assets, Rails credentials + RAILS_MASTER_KEY in Secrets Manager with the rest split into Parameter Store, and GitHub Actions → ECR → ECS for deploys. Postgres moved via AWS DMS (full load + CDC); the export endpoints were refactored onto a dedicated Sidekiq queue.
Outcome: Cutover ran in a Sunday-night window with ~12 minutes of write-downtime — Sidekiq paused, DMS had Aurora fully in sync (row counts and sequences verified), so the switch was DNS + verification, not data transfer. Steady-state AWS bill landed at ~$1,250/month (a ~62% cut); the connection ceiling and export 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: ~12 min · monthly spend: $3,300 → $1,250 (−62%) · migration cost to customer: ~$0 (MAP-funded)
CloudRoute routes you to a vetted AWS partner who plans and runs the whole Ruby on Rails migration — Fargate for web + Sidekiq, RDS/Aurora, ElastiCache, S3/CloudFront, Secrets Manager, 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.