Skip to main content

From Fly.io to Hetzner in a Weekend: A Migration Playbook That Cuts the Bill 70%

10 min readDora NodaDora Noda
Share
On this page

Fly.io spent 2026 unbundling its bill. Volume snapshots started metering at $0.08/GB-month on January 1, inter-region private-network traffic followed in February, and new accounts get a trial measured in hours, not months. None of these lines is large on its own. Together, they change the answer to a question multi-region teams used to be able to wave off: what does this app actually cost to run, line by line?

This post answers that question with a concrete migration playbook: a typical three-region app on Fly.io — six Machines, Postgres with cross-region replicas, daily volume snapshots — moved over one weekend to a Cluster-API-managed fleet on Hetzner, where inter-region traffic and snapshot storage are already-owned flat capacity. The before bill is about $108/month. The after bill is about $31/month. Here is exactly what moves, what replaces it, and what you give up.

The bill that changed​

For years, Fly.io's pitch to multi-region apps was simple: Machines in 30-plus regions, a global WireGuard mesh (6PN) connecting them with zero config, and volume snapshots you never thought about because they were free. The 2026 pricing notices rewrote the last two clauses.

Starting January 1, 2026, volume snapshot storage bills at $0.08/GB-month (first 10 GB free), pro-rated to the hour, with the first charges landing on the February invoice. Snapshots are incremental — you pay for changed blocks actually stored, not provisioned volume size — and new volumes ship with automatic daily snapshots at 5-day retention. Then, starting February 2026, inter-region private-network usage started billing too, beginning with Managed Postgres traffic: on granular bandwidth rates, cross-region private transfer runs $0.006/GB inside North America and Europe and $0.015/GB in Asia-Pacific, while same-region private traffic stays free. Add the trial terms for new organizations — two total VM hours or seven days, whichever ends first — and the era of "spin it up and look at the invoice later" is over.

Single-region hobby apps barely notice. Multi-region apps with routine snapshot backups notice twice: replication chatter that used to ride free now meters by the gigabyte, and every retained snapshot now has a monthly price.

The reference app and its Fly.io baseline​

To keep this honest, here is the workload we are pricing — a production-shaped SaaS, not a hello-world:

  • App tier: 2 Machines per region × 3 regions (iad, lhr, sin), shared-cpu-1x with 512 MB RAM, always on.
  • Data tier: Postgres primary in iad with async replicas in lhr and sin, shared-cpu-2x with 2 GB RAM each.
  • Storage: 60 GB of Postgres volumes plus 12 GB of app volumes; daily snapshots with roughly 50 GB of retained incremental data.
  • Traffic: 400 GB/month public egress, 300 GB/month of inter-region replication and cache traffic.

Fly.io publishes per-second Machine rates with regional markups (1.0× in iad, 1.21× in lhr, 2.0× in sin). A shared-cpu-1x/512 MB Machine is $0.00000128/second — $3.32/month at 1.0× — and a shared-cpu-2x/2 GB Machine is $0.00000456/second, or $11.82/month. Running the numbers:

Line itemMath$/mo
App Machines (6×)2×$3.32 + 2×$4.02 + 2×$6.64$27.96
Postgres Machines (3×)$11.82 + $14.30 + $23.64$49.76
Volumes (72 GB provisioned)72 × $0.15$10.80
Snapshots (~50 GB stored)(50 − 10 free) × $0.08$3.20
Public egress (400 GB blended)250 × $0.02 + 150 × $0.04$11.00
Inter-region private (300 GB)blended ≈ $0.01$3.00
Dedicated IPv41 × $2$2.00
Total≈ $108

Two things worth noting. First, the two newly unbundled lines — snapshots and inter-region private traffic — are only about $6 of this bill today. They matter not because they dominate, but because they grow with exactly the things a maturing app accumulates: retention windows and cross-region chatter. Second, Fly.io's 40%-off reservation blocks would cut the compute lines materially; with full reservations this baseline drops to roughly $75/month. Keep that number in mind — it is the fair comparison, and Hetzner still wins it by more than 2×.

What actually moves: the primitive map​

Every Fly.io primitive in the baseline has a boring, well-understood replacement on a Hetzner fleet. The table first, then the weekend schedule that executes it.

Fly.io primitiveHetzner / CAPI replacementNotes
Machines (Firecracker microVMs)MachineDeployment via CAPH (Cluster API Provider Hetzner)Declarative replicas instead of fly scale; HCloudMachine templates pin CX22/CAX11 shapes
fly proxy + AnycastGateway API (Gateway + HTTPRoute) on Envoy/Cilium, behind one Hetzner LB11Anycast edge becomes regional LB + GeoDNS; fine for 2–3 locations, not 37
6PN WireGuard mesh + .internal DNSWireGuard mesh (or Cilium ClusterMesh) between nodes + CoreDNSYou own the mesh config now; templates and CAPH bootstrap make it repeatable
Fly VolumesHetzner Cloud Volumes via CSI (€0.044/GB)Same attach-to-one-node semantics; size them like-for-like
Automatic snapshotsHetzner snapshots (€0.011/GB) on a cron + retention policy7× cheaper per GB than Fly's $0.08; retention is your cronjob, not a dashboard toggle
fly secrets / configExternal Secrets or SOPS-encrypted manifests in gitSame twelve-factor shape, GitOps-native
Postgres on MachinesPostgres on StatefulSets (CloudNativePG or plain StatefulSet)Same async-replica topology; pg_basebackup seeds replicas over the new mesh

