Static Compilation of Go Programs
One of the advertised features of Go is its ability to build self-contained static executables. This gives us the ability to develop on Arch and deploy on Debian, or even Windows and ARM platforms.
As it turns out, in some cases Go does in fact create dynamically linked executables that expect specific versions of libraries. These are not portable. This happens because Go may use the C compiler even when we do not expect it.
Why does this happen?
$ ldd web linux-vdso.so.1 (0x00007fffce3d2000) libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007f0d8f33d000) libc.so.6 => /usr/lib/libc.so.6 (0x00007f0d8f171000) /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f0d8f385000)
The developer of GoatCounter pointed out that certain standard Go libraries use C bindings to provide extra functionality.
When these are used directly or indirectly they have the result of
gccgo being used and the executable is dynamically linked by default. However, we can ask them to stay in a pure Go implementation.
Static compilation recipe
go build -tags osusergo go build -tags netgo go build -tags osusergo,netgo
One of these build flags will instruct the troubling library to stick to pure Go and give us the static executable we expect.
$ ldd web not a dynamic executable
I think that’s good enough for me but there are options to consider:
CGO_ENABLED=0 go build
This should have the same result. Finally, if we actually do intend to use the C library, we can pass the static compilation flag to the C compiler:
go build -ldflags="-extldflags=-static"
But in that case the executable will not be able to pick up any security updates of the linked libraries. We still have the option of building a dynamically linked executable in a container.