How to leave AWS without rewriting the whole stack: practical 2026 guide

Migrating from AWS to a cheaper cloud (Hetzner/DO) or self-hosted seems like a 1-year project. In practice, you can do it in 6-8 weeks if you map the 12 AWS-only services your stack actually uses.

HeroCtl team··16 min· Read in Portuguese →

Most Brazilian teams thinking about leaving AWS postpone indefinitely because they believe they are facing a project of "rewriting the entire stack". They aren't. It is a mapping project, not a rewrite. And the mapping fits in a twelve-row spreadsheet.

TL;DR — what you'll read in three minutes

A typical Brazilian SaaS stack uses about twelve AWS services, and each of them has a portable alternative that costs between three and seven times less. EC2 becomes VPS at any provider (Hetzner, DigitalOcean, Magalu Cloud). RDS becomes Postgres on a dedicated VPS, Neon or Supabase. ElastiCache becomes self-hosted Valkey. S3 becomes Cloudflare R2 or Backblaze B2 — both with S3-compatible API, so the code doesn't even change. SQS becomes a Redis-based queue or RabbitMQ. Lambda becomes an endpoint on the traditional app server or Cloudflare Workers. ALB becomes the orchestrator's integrated router. CloudFront becomes free Cloudflare. IAM becomes secret injection in the orchestrator.

Realistic schedule for a startup with five to ten applications: six to eight weeks, eighty to one hundred sixty hours of development. Typical savings: three to seven times on the infra bill, with payback in less than a month of senior salary.

Don't migrate if your compliance requires AWS by name, if the team is single and focused on product, or if the stack uses deep lock-in (DynamoDB with specific features, Aurora Serverless v2, complex cross-account IAM).

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Why do so many Brazilian teams postpone leaving AWS?

The honest answer is confusion between two different projects. "Leaving AWS" became a mental synonym for "rewriting the application". It isn't the same thing.

Rewriting the application is changing core technology — relational database for NoSQL, synchronous framework for reactive, monolith for microservices. That does take quarters. Leaving AWS is changing the infra that sustains the application you already have. The domain code stays identical. What changes are database endpoints, credentials, some SDKs and the way to declare deploys.

The confusion lasts because the team looks at the AWS console and sees two hundred services. Nobody uses two hundred. The vast majority uses twelve. Map those twelve, find an alternative for each one, and what's left is execution work — not research.

The twelve AWS services your stack probably uses

The starting spreadsheet is this. Anything outside it in your account is probably satellite — a CloudWatch alarm nobody looks at, a forgotten S3 bucket, a dead Lambda function. Focus on the twelve:

  1. EC2 — virtual machines running app server and workers
  2. RDS — managed relational database (Postgres or MySQL)
  3. ElastiCache — Redis for cache and session
  4. S3 — object storage (uploads, backups, assets)
  5. ALB / NLB — load balancer in front of the EC2s
  6. CloudFront — CDN for static assets
  7. Route 53 — authoritative DNS
  8. SES — transactional email
  9. SQS / SNS — queue and pub-sub
  10. IAM — credentials and roles for services to talk to each other
  11. CloudWatch — metrics and logs
  12. Lambda — serverless functions

If your account has all twelve, congratulations: you are the median stack. If you have eight or nine, better — less to migrate. If you have five very specific services (Aurora Global, DynamoDB with Streams, complex EventBridge), you are on a different path — read the lock-ins section before continuing.

Service-by-service mapping — alternative, cost and complexity

The table below is the shortcut. Each row has expanded detail afterwards.

AWS servicePortable alternativeCost before (R$/month)Cost after (R$/month)Migration complexity
EC2 t3.mediumHetzner CPX21 VPS15044Low
RDS db.t4g.largeSelf-hosted Postgres or Neon70050–250Medium
ElastiCache cache.t4g.microSelf-hosted Valkey7530Medium
S3 (1TB + egress)Cloudflare R260075Low
ALBOrchestrator integrated router1100Medium
CloudFrontFree Cloudflare4000Low
Route 53Cloudflare DNS250Trivial
SESResend or Postmark5075–100Trivial
SQS / SNSRedis Streams or RabbitMQ800 (same VPS)Medium–high
IAMOrchestrator secrets00Medium
CloudWatchPrometheus + Loki2500 (same VPS)Medium
LambdaApp server or Cloudflare Workers2000–60Variable

