Containerization (Docker)
Containerization: Multi-Stage Builds
Go compiles to a static binary. This is its deployment superpower. You do not need the Go compiler on your production server.
The Multi-Stage Pattern
We use a “Builder” image to compile, and a “Scratch” (or distroless) image to run.
# Stage 1: Build
FROM golang:1.26-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# CGO_ENABLED=0 is critical for static binaries (no libc dependency)
# -ldflags="-w -s" strips debug info for smaller size
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o myapp ./cmd/server
# Stage 2: Runtime
# "scratch" is an empty image (0 bytes).
# "gcr.io/distroless/static" is better (includes CA Certs and Timezone data).
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/myapp /myapp
ENTRYPOINT ["/myapp"]Why this matters
- Size: The final image is ~10MB (Binary) + 2MB (OS overhead). Compared to Node/Python images (500MB+), this is tiny.
- Security: The runtime image has no shell (
/bin/sh). Attackers cannot “shell into” your container because there is no shell. - Speed: Pulling and starting a 12MB image is instant.
Gotchas
- CA Certificates: If your app talks to HTTPS APIs,
scratchwill fail because it lacks Root CA certs. Usedistroless/staticor manually copy/etc/ssl/certsfrom the builder. - Timezones: Similarly,
scratchlackstzdata. If you rely ontime.LoadLocation, copy the timezone database.