How do you build a minimal, secure Docker image for a Go service (multi-stage build, scratch/distroless, CGO_ENABLED=0, CA certs, time zone data)?
Compile in a full Go image, then copy only the static binary into a tiny runtime image. CGO_ENABLED=0 produces a fully static binary with no libc dependency. Without it, the pure-Go net and os/user code is not guaranteed and the binary may not run on scratch.
# syntax=docker/dockerfile:1
FROM golang:1.25 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod go mod download
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/app ./cmd/api
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/app /app
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/app"]
Distroless static already includes CA certificates, /etc/passwd with a nonroot user, and tzdata. With scratch you must copy these in yourself: COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/. Without them, every outbound HTTPS call fails with "x509: certificate signed by unknown authority".
For time zones, you can embed the database in the binary with import _ "time/tzdata" or -tags timetzdata (about 450 KB). Otherwise time.LoadLocation("Europe/Berlin") fails on scratch.
Hardening:
- Run as non-root with a read-only root filesystem.
- Pin base images by digest.
- Scan with
govulncheckand an image scanner. -trimpathremoves local paths from the binary for reproducible builds.- Build info is kept, so
go version -m appstill lists module versions.
Gotcha: -s -w strips symbols, which makes core dumps harder to analyze. Keep an unstripped build artifact. Since Go 1.25, GOMAXPROCS respects the container's cgroup CPU limit automatically, so older code that sets it by hand (for example with automaxprocs) can drop that.
More on Observability, Debugging & Production Operations
- Q569How do you keep protobuf messages backward and forward compatible? Why must you never reuse field numbers?
- Q570How do you implement health checks (liveness vs readiness) for a Go service running in Kubernetes, and how do they interact with graceful shutdown?
- Q572How do you manage configuration in Go (env vars, files, flags)? How do you validate it and reload it safely at run time?
- Q573How do you configure TLS correctly in Go (crypto/tls MinVersion, certificate reloading, mTLS)?
- Q574How do you implement WebSockets in Go? How do you handle concurrent writes, ping/pong keepalives and backpressure?
- Q575What do runtime/debug.SetMaxStack, SetMaxThreads, SetGCPercent and SetMemoryLimit do, and when would you use them?