FX considered: five reais per dollar. Before-costs assume small-medium SaaS stack with five to ten active applications.

EC2 becomes VPS at any provider

The most obvious migration. EC2 t3.medium costs about thirty dollars monthly — one hundred fifty reais. Hetzner CPX21 with the same CPU class and more RAM costs seven euros and ninety-nine — forty-four reais. DigitalOcean sits in the middle. Magalu Cloud is competitive for those prioritizing invoice in real and data on national soil.

The technical path is provisioning the VPS, running your existing Ansible (or a simple bootstrap script), copying the EC2 snapshot or bringing up the image from scratch. For each server, count two to four hours. It is not the time-consuming part of the migration.

RDS becomes self-hosted Postgres or Neon/Supabase

There are three honest paths here. The first is Postgres running on a dedicated VPS, with automated backup via pg_dump in cron and physical replication to a secondary in another region. Costs the price of the VPS — fifty to one hundred reais monthly — to replace an RDS of seven hundred.

The second is Neon. Serverless Postgres with branching, automatic ramp-up, generous free plan, paid plans starting at five dollars. Useful for those wanting to abandon AWS without taking on direct database operation.

The third is Supabase, which delivers Postgres with additional APIs (auth, realtime, storage) and a permanent free tier. Makes sense for startups that tolerate Supabase coupling in exchange for simplicity.

The migration itself is pg_dump followed by restore at destination, with a short maintenance window — usually minutes, not hours, with logical replication working — or logical replication with cutover almost without downtime if your Postgres is version 13 or higher. Four to eight hours depending on base size.

ElastiCache becomes self-hosted Valkey

Redis became Valkey after the license change in 2024 — fork maintained by the Linux Foundation. Runs on any VPS in two clicks. Thirty reais monthly replace ElastiCache of seventy-five.

The migration has two stages. First, bring up the Valkey cluster with Sentinel for automatic failover. Second, populate the cache — script that reads from AWS and writes at the destination, or simply let the application populate organically after cutover (cache cold start of a few minutes). Three to six hours of work.

S3 becomes Cloudflare R2 (or Backblaze B2)

This is the most immediate gain. Cloudflare R2 charges zero for egress — the most expensive slice of S3 when you serve assets to users. Fifteen cents of dollar per GB stored, against twenty-three cents of standard S3. Backblaze B2 is an almost identical alternative, with even cheaper integration for heavy backup workloads.

Technical migration is trivial: rclone copy s3:my-bucket r2:my-bucket in parallel. One terabyte transfers in around twelve hours depending on bandwidth. The application code changes exactly one line — the S3 client endpoint. Every AWS SDK library accepts custom endpoint configuration; R2 and B2 implement the identical S3 protocol.

Typical volume of medium SaaS (fifty GB of user uploads): R$75 monthly on R2 against R$600 on S3 with active egress. The savings pay a week of migration work in the first month.

ALB becomes orchestrator integrated router

If you are using ALB, it is because you have multiple EC2s behind it. The alternative is the router embedded in the chosen orchestrator — HeroCtl, Caddy, or the router embedded in other self-hosted stacks. The orchestrator discovers running containers, opens ports, terminates TLS via automatic Let's Encrypt, distributes traffic.

The migration swaps the AWS target group definition for an ingress definition in the orchestrator manifest. Four to eight hours to understand the right rules. One hundred ten reais saved monthly per balancer, and the orchestrator accepts however many hosts you want without additional charge.

CloudFront becomes free Cloudflare

This deserves a highlight mention. CloudFront charges per GB transferred — those who serve video or heavy downloads bleed. Cloudflare offers free global CDN on the free plan, with configurable cache, basic DDoS mitigation and rudimentary WAF. For most SaaS cases, it is more than enough.

The migration is changing the domain's nameservers to Cloudflare and configuring cache rules. Two to four hours. The savings can be massive — four hundred reais monthly for those with average traffic volume, thousands for those with high volume.

