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_ATwhich is just the build timestamp - useful to make sure I’ve not missed restarting a service to pick up changes etc.GIT_BRANCHwhich is, unsurprisingly, the name of the branch - useful to see if I’m running on amainbuild, or something from a feature branchGIT_VERSIONwhich 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!