Dockerizing a Go App (Multi-stage)

Dockerizing Go services requires reproducible builds, minimal images, safe runtime settings, and production-friendly configuration. This lesson covers multi-stage builds, distroless options, CA certs, non-root users, and deployment pitfalls.

On this page

Dockerizing a Go App: Production Ready Containerization

Go is an excellent fit for containerized environments because it produces static binaries and starts fast. However, production Docker images require more than running go build and copying a binary. You must design reproducible builds, minimal runtime images, secure defaults, and predictable configuration.

This lesson focuses on production container patterns for Go services.

Real Production Failure: Works Locally, Fails in Cluster

A Go service worked locally but failed in Kubernetes. The root causes were missing CA certificates in the runtime image and incorrect timezone data. Outbound HTTPS requests failed and timestamps were inconsistent in logs.

Lesson: minimal images require explicit runtime dependencies such as CA certs and timezone data.

Goals of a Good Go Docker Image

  • Small image size to speed deployments
  • Reproducible builds and pinned toolchain
  • Fast startup and predictable behavior
  • Secure runtime: non-root, read-only where possible
  • Correct networking: graceful shutdown, health endpoints
  • Clear config: env vars, flags, secrets

Multi Stage Build: The Standard Pattern

Multi stage builds compile in a builder image and copy only the binary into a clean runtime image.

FROM golang:1.22 AS builder
WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o server ./cmd/server

Key ideas:

  • Separate dependency download for caching
  • Use trimpath and ldflags to reduce binary size
  • Disable CGO when possible to simplify runtime

Runtime Image Options

Option 1: Scratch

Smallest possible image. But you must provide CA certs if you use HTTPS, and optionally timezone data.

FROM scratch
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]

Scratch is great for pure internal services without TLS outbound calls, but many services need CA certs.

Option 2: Distroless

Distroless images contain minimal OS files needed for running, including certs in many cases. They reduce attack surface while avoiding scratch pitfalls.

For production, distroless is a strong default choice.

Option 3: Alpine

Alpine is small but uses musl. If you enable CGO or rely on native libs, be careful. For CGO_ENABLED=0 static binaries, Alpine works fine but you must still handle certs and timezone needs.

Include CA Certificates

If your service calls HTTPS endpoints, it needs CA certificates. If using scratch, copy cert bundle from builder or use a base that includes certs.

Builder to runtime copy approach:

# in builder
RUN update-ca-certificates

Then copy CA bundle if your runtime needs it. Many teams avoid this complexity by using distroless.

Non Root User

Running as root increases risk. Run as a non-root user where possible.

In Kubernetes, set securityContext and in Docker use USER directive if base supports it.

Example runtime hardening goals:

  • Run as non-root UID
  • Drop capabilities
  • Read-only root filesystem
  • Write only to mounted volumes

Expose Ports and Bind Address

Bind to 0.0.0.0 inside containers, not localhost, otherwise it will not accept traffic from outside the container network.

srv := &http.Server{Addr: ":8080"}

Graceful Shutdown in Containers

Containers receive SIGTERM on stop or during orchestrated rollout. Implement graceful shutdown so in-flight requests drain.

Production expectations:

  • Stop accepting new traffic
  • Finish in-flight requests
  • Close DB last

Health Endpoints and Probes

Expose liveness and readiness endpoints. Kubernetes uses them to route traffic and restart unhealthy pods.

  • /live should be cheap and dependency-free
  • /ready should reflect ability to serve traffic

Configuration and Secrets

Containers should be configured via environment variables or config files mounted as volumes, not by rebuilding images.

Production rules:

  • Never bake secrets into the image
  • Use env vars or secret mounts
  • Validate config at startup and fail fast

Build Reproducibility

Pin Go version in the build stage and ensure go.mod and go.sum are committed. Use go mod download for deterministic dependencies.

Prefer building with:

  • trimpath to remove local paths
  • ldflags to remove debug symbols where appropriate

Cross Compilation Notes

Use GOOS and GOARCH for Linux containers. For multi-arch images, build with buildx and push manifest lists. Ensure your binary matches the cluster architecture.

Common Dockerfile Mistakes

  • Copying the full source into runtime image
  • Using latest tags without pinning versions
  • Binding to localhost inside container
  • Missing CA certificates for HTTPS
  • No graceful shutdown handling
  • Running as root without need
  • Logging to files instead of stdout

Logging in Containers

Write logs to stdout as structured logs. Let the platform ship and store logs. Do not rotate logs inside containers unless you have a special requirement.

Image Size Optimization Tips

  • Use multi-stage builds
  • Disable CGO when possible
  • Remove debug symbols
  • Prefer distroless or scratch runtime
  • Avoid including build tools in runtime image

Operational Checklist

  • Multi-stage build used
  • Go version pinned
  • Runtime image minimal and includes needed certs
  • Runs as non-root
  • Exposes metrics and health endpoints
  • Graceful shutdown implemented
  • Config via env vars and secrets externalized
  • Logs to stdout in structured format

Final Perspective

Dockerizing a Go app is about operational correctness and security, not only packaging. A production-ready image is minimal, reproducible, and hardened. When built with disciplined defaults and integrated with health checks, metrics, and graceful shutdown, Go services deploy safely and scale predictably in modern container platforms.