Static binaries

A statically linked binary carries every library it needs, so it runs on scratch or distroless with nothing else in the image. Go builds one with CGO_ENABLED=0.

How it works

A dynamically linked binary asks the system loader for the C library when it starts. On an image without that library (alpine has musl, scratch has nothing), the kernel answers "exec /api: no such file or directory" although the file is there: what is missing is the loader.

What breaks

go build enables cgo by default when a C compiler is available, as in golang:1.22, and packages like net then link the binary against glibc. Build with CGO_ENABLED=0, or choose a final base with the same C library (distroless/base for glibc).

In a Dockerfile

FROM golang:1.22 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /out/api ./cmd/api

FROM scratch
COPY --from=build /out/api /api
ENTRYPOINT ["/api"]

See it break, then fix it

Write the Dockerfile or the compose file, type the docker commands, and watch a simulated server run them through reboots, upgrades and traffic. Free to start.