Docker and Go: Advanced Application Containerization

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction

\n

Containerizing Go applications with Docker is the standard of modern development. However, basic Dockerfiles are often far from ideal: images become heavy, builds are slow, and in production, problems arise with logging, signals, and healthchecks. In this article, we will cover advanced techniques: from multi-stage builds to fine-tuning networks and volumes. You will learn how to make a Go application image minimal, secure, and production-ready.

\n\n

1. Multi-stage builds: a minimal image in seconds

\n

Go compiles into a static binary, which is ideal for containerization. However, if you use the full golang:1.22 image for running, you are dragging along the compiler, package manager, and system utilities. The solution is a multi-stage build.

\n\n

Basic Dockerfile with multi-stage

\n
# Stage 1: build\nFROM golang:1.22-alpine AS builder\nWORKDIR /app\nCOPY go.mod go.sum ./\nRUN go mod download\nCOPY . .\nRUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/main ./cmd/\n\n# Stage 2: minimal image\nFROM alpine:3.19\nRUN apk add --no-cache ca-certificates tzdata\nCOPY --from=builder /app/main /usr/local/bin/main\nEXPOSE 8080\nENTRYPOINT ["/usr/local/bin/main"]
\n\n

Why does this work?

\n
    \n
  • Alpine — 5 MB, minimal set of utilities.
  • \n
  • CGO_ENABLED=0 — disables C dependencies, the binary becomes static.
  • \n
  • -ldflags="-s -w" — removes debug information, reducing the binary size by 30-40%.
  • \n
  • ca-certificates and tzdata — for HTTPS requests and correct time.
  • \n
\n\n

Result: an image of 10-15 MB instead of 800+ MB. Build time — 15-20 seconds (with dependency caching).

\n\n

2. Optimizing caching and layers

\n

Docker caches layers. If you copy all the code before go mod download, any change in .go files invalidates the dependency cache. The correct order:

\n\n
COPY go.mod go.sum ./\nRUN go mod download\nCOPY . .
\n\n

Also use .dockerignore:

\n
.git\n*.md\ntest/\ncoverage/\n.env\nDockerfile
\n\n

This excludes unnecessary files from the build context, speeding up transfer to the Docker daemon.

\n\n

Advanced trick: caching modules

\n

If you use Go modules, you can mount the cache:

\n
FROM golang:1.22-alpine AS builder\nWORKDIR /app\nRUN --mount=type=cache,target=/go/pkg/mod \\\n    --mount=type=cache,target=/root/.cache/go-build \\\n    go build -ldflags="-s -w" -o /app/main ./cmd/
\n\n

This preserves downloaded modules and build artifacts between docker build runs.

\n\n

3. Healthchecks, signals, and graceful shutdown

\n

Docker can check the container's health. Add to the Dockerfile:

\n\n
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\\n    CMD ["/usr/local/bin/main", "health"]
\n\n

In the Go code, implement the /healthz endpoint:

\n
package main\n\nimport (\n    "net/http"\n    "os"\n    "os/signal"\n    "syscall"\n)\n\nfunc main() {\n    http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {\n        w.WriteHeader(http.StatusOK)\n        w.Write([]byte("ok"))\n    })\n\n    srv := &http.Server{Addr: ":8080"}\n\n    // Graceful shutdown\n    go func() {\n        sigint := make(chan os.Signal, 1)\n        signal.Notify(sigint, syscall.SIGTERM, syscall.SIGINT)\n        <-sigint\n        srv.Shutdown(nil)\n    }()\n\n    srv.ListenAndServe()\n}
\n\n

Important: The Go application must correctly handle SIGTERM (which Docker sends when stopping a container). Without graceful shutdown, you risk losing data or leaving connections open.

\n\n

4. Docker Compose for production: networks, volumes, and resource limits

Blogs

Book Recommendations