The honest headline of this table: nothing here is exotic. That is the point. The migration is a weekend project precisely because each row is a solved problem with a standard operator or manifest.

The weekend runbook​

Friday evening — inventory and prep (2 hours). Export everything Fly knows that you will need: fly status, volume IDs and sizes per region, the snapshot retention setting, the full secrets list, and a week's worth of traffic numbers from the dashboard so the after-bill has something to be checked against. Stand up the Hetzner project, install clusterctl with the CAPH provider, and generate the workload-cluster manifests from CAPH's templates. Drop DNS TTLs to 60 seconds. Checkpoint: clusterctl generate renders clean and hcloud lists the API token's project.

Saturday — cluster and data (full day). Apply the Cluster and MachineDeployment manifests — three CX22 nodes to start, split across fsn1 and nbg1 with a worker in Helsinki if you want a third failure domain. Install Cilium for CNI plus WireGuard encryption between nodes, Envoy Gateway for the Gateway API routes, and your Postgres operator. Seed the new primary with pg_basebackup straight from the Fly primary over a temporary WireGuard peering, then let the replicas catch up. Deploy the app, run smoke tests against the LB IP, and mirror a slice of shadow traffic if your router supports it. Checkpoint: pg_stat_replication shows all replicas streaming with lag under one second, and the new stack serves a full login-to-checkout flow.

Sunday — cutover and watch (half day). Freeze writes on the Fly primary for the final sync window (minutes, not hours, if Saturday's seeding held), verify lag is zero, flip DNS to the Hetzner LB, and watch error rates and p99 latency for two hours. Keep the Fly app scaled to zero — not deleted — for a week. Rollback trigger, decided in advance: if sustained error rate exceeds your budget or replica lag will not converge within 30 minutes, flip DNS back; the Fly volumes and snapshots are untouched, so rollback is a DNS change plus fly scale count.

The after bill​

Same workload, Hetzner list prices (post-April-2026):

Line itemMath≈ $/mo
3× CX22 (2 vCPU / 4 GB)3 × ~€5.50$18.00
100 GB Cloud Volumes100 × €0.044$4.80
50 GB snapshots50 × €0.011$0.60
1× LB11 load balancer€7.49$8.20
Traffic (all inside 20 TB included)0$0.00
Total≈ $31

That is $108 → $31, about a 70% cut — and $75 → $31 even against Fly's fully reserved price. Now the sensitivity check, because a single data point is an anecdote:

  • Traffic ×10 (4 TB egress). Fly adds roughly $90–100 in egress and private-transfer charges. Hetzner adds $0 — still inside the 20 TB included with EU servers. This is the line where flat capacity stops being a discount and starts being a different cost structure.
  • Snapshot retention ×2. Fly adds ~$3. Hetzner adds ~$0.60.
  • One more region. Fly multiplies Machines and volumes at that region's markup. Hetzner adds one ~€5.50 node and its volumes.

The honest caveats live in the other direction: Hetzner US and Singapore locations include only 1 TB of transfer, not 20 TB, so an Asia-Pacific-heavy app keeps some metered exposure. And the CX/CAX value lines are EU-only. Price the fleet you actually need, in the locations you actually need.

What you give up​

A 70% saving that hides the tradeoffs is a sales pitch, not a playbook. Here is what the $77/month was buying:

  • A 37-region anycast edge. Hetzner has a handful of locations. If your users genuinely span six continents with single-digit-millisecond budgets, no weekend migration fixes that — you need an edge, and edges cost money. Most B2B SaaS apps serve two or three corridors and pay for 37-region optionality they never use.
  • Zero control-plane ops. CAPH plus Cilium plus Postgres-on-Kubernetes is standard tooling, but it is your tooling now: upgrades, CVE patches, and etcd backups have your name on them. Budget a few hours a month, or this migration trades a bill for a pager.
  • Fly's operational extras. GPU Machines are deprecated past August 2026 anyway, but features like instant multi-region deploys from one CLI and per-second billing for spiky workloads genuinely have no flat-rate equivalent. Bursty, idle-mostly workloads can be cheaper on per-second metering than on always-on servers — model your duty cycle before you move.

When should you stay? If your traffic is spiky with long idle valleys, if you serve regions Hetzner does not have, or if your team has no one willing to own a cluster, Fly's premium is buying something real. The migration math above assumes always-on production workloads and a team that can carry a runbook.

The weekend math​

The pattern behind this playbook generalizes beyond one provider pair: every 2026 unbundling notice — snapshots here, private-network meters there — pushes usage-priced platforms toward bills that scale with success, while owned flat capacity stays flat. That does not make metered platforms wrong; it makes the line-by-line comparison mandatory, once a year, with your real traffic numbers instead of launch-day guesses.

Run the numbers for your own app this weekend. If the bill says move, the runbook above fits in one.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. If the migration above sounds right but you would rather not hand-roll the CAPH manifests, that is exactly the gap bex fills: star the repo on GitHub or deploy your first app today.

Related articles

Check your move before you migrate

Free browser tools: check a render.yaml or your Render scripts against bex, or turn a Heroku app or docker-compose.yml into a draft render.yaml. Nothing you paste leaves your browser.

Open the migration tools