Go (Golang) Tutorial 0/45 lessons ~6 min read Lesson 40

    Dockerizing Go Applications

    Go's static binaries shine in Docker — build a minimal image with scratch or distroless base, often under 20MB.

    Course progress0%
    Focus
    10 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    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.
    text
    flowchart LR
    Source[Go Source] --> Build[go build]
    Build --> Image[Docker Image]
    Image --> Registry[ECR / GCR]
    Registry --> K8s[Kubernetes Pod]

    Step-by-step explanation

    1. Stage 1: copy go.mod, go.sum, download deps, copy source, build.
    2. Stage 2: copy binary only to minimal base image.
    3. Expose port; ENTRYPOINT ["/main"].
    4. docker build -t myapp . && docker run -p 8080:8080 -e DATABASE_URL=...
    5. docker-compose links app + postgres for local dev.
    6. Push to ECR/GCR; K8s Deployment pulls image.

    Practical code example

    Multi-stage Dockerfile for minimal production Go image:

    go
    # Dockerfile
    # Build stage
    FROM golang:1.22-alpine AS builder
    WORKDIR /app
    RUN apk add --no-cache git ca-certificates
    COPY go.mod go.sum ./
    RUN go mod download
    COPY . .
    RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /main ./cmd/api
    # Runtime stage
    FROM gcr.io/distroless/static-debian12:nonroot
    COPY --from=builder /main /main
    COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
    EXPOSE 8080
    USER nonroot:nonroot
    ENTRYPOINT ["/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 ./ then go mod download caches 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 /server copies only the binary into the final image.
    • USER nonroot:nonroot runs the process without root privileges.
    • HEALTHCHECK CMD ["/server", "-health"] integrates with Docker and orchestrator probes.
    • EXPOSE 8080 documents 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?
    Build with full SDK; runtime only binary — tiny secure image.
    Q2BeginnerCGO_ENABLED=0 purpose?
    Pure Go static binary without C deps — runs on scratch/distroless.
    Q3Intermediatescratch vs alpine?
    scratch empty — smallest; alpine has shell+apk — easier debug.
    Q4IntermediateOptimize Docker build cache?
    Copy go.mod/sum first, download deps, then copy source.
    Q5AdvancedDesign CI pipeline building Go Docker image.
    lint → test -race → build binary → docker build → scan → push tagged digest → deploy to staging.

    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.

    Ready to mark this lesson complete?Track your journey across the entire course.