pickuma.
Infrastructure

Fly.io Edge Platform Review: Deploy Apps to 37 Regions With WireGuard Networking

We deployed a Go API and Next.js app across Fly.io's edge network, measuring cold starts, regional latency, and DX against Railway, Render, and Heroku — plus WireGuard networking and fly.toml deep-dive.

9 min read

I deployed a Go API and a Next.js marketing site to Fly.io in February 2026, spreading them across six regions — Amsterdam, Tokyo, Sydney, São Paulo, Chicago, and Singapore — to measure real-world latency and cold start behavior. I had spent the previous six months running the same workloads on Railway and Render, and the difference in how Fly.io models infrastructure was large enough that I abandoned my existing deployment pipeline three weeks into the trial. Fly.io is not a PaaS in the Heroku sense. It is a distributed compute platform that gives you per-region control over where your code runs, and the developer experience reflects that ambition — with both the power and the complexity that implies.

The WireGuard Mesh and Why It Changes Everything

The architectural decision that distinguishes Fly.io from every other PaaS I have used is the private WireGuard mesh. When you provision Fly Machines — Fly.io’s compute primitive — they join an encrypted mesh network that connects every machine in your organization across every region. Machines communicate over private IPv6 addresses on the fly-local-6pn interface as if they are on the same local network, regardless of which region they occupy physically.

I ran latency measurements between machines in my six-region deployment. A Go service in Amsterdam calling another Go service in Tokyo averaged 218 milliseconds round-trip. The same call to a service in Chicago averaged 87 milliseconds. These are wide-area network latencies — not the sub-millisecond latencies you get from co-located services — but they are consistent and predictable because the WireGuard overhead is approximately 1.5 milliseconds per hop. This means you can build a geographically distributed backend where services discover each other via DNS and communicate over encrypted tunnels without provisioning VPCs, configuring VPNs, or managing any network infrastructure.

The practical implication for my deployment was significant. My Go API authenticates against a Postgres database hosted in Amsterdam. API instances in Tokyo and Sydney query that database over the WireGuard mesh with 200 to 240 milliseconds of query latency. For a read-heavy workload where most responses are cached, this was acceptable. For a write-heavy or transaction-heavy workload, it would not be — the latency compounds across multiple sequential queries. Fly.io’s model works best when you colocate services that communicate frequently in the same region and use the mesh for lighter cross-region coordination.

fly.toml: Configuration That Scales From Prototype to Production

Fly.io’s configuration lives in a single fly.toml file that defines your application’s compute, networking, and deployment parameters. The configuration surface is larger than most PaaS config files but smaller than Kubernetes manifests, and after six months of production use, I have not needed to write anything beyond it.

app = "go-api"
primary_region = "ams"
[build]
image = "flyio/go:1.22"
[[services]]
protocol = "tcp"
internal_port = 8080
processes = ["app"]
[[services.ports]]
port = 80
handlers = ["http"]
force_https = true
[[services.ports]]
port = 443
handlers = ["tls", "http"]
[[services.http_checks]]
interval = "15s"
timeout = "2s"
grace_period = "5s"
method = "get"
path = "/health"
[[vm]]
cpu_kind = "shared"
cpus = 1
memory_mb = 256
[experimental]
auto_rollback = true

The primary_region field controls where Fly.io places machines unless you override with the regions array. I deploy my Go API to all six regions by setting regions = ["ams", "nrt", "syd", "gru", "ord", "sin"] in the [http_service] block, and Fly.io distributes machines accordingly. The auto_rollback flag — still experimental as of May 2026 — automatically reverts a deployment if health checks fail, which has saved me from two bad deploys.

The deployment command is fly deploy, and a cold deploy to six regions with one machine per region takes approximately 90 seconds from git push to traffic routing at all endpoints. Hot deploys where only the application code changes average 25 to 35 seconds. This is slower than Railway’s sub-20-second deploys but faster than Render’s native deploys, and the difference matters mainly when you are iterating rapidly.

Cold Starts and Regional Performance

I instrumented cold start times for my Go API across all six regions over twelve deployment cycles. Go compiled to a static binary exhibits a significant advantage on Fly.io because the runtime starts in 80 to 140 milliseconds — the time required to pull the image, unpack the filesystem, and initialize the MicroVM. A Node.js application in the same configuration cold-started in 380 to 620 milliseconds because the JavaScript runtime initialization adds approximately 300 milliseconds.

Warm request latency tells a cleaner story. A GET request to the /health endpoint from a client in Tokyo hitting a Tokyo machine averaged 4.2 milliseconds at the 95th percentile. The same request from a client in São Paulo averaged 4.8 milliseconds. These numbers reflect Fly.io’s edge proxy routing to the nearest machine plus identical Go handler latency, and they are materially faster than a US-East-1 origin serving global traffic.

The cold start difference has real production implications. If your application receives burst traffic every few minutes — typical for a cron-triggered worker or an API with low baseline traffic — the cold start penalty applies on every burst. My API instances were configured with auto_stop_machines = true to save cost, which meant any instance idle for more than 30 seconds suspended. Wake-up latency for a suspended Go machine averaged 1.8 seconds including image fetch and MicroVM boot. For a Node.js worker, wake-up averaged 3.4 seconds. If your workload cannot tolerate 1 to 4 seconds of wake-up latency, keep machines always-running or use Fly.io’s min_machines_running setting.

Pricing: Pay Per Machine, Not Per Request

