DEV Community

Cover image for The Complete Docker + Go Guide: From Beginner to Kubernetes Expert
Stanley Chege Thuita
Stanley Chege Thuita

Posted on

The Complete Docker + Go Guide: From Beginner to Kubernetes Expert

Click To Jump to your Favorite Topic Bellow:


Beginner: Why Docker + Go?

Why Docker + Go?

  • Tiny images (even 5–10 MB)
  • Fast startup (microseconds)
  • Predictable memory usage
  • No "works on my machine" bugs

Install Docker

Skip if Docker is already installed.

Mac / Windows

Docker Desktop

Linux

sudo apt install docker.io
Enter fullscreen mode Exit fullscreen mode

Verify installation:

docker --version
Enter fullscreen mode Exit fullscreen mode

Your First Go Container

2.1 Simple Go Program

main.go

package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, Docker!")
    })

    http.ListenAndServe(":8080", nil)
}
Enter fullscreen mode Exit fullscreen mode

2.2 Write a Dockerfile

Dockerfile

FROM golang:1.22

WORKDIR /app

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

COPY . .

RUN go build -o myapp .

EXPOSE 8080

CMD ["./myapp"]
Enter fullscreen mode Exit fullscreen mode

2.3 Build & Run

docker build -t go-web .

docker run -p 8080:8080 go-web
Enter fullscreen mode Exit fullscreen mode

Visit:

http://localhost:8080
Enter fullscreen mode Exit fullscreen mode

2.4 Basic Commands

docker ps
docker stop <container>
docker rm <container>
docker images
docker rmi go-web
Enter fullscreen mode Exit fullscreen mode

Intermediate: Optimizing Builds & Caching

3.1 Leverage Docker Layer Caching

Dependencies change less frequently than application code.

✅ Good

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

COPY . .
Enter fullscreen mode Exit fullscreen mode

❌ Bad

COPY . .
RUN go mod download
Enter fullscreen mode Exit fullscreen mode

3.2 Use .dockerignore

.git
*.log
tmp/
*.test
coverage.out
Dockerfile
README.md
Enter fullscreen mode Exit fullscreen mode

3.3 Build with Tags & Versions

docker build -t myapp:1.0.0 .

docker tag myapp:1.0.0 myapp:latest
Enter fullscreen mode Exit fullscreen mode

Multi-Stage Builds (The Right Way)

Go binaries don't need the Go compiler at runtime.

4.1 Two-Stage Dockerfile

## Stage 1: Build
FROM golang:1.22 AS builder

WORKDIR /build

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

COPY . .

RUN CGO_ENABLED=0 GOOS=linux \
    go build -ldflags="-s -w" \
    -o myapp .

## Stage 2: Run
FROM alpine:latest

RUN apk --no-cache add ca-certificates

WORKDIR /root/

COPY --from=builder /build/myapp .

EXPOSE 8080

CMD ["./myapp"]
Enter fullscreen mode Exit fullscreen mode

Build Flags Explained

  • CGO_ENABLED=0 → Pure Go binary
  • GOOS=linux → Linux target
  • -ldflags="-s -w" → Strip debug symbols

Size Comparison

Image Approx Size
golang:1.22 ~800 MB
Alpine + Binary ~12 MB

4.2 Even Smaller: Scratch

FROM scratch

COPY --from=builder /build/myapp /myapp

EXPOSE 8080

ENTRYPOINT ["/myapp"]
Enter fullscreen mode Exit fullscreen mode

Works when using CGO_ENABLED=0.


Distroless & Scratch (Minimal Images)

5.1 Google Distroless

FROM gcr.io/distroless/static-debian12

COPY --from=builder /build/myapp /myapp

ENTRYPOINT ["/myapp"]
Enter fullscreen mode Exit fullscreen mode

Benefits

  • Includes CA certificates
  • Includes timezone data
  • No shell
  • No package manager
  • Reduced attack surface

5.2 Adding TLS to Scratch

FROM scratch

COPY --from=builder \
  /etc/ssl/certs/ca-certificates.crt \
  /etc/ssl/certs/

COPY --from=builder /build/myapp /myapp

ENTRYPOINT ["/myapp"]
Enter fullscreen mode Exit fullscreen mode

Advanced: Debugging, Profiling, & Signal Handling

6.1 Graceful Shutdown (SIGTERM)

