2025-04-20 Build Info In Go Binaries
I like having assorted build info in my Go binaries and here’s how I do it.
Attention
Shortly after writing this, I learned that as of Go 1.18 (2022), Go embeds source version information like commit hash and build time in binaries by default and you can get it as a BuildSetting
with runtime/debug, so I no longer need to DIY it with -ldflags
at build time, yay!
I first started doing this in 2020 with Go 1.13, so it was a useful approach for two years or so - but this is a great example of the value of reading release notes…!
I’m leaving this here because I think the general technique is potentially helpful, and translateable to languages that don’t just handle it for you.
At the core: Go linker flags, specifically -X importpath.name=value
- “Set the value of the string variable in importpath
named name
to value
.”
Other methods exist but this is the right combination of powerful, resilient, and clear for me.
This lets me do this in my code:
var GIT_VERSION string
var GIT_BRANCH string
var BUILT_AT string
And then this at build time (taken from my Makefile
, hence the $$
):
-ldflags "-X 'main.GIT_VERSION=$$(./git-version)' -X 'main.BUILT_AT=$$(date)' -X 'main.GIT_BRANCH=$$(git symbolic-ref --short HEAD)'"`
That gives me:
BUILT_AT
which is just the build timestamp - useful to make sure I’ve not missed restarting a service to pick up changes etc.GIT_BRANCH
which is, unsurprisingly, the name of the branch - useful to see if I’m running on amain
build, or something from a feature branchGIT_VERSION
which is the output of a short script that gives me a slightly more detailed idea of what I built from
git-version
is:
#!/bin/bash
HEAD_HASH=$(git rev-list -1 HEAD)
GITVERSION=$HEAD_HASH
# https://stackoverflow.com/a/2659808
DIRTY=$(git diff-index --quiet HEAD -- || echo '-DIRTY')
GITVERSION+=$DIRTY
echo "$GITVERSION"
which outputs the commit hash as a string, plus a -DIRTY
suffix if there are uncommitted changes - useful to know exactly what I built from, and/or if there was anything funky - e.g.:
$ ./git-version
ace0fba5eace0fba5eace0fba5eace0fba5e-DIRTY
Then I can simply use those as needed, e.g. for dumping information when running with --version
:
$ ./mybin --version
GIT_VERSION: ace0fba5eace0fba5eace0fba5eace0fba5e-DIRTY
GIT_BRANCH: feature/foo
BUILT_AT: Mon 30 Feb 1712 03:13:37 CEST
I don’t write a huge amount of Go, so it wouldn’t surprise me to learn that there are better ways / room for improvement, but this works nicely for me!
Update
I shared this while noting that I was conscious that my use of BUILT_AT
screwed my build determinism but I hadn’t gotten round to figuring out a better way.
Chris McDonald helpfully pointed me at the SOURCE_DATE_EPOCH environment variable which is exactly for this kind of thing!