Go 1.21 will (likely) have a static toolchain on Linux

April 7, 2023

A while back, I lamented on the Fediverse:

Current status: yak shaving a Go 1.17 built on Ubuntu 20.04 so I can build Go 1.20 on 20.04 so I can build a binary with Go 1.20 that will run on 20.04 for reasons.

The easy way to solve this problem would have been to download an official binary release tarball, because these are built so that they'll run on pretty much any Linux (presumably on a system with a very old glibc, since they're actually dynamically linked, with glibc symbol versioning only requiring 2.3.2 or later). Because I already had a whole set of Go source trees, I picked the hard way.

At this point you might wonder why the Go toolchain is dynamically linked against the system glibc. Although I haven't tried to analyze symbol usage, the obvious assumption is that it's dynamically linked because various Go tools want to download packages over the network, which requires looking up DNS names, which is a very common cause of dynamically linking to glibc.

The good news, as pointed out to me by @magical, is that in Go 1.21 and later the plan is for the compiler to be built using the pure Go resolver only and to be a static executable. Relevant reading here is apparently issue #53862 and issue #57007 (via). As far as I know, the elements of this plan have already landed in the Go development version; my current development Go binaries are static binaries.

; ./go version
go version devel go1.21-66cac9e1e4 Fri Apr 7 23:34:21 2023 +0000 linux/amd64
; ldd ./go
        not a dynamic executable

Unless the Go developers revert this for some reason, Go 1.21 and later will be static executables on Linux.

(Doing this before Go 1.21 is tricky for reasons beyond the scope of this entry.)

This is a nice little quality of life improvement for people (like me) mostly working on recent Linuxes but who periodically have to deal with older ones. It won't automatically make your own programs version-independent, but for them you can use '-tags osusergo,netgo' when you build or install Go programs.

(You might wonder why I didn't just build the Go program I needed to run on Ubuntu 20.04 with those flags in the first place. The answer is that I was distracted by the flow of circumstances. First I tried to run the program on a 20.04 machine in addition to some 22.04 ones, and got glibc version errors, so I tried to rebuild it on 20.04 to be more universal, then I had the 'go' compiler toolchain not work with the same problem, and by that point my mental focus was on 'make the compiler toolchain work'.)

Comments on this page:

By rwoodsmall at 2023-04-08 01:53:15:

I ended up rolling my own static Go toolchain(s) against musl libc specifically to get around some of the pain points with glibc. Might be of interest:

If you don't need any cgo, setting CGO_ENABLED=0 and static linking against musl should likely be enough - for most of the utilities I build, I want a static binary without any hooks into glibc/nss. With cgo, which might be what you're after to use the glibc DNS stack, etc., I'd build the latest and greatest Go against musl, then distribute that and use that to build a "native" version against whatever system compilers+glibc. A musl static toolchain specified via GOROOT_BOOTSTRAP is able to compile a cgo-enabled glibc toolchain as long as CC and CXX are set correctly to the OS compilers.

Hope this helps at least a bit!

Really glad that someone tracked this down and that the Go toolchain can level-up. The static linking promise with CGO was always something that I was skeptical of, in part because we had to solve that problem specifically for RKE2 (leveraging the work by Darren and Erik to make it work for k3s we ran into an issue with newer-at-the-time Gotoolchains we're emitting elf-interpreter-dependent executables, so, I, uh, rebuilt Go and GoBoring on Alpine).

See https://github.com/rancher/image-build-base/commit/6d723fda3553f177efd0e835f7d6295919346979

By Jon Forrest at 2023-04-08 20:23:59:

A static toolchain for Go 1.21 would be nice but I'm trying to use Go to create dynamically-linked executables. The default, statically-linked, is fine but for reasons that admittedly aren't that important, dynamically-linked would be nice too.

I started a thread on Go-Nuts a while back describing what happens when I tried to follow the official directions for creating dynamically-linked executables. If you're interested checkout


Written on 07 April 2023.
« ZFS On Linux (still) needs better ways to control the ZFS ARC
A Prometheus Alertmanager alert grouping conundrum »

Page tools: View Source, View Normal, Add Comment.
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Fri Apr 7 23:20:23 2023
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.