How do you cross-compile Go and produce a fully static binary for a scratch container?
Question 428MediumGo 1.22 to 1.25
The Go toolchain can cross-compile to every supported target: set GOOS/GOARCH (and GOARM/GOAMD64 for micro-architecture levels). cgo is disabled by default when cross-compiling, and since Go 1.20 also whenever no C compiler is found.
go tool dist list # all supported GOOS/GOARCH pairs
GOOS=linux GOARCH=arm64 go build -o bin/app ./cmd/app
GOOS=windows GOARCH=amd64 go build -o app.exe ./cmd/app
# static, reproducible, smaller:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOAMD64=v3 \
go build -trimpath -ldflags="-s -w" -o app ./cmd/app
# Dockerfile final stage
FROM scratch
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /app /app
ENTRYPOINT ["/app"]
Gotchas for scratch images:
- No CA certificates means TLS fails. Copy them, or import
golang.org/x/crypto/x509roots/fallback. - No
/usr/share/zoneinfo. Importtime/tzdataor build with-tags timetzdata. - No
/etc/passwd, soos/userlookups fail. - With cgo on, the binary links against glibc dynamically. Check with
ldd/file.
Cross-compiling with cgo needs a cross C toolchain (CC=aarch64-linux-gnu-gcc) or zig cc.
More on Modules, Packages & Tooling
- Q426What are the tradeoffs of using cgo? When would you avoid it?
- Q427What are the cgo pointer-passing rules, and how does runtime.Pinner help?
- Q429How do you inject a version string at build time with -ldflags? What does this program print?
- Q430What build metadata does a Go binary carry, and how do you make builds reproducible?
- Q431What does go vet catch? What does it report for the code below?
- Q432How do staticcheck and golangci-lint differ from go vet? How would you set up linting for a team?