Click To Jump to your Favorite Topic Bellow:
- Beginner: Why Docker + Go?
- Your First Go Container
- Intermediate: Optimizing Builds & Caching
- Multi-Stage Builds (The Right Way)
- Distroless & Scratch (Minimal Images)
- Advanced: Debugging, Profiling, & Signal Handling
- Expert: Go Modules, Vendoring, & CI/CD Pipelines
- Expert: Docker Compose for Go Microservices
- Expert to God: Kubernetes, Init Containers, & Sidecars
- Security & Best Practices Checklist
- Final Expert Wisdom
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
Verify installation:
docker --version
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)
}
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"]
2.3 Build & Run
docker build -t go-web .
docker run -p 8080:8080 go-web
Visit:
http://localhost:8080
2.4 Basic Commands
docker ps
docker stop <container>
docker rm <container>
docker images
docker rmi go-web
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 . .
❌ Bad
COPY . .
RUN go mod download
3.2 Use .dockerignore
.git
*.log
tmp/
*.test
coverage.out
Dockerfile
README.md
3.3 Build with Tags & Versions
docker build -t myapp:1.0.0 .
docker tag myapp:1.0.0 myapp:latest
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"]
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"]
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"]
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"]
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)
}
}
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"
]
Run:
docker run \
-p 40000:40000 \
-p 8080:8080 \
debug-image
6.3 Profiling with pprof
import _ "net/http/pprof"
go tool pprof \
http://localhost:6060/debug/pprof/heap
Expert: Go Modules, Vendoring, & CI/CD Pipelines
7.1 Vendoring
go mod vendor
COPY vendor ./vendor
RUN go build -mod=vendor -o myapp .
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
7.3 Build Args
ARG VERSION=dev
RUN go build \
-ldflags="-X main.version=$VERSION" \
-o myapp .
docker build \
--build-arg VERSION=1.2.3 \
-t myapp .
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:
Run:
docker compose up -d
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
9.2 Init Containers
initContainers:
- name: migrate
image: migrate/migrate
9.3 Sidecar Pattern
containers:
- name: go-app
image: myapp
- name: log-shipper
image: fluent/fluent-bit
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=0unless you require native C libraries. - Prefer
scratchwhen 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)