Why do Go binaries make containerizing applications easy?
Quick Answer
Go compiles to a single, statically linked binary by default (as long as cgo isn't used), with no runtime, interpreter, or dynamic library dependencies needed on the target machine — you can copy the binary alone onto a bare Linux system and run it directly. This makes Go an unusually good fit for minimal container images: a multi-stage Dockerfile can build the binary in a full Go SDK image, then copy just that single binary into a scratch or distroless base image with no OS package manager, no shell, and no extra libraries at all, producing images that are often just a few megabytes and have a dramatically smaller attack surface than a typical language runtime's base image.
Detailed Answer
Static linking is the single feature that makes Go's container story unusually simple compared to most other languages.
A minimal multi-stage Dockerfile
# Stage 1: build
FROM golang:1.22 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /myapp .
# Stage 2: minimal runtime image
FROM scratch
COPY --from=builder /myapp /myapp
ENTRYPOINT ["/myapp"]
The final image contains literally nothing except your compiled binary — no shell, no package manager, no OS userland at all, since scratch is an empty base image.
Why CGO_ENABLED=0 matters here
CGO_ENABLED=0 go build -o myapp .
With cgo enabled (the default when a C toolchain is available), your binary may dynamically link against the system's C library, which means it needs a compatible C library present on the target system, breaking the "just copy the binary anywhere" story. Disabling cgo forces a fully static, pure-Go binary with zero external runtime dependencies.
Comparing image sizes, roughly
Typical Node.js app image (node:18-slim base): ~150-250 MB
Typical Python app image (python:3.12-slim base): ~150-200 MB
Typical Go app image (scratch or distroless base): ~10-30 MB, often just a few MB
Why this matters operationally
Smaller images pull faster (meaningfully speeding up deploys and autoscaling events), and a scratch-based image has essentially no attack surface for OS-level vulnerabilities, since there's no OS present at all to have vulnerabilities in. This is a significant, practical operational advantage Go has over languages that require shipping a full interpreter/runtime and its dependencies inside every container image.