You can probably disable cgo: Go's stdlib has pure-Go implementations

CGO_ENABLED defaults to 1. That means a standard go build produces a binary that links against C libraries (e.g., glibc) at runtime. For many parts of the Go standard library, there is a C-backed implementation and a pure-Go implementation. CGO_ENABLED selects which one gets compiled in. Pure-Go alternatives also exist for many third-party libraries, so it is likely you can turn off cgo by setting CGO_ENABLED=0.

Whether to use C libraries or not is a build-time decision, not a runtime fallback like "foo.so doesn't exist, fall back to pure-Go". If the required .so is missing when the cgo binary runs, the dynamic linker fails immediately with an error like:

1./dns-with-cgo: error while loading shared libraries: libresolv.so.2: cannot open shared object file: No such file or directory

Example: DNS resolution with net.LookupHost

With cgo enabled, net.LookupHost delegates to glibc's getaddrinfo via libresolv.so.2. With cgo disabled, the same call uses Go's built-in resolver that reads /etc/resolv.conf directly, with no C library involved. Same API either way.

 1package main
 2
 3import (
 4    "fmt"
 5    "net"
 6)
 7
 8func main() {
 9    addrs, err := net.LookupHost("amazon.com")
10    if err != nil {
11        fmt.Println("error:", err)
12        return
13    }
14    fmt.Println("resolved:", addrs)
15}

Build it both ways:

1# cgo on (default)
2go build -o dns-with-cgo .
3
4# cgo off
5CGO_ENABLED=0 go build -o dns-no-cgo .

ldd shows the difference

 1% file dns-with-cgo
 2dns-with-cgo: ELF 64-bit LSB executable, x86-64, version 1 (SYSV),     
 3dynamically linked (uses shared libs), BuildID[sha1]=4d51b80894921ca4be742c64a7dd76c4c7205697, not stripped
 4
 5% ldd dns-with-cgo 
 6        linux-vdso.so.1 (0x00007ffd8efb9000)
 7        libresolv.so.2 => /lib64/libresolv.so.2 (0x00007f5bc44b8000)
 8        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f5bc429a000)
 9        libc.so.6 => /lib64/libc.so.6 (0x00007f5bc3eed000)
10        /lib64/ld-linux-x86-64.so.2 (0x00007f5bc46ce000)
11
12% file dns-no-cgo 
13dns-no-cgo: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), 
14statically linked, BuildID[sha1]=e8c17c6cb53d034c052336acd4b83a09527bd22c, not stripped
15
16% ldd dns-no-cgo 
17        not a dynamic executable

The cgo binary dynamically links against libresolv.so.2, libc.so.6, and other glibc libraries. The CGO_ENABLED=0 binary is fully static — no shared libraries at all.

strace confirms it at runtime

 1# cgo on: loads libresolv.so.2, then delegates to glibc
 2% strace -e trace=openat -f ./dns-with-cgo 2>&1 | grep -E "(resolv|\.conf)"
 3openat(AT_FDCWD, "/lib64/libresolv.so.2", O_RDONLY|O_CLOEXEC) = 3 # <-- glibc dependency
 4[pid  1553] openat(AT_FDCWD, "/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 5
 5[pid  1553] openat(AT_FDCWD, "/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 5
 6resolved: [98.82.161.185 98.87.170.74 98.87.170.71]
 7
 8# cgo off: Go reads config directly, no .so loaded
 9strace -e trace=openat -f ./dns-no-cgo 2>&1 | grep -E "(resolv|\.conf)"
10[pid  2326] openat(AT_FDCWD, "/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 5
11[pid  2326] openat(AT_FDCWD, "/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 5
12resolved: [98.82.161.185 98.87.170.74 98.87.170.71]

With cgo, the first thing that happens is loading the C library. Without cgo, Go skips straight to reading the resolver config itself.

Why CGO_ENABLED=0 matters

A cgo binary built on one Linux may depend on libc.so.6 being present and ABI-compatible on the target. That's usually fine, but it's an implicit runtime dependency you may not notice until deployment fails on a minimal OS, or an OS with incompatible libc versions, or inside a scratch container image with no libc. CGO_ENABLED=0 removes that dependency entirely. The binary becomes fully static and runs on any Linux system regardless of what libc is installed.

Conclusion

Go's pure-Go implementations are not always full replacements. The pure-Go DNS resolver doesn't support all NSS plugins, so environments relying on custom NSS modules for service discovery or LDAP may see different behavior. For standard DNS over /etc/resolv.conf, it works fine.

Wherever the stdlib has both a cgo and a pure-Go path, CGO_ENABLED=0 at build time opts into the pure-Go one. You get a simpler, more portable binary at the cost of potentially narrower feature coverage in those specific areas. Just remember the choice is made when you run go build, not when the binary runs on the target machine.