Route 53 becomes Cloudflare DNS

DNS at Cloudflare is free and faster than Route 53 in most public measurements. Migration is exporting the zone file, importing in Cloudflare, validating records, changing nameservers at the registrar. Thirty minutes. Twenty-five reais monthly that come back to the cash flow.

SES becomes Resend, Postmark or Mailgun

AWS is cheap for volume sending, but SES deliverability requires IP warming and reputation configuration that takes time. Resend charges twenty dollars for fifty thousand monthly emails and has superior deliverability out of the box. Postmark charges fifteen for ten thousand. Mailgun covers the case of those sending lots of non-transactional volume.

The migration is changing SMTP credentials in the app — one hour of work.

SQS and SNS become Redis Streams or RabbitMQ

The most delicate migration. SQS is a service that does one thing and does it well; replacing it requires choosing queue technology and refactoring producer and consumer.

The shortest path is Redis Streams, especially if you are already running Valkey for cache. Libraries like Sidekiq (Ruby), BullMQ (Node), RQ (Python) and Asynq (Go) consume Redis natively. RabbitMQ is more robust for complex routing scenarios. NATS is a modern alternative for pub-sub.

For each queue, count one to three days depending on complexity. Simple background job queues are trivial. Queues with fan-out, dead letter queues and custom visibility timeout require more care. Eighty reais monthly saved, and the queue runs on the same VPS as the cache — zero additional in infra.

IAM becomes orchestrator secrets

Here is the non-obvious migration that catches many AWS-experienced teams off guard. On AWS, the application accesses S3 and RDS without explicit credentials in the code — the EC2 inherits an IAM role and the SDK fetches tokens automatically. Outside AWS, that disappears.

The solution is secret injection by the orchestrator. HeroCtl, k3s and similar accept secrets as first-class resources — you declare DATABASE_URL or S3_ACCESS_KEY in the job manifest and the orchestrator injects as environment variable in the container. For more sophisticated scenarios, self-hosted HashiCorp Vault does automatic rotation.

The migration is refactoring each IAM role into a set of explicit credentials, created at the destination provider (Cloudflare API token, specific Postgres user, etc.), and declared as secrets. Four to eight hours for a medium stack.

CloudWatch becomes Prometheus + Loki

Metrics become Prometheus + Grafana. Logs become Loki + Grafana. Everything runs in containers in the same cluster. Two hundred fifty reais monthly of CloudWatch become zero additional.

Initial configuration takes about four hours to be productive: Prometheus with service discovery pointing to the orchestrator agents, Loki receiving via Promtail or directly from the container runtime, Grafana with basic dashboards. There are dedicated posts about this migration on the blog.

Lambda — the hardest part

Lambda is the service with the largest variance of complexity in migration. Depends entirely on how you are using it.

Simple HTTP Lambda (API Gateway → Lambda → response) is trivial. Becomes an endpoint on your app server. The function code changes little — framework handler in place of Lambda handler. One to two hours per function.

Event-driven Lambda (S3 triggers Lambda, SQS triggers Lambda, EventBridge schedules Lambda) is the expensive part. For S3 events, R2 offers events via Cloudflare Workers — you rewrite the Lambda as a Worker and keep the pattern. For SQS, becomes a consumer on the app server. For scheduled EventBridge, becomes a cron in the orchestrator.

Worst scenario: complex Lambda with chained EventBridge, Step Functions and dead letter queues. Here it is redesign. Reserve a week or two and design a simpler event model — usually the system gets better.

Realistic six-to-eight-week schedule

Order matters. Starting with the database is temptation and trap — database is last to migrate, not first.

Week 1 — Inventory and decision. List the twelve services, note current cost, identify integrations between them. Choose alternative for each. One-page document with the mapping table. No code yet.

Week 2 — Provisioning destination in parallel. Bring up the VPS, install the orchestrator (HeroCtl or similar), configure test DNS pointing to a subdomain. Bring up Postgres, Valkey, Cloudflare R2. Everything empty. Smoke test: a "hello world" running.

