Skip to main content

From $3K Kubernetes to $200 Fly.io: What a Six-Month Reverse-Migration Receipt Actually Proves

11 min readDora NodaDora Noda
Share
On this page

Most migration math only runs in one direction: off the expensive managed platform, onto the cheap box you own. Earlier this year, a small-team write-up ran the numbers the other way — a full exit from self-run Kubernetes at roughly $3,000 a month to Fly.io at roughly $200 a month, with six months of receipts behind it. One two-hour major outage. A few minor ones. A Postgres migration that hurt, and an open question about mixing Fly.io compute with AWS or GCP services for the parts that did not fit.

It is the rare reverse-migration ledger: the before and after bill for the same workload, the observed outage record instead of the SLA brochure, and an honest note about what stayed outside the managed platform. Here is what that ledger contains, where the $200 breaks, and the checklist any team should demand from a migration receipt — including this one.

The receipt, up front

The headline numbers, as the team reported them:

Before (self-run Kubernetes)After (Fly.io)
Monthly bill~$3,000~$200
ShapeManaged K8s cluster, three small services, Postgres read replica, Redis cacheSame services on Fly.io Machines, Postgres on the platform, Redis via managed service
Deploy painCluster upgrades, image-pull failures, 3 a.m. pod restartsfly deploy from a Dockerfile
Observed reliability (6 mo)N/A (the old world)One 2-hour major outage, a few minor ones
Latency noteBaselineSlightly better — no more Kubernetes iptables and CNI overhead on the request path

A 93 percent cost reduction with better latency and fewer pages is the kind of result that should make you suspicious in exactly the way this post is about. So let us interrogate both sides before believing either.

Where the $3,000 actually went

Almost nobody's $3,000 Kubernetes bill is $3,000 of compute. For a small production footprint on AWS EKS, the bill has a fixed floor that runs even if every pod is idle — and that floor is the part that generalizes across teams:

Line itemTypical unit price (us-east-1)Monthly
EKS control plane$0.10/hr, cannot be turned off$73
NAT Gateway (×2 for multi-AZ)~$32.40 each + $0.045/GB processed~$65 + data
Application load balancer~$16 base + capacity charges~$20–50
CloudWatch logs and metrics~$5–12 for a small footprint~$5–12
Fixed floor, zero pods running~$160–200

That is the number to stare at: roughly $160 to $200 a month before a single request is served. Everything above it — worker nodes, EBS volumes, RDS, ElastiCache, data transfer — scales with the workload, and a lean three-service footprint might add $800 to $1,500 there. The author's $3,000 implies a heavier data layer and transfer bill on top (their stack carried a Postgres read replica and Redis alongside the cluster), but the exact composition matters less than the structural point: on self-run Kubernetes, tenancy has a cover charge. You pay the control plane, the NATs, and the load balancer whether you serve ten requests or ten million.

This is also why "just shrink the cluster" rarely fixes the bill. Halve your nodes and the control plane still costs $73, the NATs still cost $65, the ALB still meters. The fixed floor is immovable — it does not scale down with you.

Where the $200 goes, and what breaks it

Fly.io's pricing shape is the inverse: almost no cover charge, everything metered per second. A representative ~$200 stack for the same small production workload looks like this (2026 list prices, always-on, single region):

Line itemUnit priceMonthly
App machines (e.g. 6× shared-cpu, ~1 GB)~$2–7 each depending on size~$30–40
Postgres (self-managed cluster on Machines + volumes)Machines + $0.15/GB volumes~$15–30
Volumes (app + DB, ~100 GB total)$0.15/GB~$15
Dedicated IPv4 addresses~$2 each~$4
Egress (a few hundred GB)~$0.02/GB in NA/EU~$5–15
Redis (managed, pay-as-you-go)Usage-based~$10–30
Headroom: second region / staging / spikes~$50–70
Total~$150–220

Two honest caveats. First, this composition is reconstructed from current list prices, not the author's invoice — treat it as "the $200 is buildable," not "this is their bill." Second, the $200 is a point, not a plateau. It holds for a specific workload shape, and the write-up is admirably clear about the binding constraint: their workload was read-heavy, so Fly.io's Postgres being slower on writes barely registered. Change the shape and the number moves fast:

  • Write-heavy Postgres. Managed Postgres on Fly.io lists around $38 a month for the basic tier and $55–65 for larger plans. That single line item eats a quarter of the budget — and it is still the right call versus self-managing a write-heavy primary to save $20.
  • Egress-heavy workloads. At roughly $0.02/GB, a terabyte of transfer is $20 — fine. Ten terabytes is $200, and the whole migration savings evaporate into the network bill. Video, downloads, and proxy-shaped traffic should do this multiplication first.
  • Multi-region. Every extra region duplicates machines and volumes. Two regions of everything turns $200 into $350 before traffic arrives.
  • GPUs or special hardware. On-demand GPU machines run on the order of a dollar per hour — a single always-on accelerator costs more than the entire $200 stack. The moment the workload needs more than CPUs, this comparison is over.

The general rule: the $200 survives as long as the workload is CPU-shaped, read-shaped, and single-region-shaped. The write-up's workload was all three. Yours may not be.

Six months of observed uptime beats six nines of promised uptime

