The best Go hosting platforms in 2026
Go changes the hosting conversation more than any other language. There's no interpreter to install, no virtualenv, no node_modules, no wheel that fails to build. `go build` produces one static binary; you copy it somewhere and run it. Most of what a platform's buildpack does for Python or Node is unnecessary here.
That means the differences between Go hosts are narrower and more specific. Here they are, roughly in the order they'll bite you. I build PandaStack, which is one of the options — flagged below.
The one build decision that matters: cgo
A pure-Go build with CGO_ENABLED=0 is fully static and will run on essentially any Linux, in a scratch container, on any platform, forever. The moment you import something that needs cgo — the classic mattn/go-sqlite3, some image codecs, certain database drivers — you get a dynamically linked binary that depends on the glibc version of the build machine.
That's when 'works on my machine' becomes real: the binary built on Ubuntu 24.04 fails to start on an older base image with a version error about GLIBC. It's also when your build host needs a C toolchain, which not every platform's build image has.
# Fully static, pure-Go DNS resolver — deploys anywhere, no surprises
CGO_ENABLED=0 go build -ldflags='-s -w' -o bin/server ./cmd/server
# Needs cgo: the build image must have a compiler, and the runtime's
# glibc must be at least as new as the build machine's
CGO_ENABLED=1 go build -o bin/server ./cmd/server
# Check what you actually produced
file bin/server # "statically linked" is the answer you wantThe categories
1. Serverless functions
Lambda and friends. Go is unusually good here — cold starts are small because there's no runtime to initialise and no dependency tree to import. If your workload is genuinely request-scoped, this is cheap and fast and the usual serverless cold-start objection barely applies.
The mismatch is the same as always: no long-lived process. Goroutines doing background work, in-memory caches, connection pools you wanted to reuse, and anything holding a connection all stop making sense. Go apps tend to have more of these than Node or Python apps, because the language makes them so easy to write.
2. Container PaaS
Render, Railway, Fly.io, Koyeb, Northflank. Detect go.mod, run go build, run the binary. Nothing exotic. What to check is narrower than for other languages: does the build image have a C toolchain if you need one, does the build cache the module download between deploys, and is the health check pointed at something your router serves.
The build cache is worth real attention. A cold `go mod download` on a large dependency tree adds a minute or two to every deploy, and the difference between a platform that caches modules and one that doesn't is your entire feedback loop.
3. A VM with systemd
Genuinely underrated for Go. A static binary, a systemd unit, and a reverse proxy is a complete, boring, cheap deployment that will run for years. Deploys are scp plus a restart. You own patching and TLS, and if you have one service that's a fair trade.
4. MicroVM platforms
Fly Machines, PandaStack — my category. A PaaS-shaped deploy onto a hardware-isolated VM with its own kernel. For a plain Go API this is not obviously better than a container, and I'll say so. It matters when the service runs code it didn't write, when you need kernel-level capabilities a shared-kernel container won't give you, or when the Go service's job is to orchestrate isolated environments — creating a microVM through snapshot-restore costs about 179ms at p50, which is fast enough for a Go control plane to treat machines as request-scoped objects.
What a Go deploy looks like without a Dockerfile
A repo with a go.mod at the root and a main package is enough for detection. Point the build at a specific command directory if your main isn't at the root — the single most common reason auto-detection picks the wrong thing.
# Detected from go.mod: build, then run the resulting binary
pandastack apps create --name checkout-api \
--git-url https://github.com/acme/checkout
# Explicit, for a repo with several commands under ./cmd
pandastack apps create --name checkout-api \
--git-url https://github.com/acme/checkout \
--build-cmd 'CGO_ENABLED=0 go build -o bin/server ./cmd/server' \
--start-cmd './bin/server'// Read the port from the environment and bind to all interfaces.
// A Go service listening on "localhost:8080" passes every local test
// and is unreachable from the platform's proxy.
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
srv := &http.Server{Addr: ":" + port, Handler: mux}
// Drain on SIGTERM so a deploy doesn't cut in-flight requests.
go func() {
<-sigterm
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
_ = srv.Shutdown(ctx)
}()Four checks before you commit
- Run `file` on your built binary. If it isn't statically linked, you've adopted a glibc dependency and should know it deliberately.
- Time a deploy twice in a row. If the second isn't much faster, the platform isn't caching module downloads and every deploy pays full price.
- Confirm the health check path exists in your router. Go routers 404 unregistered paths cleanly, so a wrong health check produces a permanently unhealthy, perfectly working service.
- Set server timeouts and check memory under a connection flood, not just under request load.
The short version
Go is the language where the platform matters least, so optimise for the things that actually differ: module caching in the build, a C toolchain if you need cgo, and a proxy that doesn't mangle your timeouts. A container PaaS is the default and it's fine. Functions are unusually good for Go if your work is request-scoped. A VM with systemd remains a legitimate, cheap answer. MicroVMs earn their place when isolation is a requirement rather than a preference.
Frequently asked questions
Do I need a Dockerfile to deploy a Go app?
No. A repo with a go.mod and a main package gives any platform with build detection everything it needs: run go build, run the resulting binary. Because Go compiles to a static binary with no runtime to install, the buildpack has far less to do than for Python or Node. Be explicit about which command you build when the repo has several under ./cmd, since auto-detection guesses and sometimes guesses wrong. A Dockerfile is worth adding when you need system packages at runtime, when cgo drags in shared libraries, or when you want the exact same image in CI and production.
What is CGO_ENABLED and why does it break my deploy?
It controls whether the build may link against C code. With CGO_ENABLED=0 you get a fully static binary that runs on any Linux, including a scratch container. With cgo enabled — which happens automatically when you import a package with C bindings, such as mattn/go-sqlite3 or some image codecs — the binary links dynamically against the build machine's C library. If the runtime image has an older glibc than the build machine, the process fails to start with a version error, and if the build image lacks a compiler the build fails outright. Run `file` on the binary to see which one you produced.
Are serverless functions a good fit for Go?
Better than for most languages. Go has no interpreter to start and no dependency tree to import, so cold starts are small and the usual serverless latency objection mostly evaporates. The mismatch is structural rather than performance-related: functions have no long-lived process, so background goroutines, in-memory caches, reusable connection pools, and anything holding a connection stop working as designed. Go programs tend to accumulate exactly those things because the language makes them trivial to write, so audit what your service does between requests before assuming it ports cleanly.
Why is my Go service unreachable when the logs say it started?
Almost certainly it bound to localhost rather than all interfaces. A server started on "localhost:8080" or "127.0.0.1:8080" is reachable only from inside the machine, so it passes every local test and is invisible to the platform's proxy. Use ":"+port so it binds to all interfaces, and read the port from the PORT environment variable rather than hard-coding it. The second most common cause is a health check pointed at a path your router doesn't register — Go routers return a clean 404, the platform marks the instance unhealthy, and a perfectly working service never receives traffic.
Should I set timeouts on Go's HTTP server?
Yes, and it is the most commonly skipped step. Go's default http.Server has no read, write, or idle timeouts, so a client that opens a connection and sends bytes very slowly can hold it open indefinitely. On a public endpoint that is a slow-loris attack with no effort required: connections accumulate, memory climbs, and the service eventually dies in a way that looks like a platform problem. Set ReadHeaderTimeout at an absolute minimum, and ideally ReadTimeout, WriteTimeout, and IdleTimeout as well, sized to your slowest legitimate request rather than to a round number.
Keep reading
- App hosting on PandaStack — go.mod detected, built, and run with no Dockerfile
- Deploy a Go app without a Dockerfile
- Sandboxing an untrusted Go build
- Sandbox APIs for Go agents
- The best Express and Node.js hosting platforms
49ms p50 cold start. Fork, snapshot, and scale to zero.