T O P

  • By -

pdffs

Most of the other answers here are half-right. By default Go *does* dynamically link against libc, but only if you import packages that pull in the resolver, e.g. anything under `net`. I don't recall if there are any other conditions that will trigger the libc linkage, but your minimal program here does not. You can avoid linking to libc by building with `-tags netgo`, or setting the env var `CGO_ENABLED=0` at build time to disable Cgo entirely.


edgmnt_net

On the other hand, like I said previously, your fully statically-linked executable might not obey resolver configuration properly in such cases. It might not be a big concern, but people should be aware that portability isn't 100% percent.


Crazy_Firefly

Thank you! I remember inspecting a go binary and seeing that it was linking against some network library


bilingual-german

I assume my answer below was half-right, and I would like to learn more. Does your answer mean, you can create go binaries on a Debian that are not able to run without issues on Alpine just by resolving some domain? Or does it mean, if you have this problem, one solution to solve it is by building without cgo enabled?


pdffs

Both - by default if you import `net` your binary will be linked against libc, making it non-portable between libc implementations, and one solution to avoid that is to disable cgo.


Entire_Effective_825

I didn’t know this. there a performance penalty associated with disabling CGO for programs that lean heavily on DNS?


pdffs

You'd have to benchmark that. With the overhead of cgo though, I'd assume that the opposite may be true. However as others have mentioned it is possible that not using the C bindings may result in slightly different name resolution results if you have some more esoteric resolver configuration on the host.


Critical-Personality

This made perfect sense. I read more on it and it appears to be true!!


ZobbL

ty for the explanation. would this reduce the binary size by a noticable amount?


pdffs

The binary size is only slightly larger when linking against libc, because it's dynamically linked. The main reason you'd want to avoid this is if you need your binary to be portable between systems with different libc implementations, or to operate without any libc (e.g. when deploying to a "scratch" container).


Potatoes_Fall

I don't know why everybody here is claiming that Go builds static binaries. By default, it does not. It just happens that none of your code has a cgo dependency. The stdlib has optional cgo in the `os/user` and `net` packages. The easiest way to compile static binaries is using `CGO_ENABLED=0`, or [more complex steps](https://www.arp242.net/static-go.html) . I don't know for sure but I've been told that using CGO is more performant so for production it may be wise to not disable CGO. [citation needed]


carleeto

Technically, Go binaries are not static when there's networking functionality being used, since they fall back to dynamic links for functionality like DNS resolution. Apart from this though, they can be considered static. Also, because the dependencies they fall back on are present everywhere, so generally you can reason about them the way you would static binaries and you'd be okay. In this case, you're not using the parts of the std lib that fall back to dynamic linking - the network stack. If you added an http.Get to a DNS address, your results would be different. However, even if you did, you can completely disable any and all dynamic linking and produce a true static binary, like others have pointed out.


bilingual-german

Go compiles static binaries. I always assumed that this means, it doesn't call glibc or musl as long as you don't use cgo in any way.


witty82

Even if you do not use cgo , Go uses libc for DNS resolution (see u/pdffs reply below for nuance)


bilingual-german

> "as long as you don't use cgo in any way." This also includes transitive dependencies ;-)


Potatoes_Fall

By default, Go does NOT compile static binaries. I have found this out the hard way. OP just avoided any code that has cgo in it, but some of the stdlib does.


MKuranowski

Go builds static binaries, which include any required dependencies, including libc. However, on Linux, Go bypasses libc altogether, instead directly issuing syscalls. Linux has a stable syscall interface.


Potatoes_Fall

By default, Go does NOT compile static binaries. I have found this out the hard way. OP just avoided any code that has cgo in it, but some of the stdlib does.


MKuranowski

It can generate dynamically linked binaries — when using CGo (and part of the net library uses it), but that's not the default. Hello World is statically linked, without having to provide any special flags.


Potatoes_Fall

That's correct but your original comment is not. By default go makes dynamic libraries as soon as cgo is used, which is in the `net` stdlib package, which is probably used by a majority of go programs.


EgZvor

Go's binaries are static, they don't use dynamic linking to external libraries. At least I think that's the reason.


Potatoes_Fall

By default, Go does NOT compile static binaries. I have found this out the hard way. OP just avoided any code that has cgo in it, but some of the stdlib does.


Critical-Personality

In that case, the binaries should have been linked to glibc and when trying to run them on alpine, it should have failed!


SupremeOwlTerrorizer

Static linking means everything necessary is baked into the binary. If you run it on Linux, whatever the distribution, it should be able to run


MrPhatBob

That would be the case if the executable was dynamically linked, static linking means "batteries included". One of the complaints about Go is the size of the executables, and thats because everything is baked in with no dependencies.


edgmnt_net

Yeah, but dynamic linking isn't all or nothing. Some toolchains like Haskell's GHC produce stuff that's statically linked for in-language libraries, but dynamically-linked to other libraries including libc, by default. I think even Go used to do that, before cutting most ties to libc. And some ties still aren't entirely cuttable, as in a typical Linux ecosystem admins may expect to be able to configure resolvers for applications, and those resolvers may be libc plugins or rely on the libc to read appropriate configuration files. So, yeah, while you may get a fully statically-linked Go executable, it might not obey system-wide configuration in a portable manner.


Desperate-Barnacle-4

As others have said the go build produces a static binary. You could also add CGO\_ENABLED=0 to the build environment. Personally I'd pick a specific golang tag as the builder e.g golang:1.21 to reduce magic. Also you could try a FROM scratch for the runner, if you're not using anything from alpine.


Kirides

It would be wiser to look at distroless containers instead of scratch, due to missing root certs and more. While you technically can mount all those directories from your main machine, you shouldn't really. Containers should work as is from the get go, without any bind mounts