Week 3 — Storage migration. S3 to R2 with rclone. Usually slow (volume) but very low risk. Application still reads from S3, but you validate that R2 is synchronized. By end of week, dual-write — application writes to both.

Week 4 — Database migration. Logical Postgres replica from RDS to destination. Cutover in a short maintenance window — usually minutes, not hours, with logical replication working. Application points to new database. RDS stays as hot standby for a week.

Week 5 — Web application migration. Apps running on EC2 become jobs in the orchestrator. Integrated router plays the ALB role. DNS points to the orchestrator (or to Cloudflare in front of it). Gradual cutover using weighted DNS.

Week 6 — Queues and async jobs. SQS leaves, Redis Streams or RabbitMQ enters. Workers run in the orchestrator. Period of dual-consume to ensure no message is dropped.

Week 7 — Lambdas and event-driven workloads. The most variable week. HTTP Lambdas migrate quickly. Event-driven Lambdas require the redesign discussed above. If you have more than ten complex Lambdas, consider extending to two weeks.

Week 8 — Final cutover, intensive monitoring, decommission. Cloudflare in front replaces CloudFront. Route 53 becomes Cloudflare DNS. CloudWatch goes to Prometheus + Loki. Last thing: turn off the old EC2s and close the AWS account — or leave a minimum balance if you still keep some residual service.

The five lock-ins that hurt most in the migration

Honesty matters: not everything migrates easily. Five things require extra work and sometimes change project viability:

  1. DynamoDB with specific features. GSI, Streams, scan limits, TTL. There is no direct equivalent. The realistic path is redesign to Postgres with JSONB, or to a self-hosted NoSQL (FoundationDB, ScyllaDB) — re-architecture, not migration.
  2. Aurora-only features. Aurora Serverless v2 with auto-scaling of connections, Aurora Global Database, Aurora I/O optimized. Self-hosted Postgres does almost everything, but doesn't have the instant auto-scaling. For spiky workloads, consider Neon (which offers a similar pattern).
  3. Complex cross-service IAM. Teams using cross-account IAM roles, Service Control Policies and hierarchical account organization have access control embedded in the architecture. Migrating requires reimplementing the hierarchy elsewhere — Vault, Cloudflare Access, or orchestrator secret injection. Count days, not hours.
  4. Lambda + complex EventBridge. Event pipelines with multiple hops, retries, dead letter queues. Doesn't migrate as is. Redesign around queues (RabbitMQ, NATS) and persistent workers. Usually the system gets simpler — but takes time.
  5. S3 events triggering Lambda. Very common pattern, and R2 with Cloudflare Workers covers most cases. For workloads that need exactly-once guarantee or strong ordering, switch to a queue pattern — producer writes event to queue when file is confirmed, worker consumes.

The savings calculation, without optimism

Typical Brazilian SaaS scenario with five applications:

Before on AWS:

  • Five EC2 t3.medium: R$750
  • RDS db.t4g.large Multi-AZ: R$1,400
  • ElastiCache cache.t4g.micro: R$75
  • S3 with 100GB and average egress: R$300
  • ALB: R$110
  • CloudFront with average volume: R$400
  • Route 53 + SES: R$75
  • CloudWatch logs/metrics: R$250
  • Lambda with average volume: R$200
  • NAT Gateway: R$200
  • Total: R$3,760/month = R$45,120/year

After self-hosted:

  • Four Hetzner CPX21 VPS with orchestrator: R$176
  • Self-hosted Postgres (included on the VPS): R$0
  • Valkey (included): R$0
  • Cloudflare R2 50GB with unlimited egress: R$75
  • Cloudflare CDN + DNS: R$0
  • Resend for email: R$100
  • Prometheus + Loki (included): R$0
  • Queue workers (included): R$0
  • Total: R$351/month = R$4,212/year

Savings: R$3,409/month, R$40,908/year. Roughly one month of senior engineer salary.

The migration consumes eighty to one hundred sixty hours. In senior internal dev hours, between sixteen and thirty-two thousand reais. Payback in five to ten months, with perpetual savings afterwards.

The most non-obvious migration: secrets and credentials