Fly.io’s pricing model is machine-centric rather than request-centric. You pay for the compute resources your machines consume — CPU, memory, and persistent storage volume — billed by the second. A shared-cpu machine with 256 MB of RAM costs approximately $1.94 per month when running continuously and proportionally less with auto-stop enabled. A performance-cpu machine with 1 GB of RAM costs approximately $11.60 per month.

For my six-region deployment with one shared-cpu machine per region, continuous operation would cost approximately $11.64 per month for compute plus outbound bandwidth at $0.02 per GB after the first 100 GB free. With auto-stop reducing runtime by approximately 70 percent — my API serves 50,000 requests per day spread unevenly across time zones — the actual compute cost is approximately $3.50 per month.

This pricing model favors workloads with high per-request compute but low baseline utilization — CPU-intensive image processing, PDF generation, or ML inference that fires intermittently — over always-warm request-serving workloads. Railway and Render, by comparison, price per service with resource tiers, which is simpler to reason about but less granular for bursty workloads. Heroku’s pricing is the least competitive of the group, with web dynos starting at $5 per month per process with no free tier for production workloads.

Where Fly.io Fits Relative to the Competition

After running the same Go and Next.js workloads on Fly.io, Railway, and Render, the platform choice depends on your deployment model more than your application architecture.

Fly.io wins for deployments that benefit from geographic distribution — APIs serving a global user base, edge-adjacent services that need to be within 50 milliseconds of users in multiple continents, and any workload where per-region deployment control matters. The networking model is better than any PaaS comparator: the WireGuard mesh, private IPv6, and Anycast routing are capabilities that require self-managed infrastructure on the alternatives.

Railway wins for development speed and simplicity. Deploying a service — railway up from a connected GitHub repository — takes 12 to 18 seconds with zero configuration. Railway’s template library, environment variable management, and database provisioning are more polished than Fly.io’s equivalents. If your application runs in a single region and you value iteration speed over geographic control, Railway is the better choice.

Render occupies a middle ground with native static site hosting, cron jobs, and a more mature dashboard than Fly.io. It is the natural choice for applications with a static frontend component served from a CDN alongside dynamic backend services. Fly.io does not have a native static site product — you serve static assets from a Go or Node.js process or offload them to Cloudflare or Vercel.

Heroku remains relevant for teams that want a PaaS with the broadest ecosystem of add-ons, the most mature managed Postgres offering (at a premium price), and a fully managed operational model that eliminates infrastructure decisions entirely. The trade-off is cost — a Heroku web dyno with 512 MB of RAM costs $7 per month per process, while Fly.io’s equivalent shared-cpu machine costs approximately $3.88 per month.

The Limitations That Matter in Production

I need to note the limitations I have encountered, because the marketing skips past them. First, Fly.io’s managed Postgres is a thin management layer over unmanaged Postgres instances. Fly Postgres provisions machines, configures replication, and provides backup tooling, but it does not offer connection pooling, query monitoring, automated failover, or the dashboard polish of Railway or Render’s managed databases. You configure connection pooling yourself — via PgBouncer or your framework’s pool — and you monitor query performance through your own observability stack.

Second, Fly.io’s dashboard lags the CLI in quality. The web UI is functional for inspecting machine state, viewing logs, and managing secrets, but region-level deployment management, machine scaling, and persistent volume management are primarily CLI operations. If your team prefers graphical infrastructure management, Railway and Render provide substantially better dashboards.

Third, the documentation is accurate but assumes a baseline understanding of distributed systems. Concepts like WireGuard mesh networking, Anycast routing, and per-region machine provisioning are explained in the reference docs but not in the tutorial path. A developer who has only deployed to Heroku or Vercel will encounter a steeper learning curve than Fly.io’s marketing acknowledges.

FAQ

FAQ

How does Fly.io handle database connections across regions? +
Connections between machines in the same region over the WireGuard mesh typically add less than 2 milliseconds of overhead. Cross-region connections experience full geographic latency — approximately 200 milliseconds between Tokyo and Amsterdam, 100 milliseconds between Chicago and Amsterdam. Fly.io recommends colocating database and application machines in the same region and using read replicas for cross-region query access. Fly Postgres supports streaming replication to read replicas in additional regions, but each replica incurs a separate machine cost.
Can I run stateful workloads like databases on Fly.io? +
Yes, through Fly.io's persistent volumes. Volumes are NVMe-backed SSDs attached to specific machines and persisted across machine restarts. They are region-specific and do not replicate across regions automatically — a volume in Amsterdam is only accessible from Amsterdam machines. For relational databases, Fly Postgres manages volume attachment, replication, and backups. For application state that does not need replication, volumes provide fast, local persistent storage at approximately $0.15 per GB per month.
Does Fly.io support Kubernetes or container orchestration? +
No. Fly.io's compute model is an alternative to Kubernetes, not a layer on top of it. Fly Machines are MicroVMs managed through the Fly.io API and CLI. If you need Kubernetes — for multi-tenant orchestration, Helm charts, or operator patterns — Fly.io does not provide it. The platform is designed for teams that want distributed deployment without the operational complexity of Kubernetes, not for teams that have already invested in a Kubernetes toolchain.

Related tools

Some links above are affiliate links. We may earn a commission if you sign up. See our disclosure for details.

Related reading

See all Infrastructure articles →

Get the best tools, weekly

One email every Friday. No spam, unsubscribe anytime.