One of our core design constraints was: the agent binary must work on any modern Linux x86_64 distribution without requiring the user to install any dependencies — not even libc. This post walks through achieving a sub-12MB fully static binary.
Why Go?
Go was the obvious choice. Its cross-compilation story is excellent, it produces single-binary executables, its GC is efficient for a long-running daemon, and critically — with CGO disabled — it produces fully static binaries with no shared library dependencies.
The CGO Problem
By default, Go links certain stdlib functions against glibc — particularly DNS resolution and user lookups. A binary compiled on Ubuntu won't run on Alpine (which uses musl libc). The fix: compile with CGO_ENABLED=0, forcing Go to use its pure-Go implementations.
Keeping the Binary Small
Our initial binary was 18MB. We brought it under 12MB through:
- Build flags:
-ldflags="-s -w"strips debug symbols and DWARF info - Dependency pruning: audited every dependency and replaced heavy libraries with stdlib alternatives
- UPX compression: release binary compressed to ~4MB
Cross-Compilation
Our CI pipeline compiles for linux/amd64 and linux/arm64 via GitHub Actions:
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o dist/opsot-agent-amd64 ./cmd/agent
We run every release binary on Ubuntu 20.04, 22.04, Debian 11, CentOS Stream 9, Alpine 3.18, and Amazon Linux 2023 in a matrix CI job. The binary runs identically on all of them.