Worth repeating, because it is what most surprises an AWS-experienced team. On AWS you access S3 without credentials in code — the EC2's IAM role resolves it. Access RDS via IAM authentication. Access parameter store via IAM. The team loses awareness that this "magic" exists.

Outside AWS, every credential is explicit. The application needs:

  • Access key and secret for R2 (created in Cloudflare panel)
  • Connection string with user and password for Postgres
  • Valkey URL with password
  • API key for Resend
  • Token for Cloudflare API if you automate DNS

The orchestrator solution is to declare all of that as secrets injected into the container as environment variables. The secret is encrypted at rest in the orchestrator and never appears in logs. For automatic rotation and sophisticated audit, self-hosted Vault enters the game — but most teams don't need it.

Plan: make a spreadsheet with all the credentials each app needs, create each at the destination provider, declare as secret in the orchestrator, inject into the container. Four to eight hours for a medium stack.

When NOT to migrate (honest profiles)

Four situations where leaving AWS is the wrong decision:

Compliance that lists AWS by name. FedRAMP, ITAR, certain American government contracts and some financial certifications require infra to run on pre-approved components — and most lists include AWS, GCP, Azure, and few additional providers. If your client is an American federal agency, AWS resolves a slice of compliance that would cost months to replicate elsewhere.

Single team focused on product. If you are the only dev and are building the product, eight weeks redirected to migration kill roadmap. Do it when you have the second dev, or when AWS costs come to represent a significant slice of MRR. Before that, AWS is expensive but buyable.

AWS costs below 2% of MRR. Bill of one thousand reais monthly for a startup billing one hundred thousand. The savings are real but the effort isn't worth the focus. Migrate when the bill exceeds five to ten percent of MRR — there the gain covers the lost opportunity.

Deep lock-in in DynamoDB or Aurora Serverless v2. Already addressed above. If half your architecture is DynamoDB with Streams, you don't migrate — you re-architect. That's a different project, with different scope, different decision.

Hybrid strategy — alternative for those not wanting to migrate everything

Teams with fifty or more applications on AWS rarely migrate in block. Hybrid strategy works better:

  • Keep on AWS what is expensive to move (Aurora with specific features, critical Lambda, DynamoDB)
  • Move what is cheap to move and expensive to maintain (S3 → R2, CloudFront → Cloudflare, non-critical EC2 → VPS)
  • Establish VPN or private connection between the two endpoints
  • Partial savings but zero risk of radical migration

Typical result: cut of forty to sixty percent of the AWS bill, without touching critical pieces. For a company paying fifty thousand monthly, that is twenty to thirty thousand back — and the rest migrates organically over the following twelve months, as teams rewrite components for other reasons.

HeroCtl as destination — what changes in practice

HeroCtl is a container orchestrator that runs on any Linux server with Docker. Four VPS running HeroCtl deliver an operational experience close to what you would have with managed ECS — without managed billing, without lock-in.

What it replaces:

  • ALB becomes the HeroCtl integrated router, with automatic Let's Encrypt TLS
  • Partial CloudWatch becomes embedded metrics and native centralized logs
  • RDS automated backups becomes managed backup on Business Edition
  • IAM roles in apps becomes secret injection in the job manifest

What stays the same: Docker running your app exactly as it runs on ECS. Environment variables, healthchecks, rolling deploys, multi-replicas. The application doesn't notice the difference.

There are three plans. Community is permanent free, no server or job limit — runs the entire stack described above including real high availability, router, certificates, metrics and logs. Business adds SSO, granular RBAC, detailed auditing, managed backup and SLA support — useful for those who already have formal platform requirements. Enterprise adds source code escrow, 24×7 support and dedicated development. Business and Enterprise pricing is published on the plans page, without mandatory "talk to sales".

The public demo cluster runs on four servers and coordinator election happens in around seven seconds when the current node falls — measured number, not estimated.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Questions we get about leaving AWS

How long does it really take to migrate a medium stack?

For a startup with five to ten applications, without deep lock-ins: six to eight weeks with a senior dev devoting half time, or three to four weeks with full dedication. Larger stacks or with complex event-driven Lambdas: three to four months. Stacks with critical DynamoDB or Aurora Serverless v2: turn it into a re-architecture project, six-month timeline or more.

