Datadog engineers reduced their Go Agent binary size by 77% through dependency auditing, build tag optimization, and eliminating reflection/plugin pitfalls without removing features.
After the Datadog Agent grew from 428 MiB to 1.22 GiB over five years, engineers embarked on a mission to reduce binary size without sacrificing functionality. The result: a 77% reduction achieved through systematic dependency analysis, build optimization, and careful attention to Go's linker behavior.
The Problem: Hidden Bloat in Go Binaries
The Agent's growth was driven by new features, additional integrations, and large third-party dependencies like Kubernetes SDKs. Go's dependency model made this particularly insidious—transitive imports meant that even small changes could pull in hundreds of packages. Network costs increased, resource usage climbed, and the Agent became harder to deploy on resource-constrained platforms.
Strategy: Two-Pronged Dependency Reduction
Datadog engineers developed two complementary approaches to eliminate unnecessary code:
Build Tags for Optional Code
Using //go:build feature_x directives, they excluded optional functionality from builds. This allowed the Agent to remain feature-rich while keeping binaries lean for specific deployments.
Package Isolation By moving code into separate packages, non-optional components stayed minimal. A striking example: relocating a single function into its own package removed approximately 570 packages and 36 MB of generated code from builds that didn't need it.
Tools That Made It Possible
Three Go ecosystem tools proved essential for this audit:
go list- Enumerates all packages used in a buildgoda- Visualizes dependency graphs and import chains to understand why dependencies existgo-size-analyzer- Shows how much space each dependency contributes
These tools transformed an overwhelming task into a systematic process.
The Reflection Trap
A major discovery was how reflection silently disabled linker optimizations. When using non-constant method names, the linker couldn't determine which methods would be used at runtime. This forced it to keep every exported method of every reachable type, along with all dependent symbols—dramatically increasing binary size.
Datadog eliminated dynamic reflection where possible, both in their codebase and in dependencies. This required submitting pull requests to projects like kubernetes/kubernetes, uber-go/dig, and google/go-cmp.
The Plugin Package Pitfall
Another surprising finding: simply importing Go's plugin package caused the linker to treat binaries as dynamically linked. This disabled method dead code elimination and even forced retention of all unexported methods. Removing this import yielded an additional ~20% reduction in some builds.
The Result: 77% Smaller Without Feature Loss
The optimization effort spanned six months but achieved its goal without removing any features. The binary shrank from 1.22 GiB to approximately 280 MiB—a 77% reduction that improved network costs, resource usage, and deployment flexibility.
This case demonstrates that Go binary bloat often stems from hidden behaviors rather than obvious code growth. By understanding how the compiler and linker work, teams can achieve dramatic size reductions while maintaining functionality.
For teams facing similar challenges, the key takeaways are clear: audit dependencies systematically, use build tags for optional features, isolate code into minimal packages, minimize reflection usage, and be cautious with plugin imports. The tools and techniques exist—it just takes methodical application to realize the benefits.

Comments
Please log in or register to join the discussion