Tools: Latest: π Stop Chasing Small Docker Images: What Actually Matters for Go in Production
A practical guide to reproducible builds, faster CI pipelines, and debuggable containers for Go engineers
π How Docker + Go Builds Actually Work
π Build Flow Diagram
β οΈ Key Insight:
π§ Reproducible Builds: The Overlooked Problem
β What Goes Wrong
β Fixing It
1. Remove Local Paths
2. Lock Dependencies
3. Standardize Build Environment
π Multi-Arch Build Flow
β‘ Docker Caching: Why Monorepos Break It
π Layer Caching Model
β Problem
β Optimized Caching Strategy
π Improved Flow
π§ Practical Fixes
Use BuildKit Cache
Cache Build Artifacts
Scope Dependencies
π³ Minimal Images: The Trade-Off Nobody Talks About
π Image Comparison
π¨ Real-World Issues
scratch
distroless
alpine
β Practical Strategy
π§ͺ CI/CD Optimized Dockerfile
π Production-Ready Template
π§ Debug Variant
π§© The Bigger Picture
π― Final Takeaway Most Docker + Go tutorials end the same way: βUse multi-stage builds, switch to Alpine, done.β That advice works until it doesnβt. At scale, different problems show up: This article focuses on what actually matters in production:
π reproducibility, caching, and operability Before optimizing, it helps to visualize whatβs happening. If go.mod changes β everything below it rebuilds Same code, different builds: Optional stricter control: π Focus on behavior consistency, not identical binaries. Most engineers optimize for: But production systems care about: If your Docker setup feels: Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse
βββββββββββββββββ β Source Code β ββββββββ¬βββββββββ β βΌ βββββββββββββββββ β go.mod/sum β ββββββββ¬βββββββββ β βΌ ββββββββββββββββββββββββ β go mod download β β (dependency layer) β ββββββββ¬ββββββββββββββββ β βΌ ββββββββββββββββββββββββ β go build β β (compile layer) β ββββββββ¬ββββββββββββββββ β βΌ ββββββββββββββββββββββββ β Final Image β β (distroless/scratch) β ββββββββββββββββββββββββ
βββββββββββββββββ β Source Code β ββββββββ¬βββββββββ β βΌ βββββββββββββββββ β go.mod/sum β ββββββββ¬βββββββββ β βΌ ββββββββββββββββββββββββ β go mod download β β (dependency layer) β ββββββββ¬ββββββββββββββββ β βΌ ββββββββββββββββββββββββ β go build β β (compile layer) β ββββββββ¬ββββββββββββββββ β βΌ ββββββββββββββββββββββββ β Final Image β β (distroless/scratch) β ββββββββββββββββββββββββ
βββββββββββββββββ β Source Code β ββββββββ¬βββββββββ β βΌ βββββββββββββββββ β go.mod/sum β ββββββββ¬βββββββββ β βΌ ββββββββββββββββββββββββ β go mod download β β (dependency layer) β ββββββββ¬ββββββββββββββββ β βΌ ββββββββββββββββββββββββ β go build β β (compile layer) β ββββββββ¬ββββββββββββββββ β βΌ ββββββββββββββββββββββββ β Final Image β β (distroless/scratch) β ββββββββββββββββββββββββ
go build -trimpath -o app
go build -trimpath -o app
go build -trimpath -o app
go mod download
go mod download
go mod download
GOPROXY=https://proxy.golang.org
GONOSUMDB=*
GOPROXY=https://proxy.golang.org
GONOSUMDB=*
GOPROXY=https://proxy.golang.org
GONOSUMDB=*
FROM golang:1.26 AS builder ENV CGO_ENABLED=0 WORKDIR /app COPY go.mod go.sum ./
RUN go mod download COPY . .
RUN go build -trimpath -ldflags="-s -w" -o app
FROM golang:1.26 AS builder ENV CGO_ENABLED=0 WORKDIR /app COPY go.mod go.sum ./
RUN go mod download COPY . .
RUN go build -trimpath -ldflags="-s -w" -o app
FROM golang:1.26 AS builder ENV CGO_ENABLED=0 WORKDIR /app COPY go.mod go.sum ./
RUN go mod download COPY . .
RUN go build -trimpath -ldflags="-s -w" -o app
ββββββββββββββββ β Source β ββββββββ¬ββββββββ β ββββββββββββββ΄βββββββββββββ βΌ βΌ
ββββββββββββββββ ββββββββββββββββ
β linux/amd64 β β linux/arm64 β
ββββββββ¬ββββββββ ββββββββ¬ββββββββ βΌ βΌ Binary A Binary B (different hash) (different hash)
ββββββββββββββββ β Source β ββββββββ¬ββββββββ β ββββββββββββββ΄βββββββββββββ βΌ βΌ
ββββββββββββββββ ββββββββββββββββ
β linux/amd64 β β linux/arm64 β
ββββββββ¬ββββββββ ββββββββ¬ββββββββ βΌ βΌ Binary A Binary B (different hash) (different hash)
ββββββββββββββββ β Source β ββββββββ¬ββββββββ β ββββββββββββββ΄βββββββββββββ βΌ βΌ
ββββββββββββββββ ββββββββββββββββ
β linux/amd64 β β linux/arm64 β
ββββββββ¬ββββββββ ββββββββ¬ββββββββ βΌ βΌ Binary A Binary B (different hash) (different hash)
Layer 1: OS Base Image
Layer 2: go.mod / go.sum
Layer 3: Dependencies (go mod download)
Layer 4: Source Code
Layer 5: Build Output
Layer 1: OS Base Image
Layer 2: go.mod / go.sum
Layer 3: Dependencies (go mod download)
Layer 4: Source Code
Layer 5: Build Output
Layer 1: OS Base Image
Layer 2: go.mod / go.sum
Layer 3: Dependencies (go mod download)
Layer 4: Source Code
Layer 5: Build Output
Change in go.mod β
Layer 2 invalidated β
Layer 3 re-runs (slow) β
Everything rebuilds
Change in go.mod β
Layer 2 invalidated β
Layer 3 re-runs (slow) β
Everything rebuilds
Change in go.mod β
Layer 2 invalidated β
Layer 3 re-runs (slow) β
Everything rebuilds
ββββββββββββββββ β go.mod/sum β ββββββββ¬ββββββββ βΌ (cached via BuildKit mount) βΌ Dependencies reused β
ββββββββββββββββ β Source Code β ββββββββ¬ββββββββ βΌ Build runs faster β‘
ββββββββββββββββ β go.mod/sum β ββββββββ¬ββββββββ βΌ (cached via BuildKit mount) βΌ Dependencies reused β
ββββββββββββββββ β Source Code β ββββββββ¬ββββββββ βΌ Build runs faster β‘
ββββββββββββββββ β go.mod/sum β ββββββββ¬ββββββββ βΌ (cached via BuildKit mount) βΌ Dependencies reused β
ββββββββββββββββ β Source Code β ββββββββ¬ββββββββ βΌ Build runs faster β‘
RUN --mount=type=cache,target=/go/pkg/mod \ go mod download
RUN --mount=type=cache,target=/go/pkg/mod \ go mod download
RUN --mount=type=cache,target=/go/pkg/mod \ go mod download
RUN --mount=type=cache,target=/root/.cache/go-build \ go build -trimpath -ldflags="-s -w" -o app
RUN --mount=type=cache,target=/root/.cache/go-build \ go build -trimpath -ldflags="-s -w" -o app
RUN --mount=type=cache,target=/root/.cache/go-build \ go build -trimpath -ldflags="-s -w" -o app
COPY services/service-a/go.mod services/service-a/go.sum ./
RUN go mod download
COPY services/service-a/go.mod services/service-a/go.sum ./
RUN go mod download
COPY services/service-a/go.mod services/service-a/go.sum ./
RUN go mod download
scratch β smallest β hardest to debug β
distroless β balanced β production-ready β
alpine β larger β easiest debugging π§
scratch β smallest β hardest to debug β
distroless β balanced β production-ready β
alpine β larger β easiest debugging π§
scratch β smallest β hardest to debug β
distroless β balanced β production-ready β
alpine β larger β easiest debugging π§
Production β distroless
Debug build β alpine
Special case β scratch
Production β distroless
Debug build β alpine
Special case β scratch
Production β distroless
Debug build β alpine
Special case β scratch
FROM golang:1.26 AS builder WORKDIR /app ENV CGO_ENABLED=0 COPY go.mod go.sum ./ RUN --mount=type=cache,target=/go/pkg/mod \ go mod download COPY . . RUN --mount=type=cache,target=/root/.cache/go-build \ go build \ -trimpath \ -ldflags="-s -w" \ -o app FROM gcr.io/distroless/base-debian12 WORKDIR / COPY --from=builder /app/app /app USER nonroot:nonroot ENTRYPOINT ["/app"]
FROM golang:1.26 AS builder WORKDIR /app ENV CGO_ENABLED=0 COPY go.mod go.sum ./ RUN --mount=type=cache,target=/go/pkg/mod \ go mod download COPY . . RUN --mount=type=cache,target=/root/.cache/go-build \ go build \ -trimpath \ -ldflags="-s -w" \ -o app FROM gcr.io/distroless/base-debian12 WORKDIR / COPY --from=builder /app/app /app USER nonroot:nonroot ENTRYPOINT ["/app"]
FROM golang:1.26 AS builder WORKDIR /app ENV CGO_ENABLED=0 COPY go.mod go.sum ./ RUN --mount=type=cache,target=/go/pkg/mod \ go mod download COPY . . RUN --mount=type=cache,target=/root/.cache/go-build \ go build \ -trimpath \ -ldflags="-s -w" \ -o app FROM gcr.io/distroless/base-debian12 WORKDIR / COPY --from=builder /app/app /app USER nonroot:nonroot ENTRYPOINT ["/app"]
FROM alpine:3.19 RUN apk add --no-cache ca-certificates COPY --from=builder /app/app /app ENTRYPOINT ["/app"]
FROM alpine:3.19 RUN apk add --no-cache ca-certificates COPY --from=builder /app/app /app ENTRYPOINT ["/app"]
FROM alpine:3.19 RUN apk add --no-cache ca-certificates COPY --from=builder /app/app /app ENTRYPOINT ["/app"]
Reproducibility β Can I trust this build?
Debuggability β Can I fix issues fast?
Performance β Can CI scale?
Reproducibility β Can I trust this build?
Debuggability β Can I fix issues fast?
Performance β Can CI scale?
Reproducibility β Can I trust this build?
Debuggability β Can I fix issues fast?
Performance β Can CI scale? - CI pipelines slow down unpredictably
- Builds stop being reproducible
- Debugging minimal containers becomes painful
- Monorepos destroy Docker cache efficiency - Different architectures (amd64 vs arm64)
- Embedded file paths
- Environment-dependent outputs - No TLS certs β HTTPS fails
- No shell β cannot debug
- No timezone/DNS tools - Secure and minimal
- But still no shell - But uses musl libc (can cause subtle issues) - Build completion - hard to debug - dependencies
- and runtime assumptions