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
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)
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.