Agent Skills: Go Container Optimization

|

UncategorizedID: laurigates/claude-plugins/go-containers

Install this agent skill to your local

pnpm dlx add-skill https://github.com/laurigates/claude-plugins/tree/HEAD/container-plugin/skills/go-containers

Skill Files

Browse the full folder contents for go-containers.

Download Skill

Loading file tree…

container-plugin/skills/go-containers/SKILL.md

Skill Metadata

Name
go-containers
Description
"Go container optimization — scratch/distroless images, static binaries, CGO, ldflags, trimpath (846MB to 2.5MB). Use when working with Go containers or optimizing Go image sizes."

Go Container Optimization

Expert knowledge for building minimal, secure Go container images using static compilation, scratch/distroless base images, and Go-specific build optimizations.

When to Use This Skill

| Use this skill when... | Use container-development instead when... | |---|---| | Building or auditing a Dockerfile for a Go application | Writing language-agnostic multi-stage build patterns | | Choosing between scratch, distroless, and Alpine for a Go binary | Hardening any container against non-root / least-privilege standards | | Driving down a Go image from hundreds of MB to single-digit MB | Composing services with Docker Compose | | Resolving CGO / static-link issues specific to Go | Optimizing a Node.js (nodejs-containers) or Python (python-containers) image |

Core Expertise

Go's Unique Advantages:

  • Compiles to single static binary (no runtime dependencies)
  • Can run on scratch base (literally empty image)
  • No interpreter, VM, or system libraries needed at runtime
  • Enables smallest possible container images (2-5MB)

Key Capabilities:

  • Static binary compilation with CGO_ENABLED=0
  • Binary stripping with ldflags (-w, -s)
  • Scratch and distroless base images
  • CA certificate handling for HTTPS
  • CGO considerations when C libraries are required

The Optimization Journey: 846MB → 2.5MB

This demonstrates systematic optimization achieving 99.7% size reduction:

Step 1: The Problem - Full Debian Base (846MB)

# ❌ BAD: Includes full Go toolchain, Debian system, unnecessary tools
FROM golang:1.23
WORKDIR /app
COPY . .
RUN go build -o main .
EXPOSE 8080
CMD ["./main"]

Issues:

  • Full Debian base (~116MB)
  • Complete Go compiler and toolchain (~730MB)
  • System libraries, package managers, shells
  • None of this is needed at runtime

Image size: 846MB

Step 2: Switch to Alpine (312MB)

# ✅ BETTER: Alpine reduces OS overhead
FROM golang:1.23-alpine
WORKDIR /app
COPY . .
RUN go build -o main .
EXPOSE 8080
CMD ["./main"]

Improvements:

  • Alpine Linux (~5MB base vs ~116MB Debian)
  • Still includes full Go toolchain (unnecessary at runtime)

Image size: 312MB (63% reduction)

Step 3: Multi-Stage Build (15MB)

# ✅ GOOD: Separate build from runtime
# Build stage
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o main .

# Runtime stage
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/main .
EXPOSE 8080
CMD ["./main"]

Improvements:

  • Build artifacts excluded from final image
  • Only Alpine + binary in final image
  • Faster deployments and pulls

Image size: 15MB (95% reduction from 312MB)

Step 4: Strip Binary & Optimize Build Flags (8MB)

# ✅ BETTER: Optimized build flags
# Build stage
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .

# Optimized build with stripping
RUN CGO_ENABLED=0 GOOS=linux go build \
    -a \
    -installsuffix cgo \
    -ldflags="-w -s" \
    -trimpath \
    -o main .

# Runtime stage with CA certificates
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY --from=builder /app/main .
EXPOSE 8080
CMD ["./main"]

Build Flag Explanations:

| Flag | Purpose | Impact | |------|---------|--------| | CGO_ENABLED=0 | Disable CGO, create static binary | Removes dynamic library dependencies | | -a | Force rebuild of all packages | Ensures clean build | | -installsuffix cgo | Separate output directory | Prevents cache conflicts | | -ldflags="-w -s" | Strip debug info and symbol table | Reduces binary size significantly | | -w | Omit DWARF debug information | ~30% size reduction | | -s | Omit symbol table and debug info | Additional ~10% reduction | | -trimpath | Remove file system paths | Security: no local path leakage |

Additions:

  • CA certificates for HTTPS requests
  • Fully static binary (no dynamic dependencies)

Image size: 8MB (47% reduction from 15MB)

Step 5: Scratch or Distroless (2.5MB)

Option A: Scratch (Absolute Minimum)

# Build stage
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .

# Optimized static build
RUN CGO_ENABLED=0 GOOS=linux go build \
    -a \
    -installsuffix cgo \
    -ldflags="-w -s" \
    -trimpath \
    -o main .

# Runtime stage - scratch (empty image)
FROM scratch

# Copy CA certificates from builder
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

# Copy binary
COPY --from=builder /app/main /main

EXPOSE 8080
CMD ["/main"]

Image size: 2.5MB (68% reduction from 8MB)

Option B: Distroless (Slightly Larger, Easier Debugging)

# Build stage (same as above)
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build \
    -a \
    -ldflags="-w -s" \
    -trimpath \
    -o main .

# Runtime stage - distroless
FROM gcr.io/distroless/static-debian12

COPY --from=builder /app/main /main
EXPOSE 8080
CMD ["/main"]