Does DynamoDB have a good alternative?

There is no identical substitute. The honest options are: Postgres with JSONB for most cases (resolves eighty percent of DynamoDB uses with excellent performance), self-hosted ScyllaDB or Cassandra for workloads that really need distributed NoSQL, FoundationDB for those needing distributed transactions. None of these is "change the connection string and done" — they require changes in the data model.

Can I keep AWS for the database and move compute?

Yes, and it is the most common hybrid strategy. Aurora or RDS stays on AWS, EC2s become Hetzner or DigitalOcean VPS, S3 becomes R2. You open VPN between the two endpoints and the app continues accessing RDS via private endpoint. Savings typically of fifty to seventy percent of the AWS bill.

S3 → R2: how much does it cost to transfer 1TB?

R2 charges zero for ingress. AWS charges for S3 egress — approximately nine cents of dollar per GB on the first 10 TB. One terabyte costs about ninety dollars to leave AWS, R$450. Transfer time: twelve to twenty-four hours with parallelized rclone, depending on bandwidth. After migration, R$75 monthly storing 50GB with unlimited egress, against R$600 for the same on S3 with active traffic.

Lambda — how to migrate event-driven?

Depends on the trigger. S3 triggering Lambda becomes R2 with Cloudflare Workers (same pattern, no radical change). SQS triggering Lambda becomes a persistent worker on the app server, consuming from the queue — usually simpler code than the original Lambda. Scheduled EventBridge becomes cron in the orchestrator. EventBridge with complex rules and chained Step Functions requires redesign — design the flow around a central queue with consumer workers, becomes more auditable.

RDS Multi-AZ → self-hosted Postgres is reliable?

Postgres with physical streaming replication and failover via Patroni reaches reliability close to RDS Multi-AZ — provided the team knows how to operate. If nobody on the team masters Postgres in production, the safest path is Neon or Supabase, which deliver managed Postgres with free tier. For teams with SRE or DBA, self-hosted is viable and saves substantially. For teams without that competence, the savings don't compensate the risk — pay for managed.

Email SES → who is cheaper?

Depends on volume. Up to 10k monthly emails, Postmark at US$15 delivers much more (superior deliverability, better dashboard, responsive support). Between 50k and 100k monthly, Resend at US$20 is the best cost-benefit. Above 500k monthly, Mailgun or Amazon SES compete on price — and SES might make sense to keep even after migrating the rest. Email is one of the few AWS services that can be rational to keep.

DNS — all Cloudflare or mix?

Cloudflare resolves DNS, CDN, DDoS, WAF and workers on the free plan. For most stacks, concentrating everything there simplifies operation and cuts cost. The exception is compliance that requires geographic provider separation — some governance frameworks ask for DNS and CDN to be from distinct providers. In that case, Cloudflare DNS + Bunny CDN (or Fastly) fulfills the separation.

Does LGPD compliance change anything?

LGPD doesn't require hosting on Brazilian soil. It requires that you know where the data is and that you have an adequate contract with the operator. Hetzner (Germany), DigitalOcean (multiple regions), Cloudflare R2 (multi-region) and Magalu Cloud (Brazil) are all LGPD-compatible provided the contract is in order. For those preferring data on national soil due to client preference, Magalu Cloud is the direct alternative.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Concrete next step

If you got this far, the next step is the spreadsheet. List the twelve services, mark which your stack uses, note current cost of each, choose alternative. In an afternoon you know if migration is worth the effort.

When you are ready to provision the destination:

curl -sSL get.heroctl.com/install.sh | sh

Runs on any Linux server with Docker. The first three become quorum for the replicated control plane. You submit jobs via CLI, API or embedded web panel. The cluster decides where to run, does health check, manages rolling deploys, issues Let's Encrypt certificates automatically.

For additional context on cost and architecture, also read AWS ECS vs Kubernetes vs self-hosted and How much does it cost to host a Brazilian SaaS in 2026.

The migration is more annoying than difficult. The hard part is deciding to start.

#aws#migration#cost#exit#guide