A Dockerfile builder for Go that turns a handful of choices — Go version, module path, build target, and runtime base — into a clean multi-stage Dockerfile that ships the smallest practical image. It is aimed at developers who want a correct, cache-friendly starting point without re-deriving the static-binary build flags every time.
How it works
A Go container is built in two stages. The builder stage starts from the official
golang image, copies go.mod and go.sum first so the module download is cached as its
own layer, then copies the source and compiles a binary with CGO_ENABLED=0 and
-ldflags="-s -w". With CGO disabled the result is a fully static binary that depends on
nothing at runtime.
The runtime stage then copies just that binary into a minimal base image. scratch is
an empty image — the binary plus a copy of the CA certificate bundle is all that ships.
distroless/static adds a non-root user and is a good default when you want something
slightly more debuggable than scratch. alpine includes a shell and apk, which you
need when CGO is enabled because the binary then links against musl libc.
Tips and notes
- If you enable CGO, do not use the
scratchruntime — the binary will fail to start because its required C library is missing. Switch toalpineordistroless/base. - Add a
.dockerignorecontaining at least.gitand your local build artifacts so the build context stays small and module caching is not invalidated by unrelated files. - A typical static Go service image built this way lands in the 5–15 MB range, versus
hundreds of megabytes for a single-stage build on the full
golangimage. - For reproducible builds, pin the Go version explicitly (for example
1.22) rather than using a floatinglatesttag.