✦ For everyone, free.

Practical knowledge for real and everyday life

Home

12.2.4.3 Go Scratch Runtime

A focused guide to Go Scratch Runtime, connecting core concepts with practical Docker and container operations.

Go scratch runtime uses Docker's completely empty scratch base image as the final stage for a statically linked Go binary, producing the smallest possible final image since there's genuinely nothing else present beyond the application's own compiled executable.

Building Against the Scratch Base

The final stage starts from scratch, copying in only the compiled, statically linked binary.

FROM golang:1.22 AS build
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o /app/server .

FROM scratch
COPY --from=build /app/server /server
ENTRYPOINT ["/server"]
Why This Requires a Genuinely Static Binary

Since scratch provides no dynamic libraries whatsoever, the copied binary must have no dynamic dependencies at all — CGO_ENABLED=0 during the build ensures this requirement is actually met.

ldd server
not a dynamic executable
Why Some Additional Files Are Often Still Needed

Despite being otherwise empty, certain common needs — outbound HTTPS connections requiring CA certificates, for instance — require explicitly copying in specific files the application still depends on.

COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
Why Debugging a Scratch-Based Container Is More Challenging

Without any shell or standard utilities present, the usual docker exec into an interactive shell simply isn't possible for a scratch-based container, requiring different debugging approaches when something needs investigation.

docker exec myapp sh
OCI runtime exec failed: exec failed: unable to start container process: exec: "sh": executable file not found in $PATH

A debugging tool like dlv (Delve, for Go) attached externally, or simply relying heavily on application logging, becomes necessary given this limitation.

Why Go Scratch Runtime Matters

The scratch base represents the extreme end of image minimalism, achievable specifically because of Go's static linking capability, providing the smallest possible attack surface and image size at the cost of losing convenient in-container debugging tools.