Distroless advantages:

  • Includes CA certificates automatically
  • Minimal OS files (passwd, nsswitch.conf)
  • No shell (security benefit)
  • Slightly larger (~2-3MB more) but includes useful metadata

Image size: ~4-5MB

Performance Impact

| Metric | Debian (846MB) | Alpine (15MB) | Scratch (2.5MB) | Improvement | |--------|----------------|---------------|-----------------|-------------| | Image Size | 846MB | 15MB | 2.5MB | 99.7% reduction | | Pull Time | 52s | 4s | 1s | 98% faster | | Build Time | 3m 20s | 2m 15s | 1m 45s | 47% faster | | Startup Time | 2.1s | 1.2s | 0.8s | 62% faster | | Memory Usage | 480MB | 180MB | 128MB | 73% reduction | | Storage Cost | $0.48/mo | $0.01/mo | $0.001/mo | 99.8% reduction |

Security Impact

| Image Type | Vulnerabilities | Attack Surface | |------------|-----------------|----------------| | Debian-based | 63 CVEs | Full OS, shell, package manager, utilities | | Alpine-based | 12 CVEs | Minimal OS, shell, package manager | | Scratch | 0 CVEs | Binary only, no OS | | Distroless | 0-2 CVEs | Binary + minimal runtime, no shell |

Security benefits of scratch/distroless:

  • No shell → no shell injection attacks
  • No package manager → no supply chain attacks
  • No OS utilities → minimal attack surface
  • No unnecessary libraries → fewer vulnerabilities

When to Use Each Approach

| Use Case | Recommended Base | Reason | |----------|------------------|--------| | Production services | Scratch or Distroless | Minimal size, maximum security | | Services with C dependencies | Alpine (with CGO) | Requires system libraries | | Development/debugging | Alpine | Need shell access for troubleshooting | | Legacy apps | Debian slim | Compatibility requirements |

Go-Specific .dockerignore

# Version control
.git
.gitignore
.gitattributes

# Go artifacts
vendor/
*.exe
*.exe~
*.dll
*.so
*.dylib
*.test
*.out
go.work
go.work.sum

# Development files
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store

# Documentation
README.md
*.md
docs/
LICENSE

# CI/CD
.github/
.gitlab-ci.yml
.travis.yml
Jenkinsfile

# Environment files
.env
.env.*
*.env

# Build artifacts
dist/
build/
bin/
tmp/
temp/

# Logs
*.log
logs/

# Test files (if not needed in image)
*_test.go
testdata/

# Docker files
Dockerfile*
docker-compose*.yml
.dockerignore

Binary Size Analysis

# Build with different optimization levels
go build -o main-default .
go build -ldflags="-w" -o main-w .
go build -ldflags="-s" -o main-s .
go build -ldflags="-w -s" -o main-ws .

# Compare sizes
ls -lh main-*

# Typical results for a medium Go app:
# main-default: 12.5MB (with debug info + symbols)
# main-w:        8.7MB (no debug info)
# main-s:       10.2MB (no symbol table)
# main-ws:       7.8MB (both stripped)

# Further analyze binary
go tool nm main-default | wc -l  # Count symbols
file main-ws                      # Verify static linking
ldd main-ws                       # Should show "not a dynamic executable"

Advanced: CGO Considerations

When your Go application uses CGO (C libraries, database drivers like SQLite, etc.), you cannot use scratch base images.

# When CGO is required (database drivers, C libraries)
FROM golang:1.23-alpine AS builder

# Install C dependencies
RUN apk add --no-cache gcc musl-dev

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

# Build with CGO enabled
RUN CGO_ENABLED=1 GOOS=linux go build \
    -ldflags="-w -s -linkmode external -extldflags '-static'" \
    -o main .

# Runtime needs musl
FROM alpine:latest
RUN apk --no-cache add ca-certificates
COPY --from=builder /app/main .
CMD ["./main"]

Note: With CGO, Alpine is the minimal option (~7-10MB final image).

Common Go Database Drivers

| Driver | CGO Required | Minimum Base | |--------|--------------|--------------| | github.com/lib/pq (PostgreSQL) | No | Scratch | | github.com/go-sql-driver/mysql | No | Scratch | | github.com/mattn/go-sqlite3 | Yes | Alpine | | modernc.org/sqlite (pure Go) | No | Scratch |

Agentic Optimizations

Go-specific container commands for fast development:

| Context | Command | Purpose | |---------|---------|---------| | Quick build | go build -ldflags="-w -s" -o app . | Build stripped binary | | Check size | ls -lh app | Verify binary size | | Test static | ldd app | Verify no dynamic deps | | Container build | DOCKER_BUILDKIT=1 docker build -t app . | Fast build with cache | | Size check | docker images app --format "{{.Size}}" | Check final image size | | Layer analysis | docker history app:latest --human | See layer sizes |

Best Practices

Always:

  • Use CGO_ENABLED=0 unless you need C libraries
  • Strip binaries with -ldflags="-w -s"
  • Use -trimpath to remove filesystem paths
  • Prefer scratch or distroless for production
  • Version pin Go and base images (never use latest)

Never:

  • Ship the Go compiler in production images
  • Use full Debian/Ubuntu bases for Go apps
  • Include debug symbols in production binaries
  • Run as root user (even in scratch, use USER 65534)

Related Skills

  • container-development - General container patterns, multi-stage builds, security
  • nodejs-containers - Node.js-specific container optimizations
  • python-containers - Python-specific container optimizations