func main() {
    srv := &http.Server{Addr: ":8080"}

    go func() {
        if err := srv.ListenAndServe(); err != nil &&
            err != http.ErrServerClosed {
            log.Fatal(err)
        }
    }()

    quit := make(chan os.Signal, 1)

    signal.Notify(
        quit,
        syscall.SIGINT,
        syscall.SIGTERM,
    )

    <-quit

    ctx, cancel := context.WithTimeout(
        context.Background(),
        5*time.Second,
    )

    defer cancel()

    if err := srv.Shutdown(ctx); err != nil {
        log.Fatal(err)
    }
}
Enter fullscreen mode Exit fullscreen mode

6.2 Debugging with Delve

FROM golang:1.22

RUN go install github.com/go-delve/delve/cmd/dlv@latest

CMD [
  "dlv",
  "debug",
  "--headless",
  "--listen=:40000",
  "--api-version=2",
  "--accept-multiclient"
]
Enter fullscreen mode Exit fullscreen mode

Run:

docker run \
  -p 40000:40000 \
  -p 8080:8080 \
  debug-image
Enter fullscreen mode Exit fullscreen mode

6.3 Profiling with pprof

import _ "net/http/pprof"
Enter fullscreen mode Exit fullscreen mode
go tool pprof \
http://localhost:6060/debug/pprof/heap
Enter fullscreen mode Exit fullscreen mode

Expert: Go Modules, Vendoring, & CI/CD Pipelines

7.1 Vendoring

go mod vendor
Enter fullscreen mode Exit fullscreen mode
COPY vendor ./vendor

RUN go build -mod=vendor -o myapp .
Enter fullscreen mode Exit fullscreen mode

7.2 GitHub Actions

name: Build and Push

on: push

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: docker/setup-buildx-action@v3

      - uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USER }}
          password: ${{ secrets.DOCKER_TOKEN }}

      - uses: docker/build-push-action@v5
        with:
          push: true
          tags: user/app:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max
Enter fullscreen mode Exit fullscreen mode

7.3 Build Args

ARG VERSION=dev

RUN go build \
  -ldflags="-X main.version=$VERSION" \
  -o myapp .
Enter fullscreen mode Exit fullscreen mode
docker build \
  --build-arg VERSION=1.2.3 \
  -t myapp .
Enter fullscreen mode Exit fullscreen mode

Expert: Docker Compose for Go Microservices

version: "3.8"

services:
  api:
    build: .

    ports:
      - "8080:8080"

    environment:
      - DB_HOST=postgres
      - REDIS_ADDR=redis:6379

    depends_on:
      - postgres
      - redis

    restart: unless-stopped

  postgres:
    image: postgres:16-alpine

    environment:
      POSTGRES_PASSWORD: secret

    volumes:
      - pgdata:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine

    ports:
      - "6379"

volumes:
  pgdata:
Enter fullscreen mode Exit fullscreen mode

Run:

docker compose up -d
Enter fullscreen mode Exit fullscreen mode

Expert to God: Kubernetes, Init Containers, & Sidecars

9.1 Kubernetes Deployment

apiVersion: apps/v1
kind: Deployment

metadata:
  name: go-app

spec:
  replicas: 3

  selector:
    matchLabels:
      app: go-app

  template:
    metadata:
      labels:
        app: go-app

    spec:
      containers:
        - name: app
          image: myapp:latest

          ports:
            - containerPort: 8080
Enter fullscreen mode Exit fullscreen mode

9.2 Init Containers

initContainers:
  - name: migrate
    image: migrate/migrate
Enter fullscreen mode Exit fullscreen mode

9.3 Sidecar Pattern

containers:
  - name: go-app
    image: myapp

  - name: log-shipper
    image: fluent/fluent-bit
Enter fullscreen mode Exit fullscreen mode

Security & Best Practices Checklist

Area Recommendation
Base Image Use scratch, distroless, or alpine
User USER 10001
Read-only Root readOnlyRootFilesystem: true
Secrets Use env vars or secret managers
Scanning trivy image myapp
Labels OCI labels
Health Checks Add HEALTHCHECK
Timeouts Configure stop timeout
Resources Limit CPU and memory

Final Expert Wisdom

  • Use CGO_ENABLED=0 unless you require native C libraries.
  • Prefer scratch when possible.
  • Never store persistent data inside container filesystems.
  • Drop unnecessary Linux capabilities.
  • Use health checks everywhere.
  • Keep images small.
  • Treat containers as immutable.

You now know everything from docker run to orchestrating 10,000 Go containers on Kubernetes.

Go forth and containerize.

Top comments (0)