$devtoolkit.sh/templates/dockerfile/go

Go Binary Dockerfile Template (Multi-Stage)

Go is arguably the best language for Docker containers because it compiles to a single statically linked binary with no runtime dependencies. This property enables Go Dockerfiles to use scratch (an empty image with nothing but the binary) or Google's distroless images, producing containers as small as 5–15MB — compared to 50–200MB for equivalent Python or Node.js applications. This size difference has real operational implications: faster image pulls, smaller attack surface, and lower container registry storage costs.

The multi-stage build pattern for Go is particularly clean. The build stage uses the full golang:1.22 image to compile the binary with CGO_ENABLED=0 (to ensure static linking) and GOOS=linux GOARCH=amd64 (for the target platform). The final stage copies only the compiled binary into a minimal base image. Because the Go runtime is statically linked into the binary itself, the runtime stage needs nothing else.

CGO_ENABLED=0 is a critical flag for production Go containers. With CGO enabled (the default), the binary dynamically links against the C standard library of the host system. This means a binary compiled in the build stage may fail to run in the runtime stage if they use different C library versions. Setting CGO_ENABLED=0 forces static linking of all Go standard library packages, producing a truly portable binary that runs in any Linux container, including scratch.

The choice between scratch, distroless, and alpine-based runtime images depends on your debugging requirements. scratch contains only your binary — if anything goes wrong, there are no debugging tools available. distroless/static (from Google) adds a minimal set of system files (SSL certificates, timezone data, /etc/passwd) without a shell. Alpine adds a full shell and package manager but adds about 5MB. For production containers where you use external observability tools, distroless is the right choice. For development and debugging-intensive environments, alpine.

Build caching for Go modules can be implemented by copying go.mod and go.sum files before the source code, then running go mod download. This caches the dependency download step separately from compilation, significantly speeding up CI builds when only source code changes.

Template Preview

{"language":"go","goVersion":"1.22","baseImage":"distroless","multiStage":true,"cgoEnabled":false,"nonRootUser":true,"healthCheck":false}

Customize this template with your own details using the free generator:

Open in Generator

FAQ

When should I use scratch vs distroless for a Go container?
Use scratch when your binary has absolutely no dependencies on system files — it needs no TLS certificates, no timezone data, and no /etc/passwd. Use distroless/static-debian12 when your binary makes HTTPS requests (needs CA certificates) or uses time.LoadLocation (needs timezone data). Distroless adds only about 2MB but prevents hard-to-debug failures related to missing system resources.
How do I run a Go container as a non-root user without /etc/passwd?
In a scratch image, you cannot use adduser because there are no system tools. Instead, copy /etc/passwd and /etc/group from the build stage into your scratch image, then set USER to a specific UID number rather than a username. Alternatively, use distroless/static which includes a nonroot user (UID 65532) you can reference with USER nonroot:nonroot.
What is GOARCH and why does it matter in Docker?
GOARCH specifies the target CPU architecture for compilation. If you build on an Apple Silicon Mac (arm64) but target cloud servers (amd64), you must set GOARCH=amd64 in your Dockerfile build stage or your binary will not run on the server. Use --platform linux/amd64 in the FROM instruction or set GOOS=linux GOARCH=amd64 as environment variables in the RUN go build step.

Related Templates

/templates/dockerfile/gov1.0.0