Dockerizing Go Applications
Go's static binaries shine in Docker — build a minimal image with scratch or distroless base, often under 20MB.
Introduction
Go's static binaries shine in Docker — build a minimal image with scratch or distroless base, often under 20MB. Multi-stage Dockerfile compiles in golang image, copies binary to alpine or scratch runtime.
Production Dockerfiles use non-root user, HEALTHCHECK, and .dockerignore excluding test files. CGO_ENABLED=0 produces fully static binary for scratch. Interviewers ask multi-stage builds, image size optimization, and K8s deployment basics.
This lesson provides production Dockerfile, docker-compose for local dev with Postgres, and build flags for reproducible containers.
The story
Cloudflare ships Go binaries in minimal scratch or distroless images — a statically compiled CGO_ENABLED=0 binary needs no libc, shrinking attack surface and image size from hundreds of MB to under 20 MB. Multi-stage Dockerfiles build with golang:1.22 and copy only the binary into the runtime stage.
Health checks, non-root users, and GOOS=linux GOARCH=amd64 cross-compilation are standard checklist items before pushing to ECR or GCR.
Understanding the topic
Key concepts
- Multi-stage: FROM golang AS builder → FROM scratch/distroless.
- CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w".
- COPY --from=builder /app/main /main — minimal runtime image.
- Non-root USER 65534:65534 for security.
- HEALTHCHECK curl or wget /health endpoint.
- .dockerignore excludes vendor, .git, *_test.go.
flowchart LRSource[Go Source] --> Build[go build]Build --> Image[Docker Image]Image --> Registry[ECR / GCR]Registry --> K8s[Kubernetes Pod]
Step-by-step explanation
- Stage 1: copy go.mod, go.sum, download deps, copy source, build.
- Stage 2: copy binary only to minimal base image.
- Expose port; ENTRYPOINT ["/main"].
- docker build -t myapp . && docker run -p 8080:8080 -e DATABASE_URL=...
- docker-compose links app + postgres for local dev.
- Push to ECR/GCR; K8s Deployment pulls image.
Practical code example
Multi-stage Dockerfile for minimal production Go image:
# Dockerfile# Build stageFROM golang:1.22-alpine AS builderWORKDIR /appRUN apk add --no-cache git ca-certificatesCOPY go.mod go.sum ./RUN go mod downloadCOPY . .RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /main ./cmd/api# Runtime stageFROM gcr.io/distroless/static-debian12:nonrootCOPY --from=builder /main /mainCOPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/EXPOSE 8080USER nonroot:nonrootENTRYPOINT ["/main"]# docker-compose.yml snippet# services:# app:# build: .# ports: ["8080:8080"]# environment:# DATABASE_URL: postgres://user:pass@db:5432/app# db:# image: postgres:16-alpine
Line-by-line code explanation
FROM golang:1.22 AS builder— first stage compiles with the full Go toolchain.COPY go.mod go.sum ./thengo mod downloadcaches dependencies in a Docker layer.RUN CGO_ENABLED=0 GOOS=linux go build -o /app/server .produces a static binary.FROM gcr.io/distroless/static-debian12— minimal runtime with no shell.COPY --from=builder /app/server /servercopies only the binary into the final image.USER nonroot:nonrootruns the process without root privileges.HEALTHCHECK CMD ["/server", "-health"]integrates with Docker and orchestrator probes.EXPOSE 8080documents the listening port — does not publish it automatically.
Key takeaway: Distroless has no shell — smaller attack surface. -ldflags -s -w strips debug info. nonroot user is security best practice.
Real-world use
Where you'll use this in production
- Kubernetes Deployment container images.
- CI/CD pipeline build and push to container registry.
- Local dev environment with docker-compose stack.
- AWS Lambda custom runtime with provided.al2023 + bootstrap binary.
Best practices
- Multi-stage build — never ship compiler in production image.
- Pin base image digests for reproducibility.
- Run as non-root user.
- Include ca-certificates for HTTPS outbound calls.
- HEALTHCHECK for orchestrator readiness.
- Use BuildKit cache mounts for go mod download speed.
Common mistakes
- Single stage image — 800MB+ with full Go SDK.
- Running as root in container.
- Forgetting ca-certificates — TLS calls fail in scratch.
- Not using .dockerignore — slow builds copying .git.
- Baking secrets into image layers.
Advanced interview questions
Q1BeginnerWhy multi-stage Dockerfile?
Q2BeginnerCGO_ENABLED=0 purpose?
Q3Intermediatescratch vs alpine?
Q4IntermediateOptimize Docker build cache?
Q5AdvancedDesign CI pipeline building Go Docker image.
Summary
Multi-stage Dockerfile produces minimal static binary images. CGO_ENABLED=0 and distroless base for secure tiny containers. Non-root user and HEALTHCHECK for production readiness. docker-compose for local app + database development. Next lesson: building microservices.