The most valuable line in the whole write-up is not a price. It is the outage log: six months on the platform, one major outage lasting two hours, a few minor ones, against a status page the author describes as transparent.

Compare that with what a migration decision usually gets: the SLA brochure. "99.95% monthly uptime" sounds precise until you convert it — 99.95% still permits 22 minutes of downtime a month, excludes maintenance windows and single-region failures in the fine print, and pays out in service credits you will never claim. An observed log — one bad afternoon in six months, minors measured in minutes — is worth more than any SLA percentage because it prices the actual failure mode: somebody's Saturday, not somebody's credit.

The public record is consistent with the shape of that claim. Fly.io's 2026 status history shows the pattern of a maturing platform: isolated component incidents (a secrets-service outage around 25 minutes, an app-discovery error stretch near 80 minutes, regional networking wobbles under half an hour) rather than cascading multi-hour platform events — plus the occasional genuinely bad day, which is exactly what the author's two-hour major outage sounds like. No fleet runs clean for six months; the question is whether the provider tells you promptly when it does not.

A transparent status page is operational infrastructure, and it belongs in the ledger as a line item with real value: every honest incident entry is a postmortem you did not have to write yourself at 3 a.m.

The author's other reliability decision deserves equal attention: keep a backup of the app runnable on another platform — plain VMs like AWS Lightsail or DigitalOcean — as a fallback. That is the unglamorous version of multi-cloud: not active-active traffic splitting, just a warm spare and a DNS change. For a team whose entire platform is one provider's control plane, a $10-a-month spare that you test quarterly is the cheapest disaster-recovery policy in existence.

The hybrid question, and when managed still loses

The write-up ends with the right open question: can you run Fly.io compute against AWS or GCP services for the parts that do not fit? The honest answer is yes, with a latency and egress tax. Cross-provider calls add milliseconds per hop and every gigabyte that leaves Fly.io for RDS or BigQuery meters at egress rates. It works fine for slow-path dependencies (a nightly warehouse sync, an external auth provider, object storage with caching in front) and poorly for hot-path ones (per-request queries to a database in another cloud). The hybrid worth wanting is narrow: compute where the margin is, data services where the operational burden is.

More useful is the inverse checklist — the shapes where this migration runs in reverse and managed loses:

  • Write-heavy or large Postgres. Once the database needs provisioned IOPS, point-in-time recovery with real RTOs, and connection counts the managed tiers price steeply, the database becomes the bill and the compute comparison stops mattering.
  • Compliance and data residency. Regulated data with audit, residency, or contractual hosting requirements usually cannot live on shared multi-tenant machines, no matter how cheap.
  • Egress-dominated traffic. See the ten-terabyte arithmetic above. CDNs and bandwidth-commit pricing beat per-GB metering at volume.
  • Special hardware and steady-state scale. GPUs, high-memory shapes, or simply enough always-on CPU that reserved dedicated servers cost less than metered Machines — the own-hardware thesis reasserts itself past a scale threshold, and that threshold is lower than enthusiasts admit.

None of these invalidate the $200 result. They bound it. A migration receipt that does not state its bounds is marketing; one that does is engineering.

How to trust any migration receipt, including this one

Here is the uncomfortable footnote: the source write-up is a single team's self-reported numbers, published on a site that discloses AI-assisted drafting, with sibling posts that tell visibly different variants of the same "$3K to $200" story (one lands on plain EC2 with systemd at $165, not Fly.io at all). The specific Fly.io claims quoted here — the CNI-overhead latency note, the slower-writes caveat, the two-hour outage, the fallback advice — are corroborated in shape by independent pricing and status data, but the invoice itself is not independently auditable.

That does not make it useless. It makes it exactly as trustworthy as most migration posts, which is the problem this checklist solves. Demand all six from any receipt, this one included:

  1. Line items sum to the headline. If the itemized parts add to $500 and the headline says $3,000, something load-bearing is missing (usually data transfer, the data layer, or staging environments). Reject any ledger that cannot be added up.
  2. Same workload on both sides. Traffic, regions, availability posture, data durability — if the "after" quietly drops multi-AZ or the read replica, the savings are partly a downgrade, and should be labeled as one.
  3. Observed uptime, not promised uptime. Six months of incident history beats any SLA percentage. Ask for the status page, not the whitepaper.
  4. Labor and pages priced. Four 3 a.m. pages a month is a cost. Zero is a feature. The on-call delta belongs in the ledger next to the infrastructure delta.
  5. Exclusions stated. Egress, the fallback spare, the external managed database, the CDN — name what is outside the headline number and what each costs.
  6. Bounds declared. The workload shapes that break the result (write-heavy, egress-heavy, multi-region, regulated) are part of the result, not footnotes.

Run the famous quit-your-PaaS posts through this gauntlet and most of them fail at step one: they price the box and never the pages. The reverse direction deserves the same skepticism. A $200 bill with a two-hour outage you slept through can still beat a $3,000 bill with four pages you did not — but only a ledger with all six lines lets you know which one you are buying.


Running this math in reverse — from a metered PaaS back toward infrastructure you own? Bex.co is the open-source, AI-native Render alternative: push a git repo, get a running HTTPS service on machines you own. Use the six-point checklist above on our numbers too — star the repo on GitHub and hold us to it.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex