A static Go binary runs on scratch, but scratch is empty: you must add CA certificates, time zone data and a non-root user yourself. The distroless static image already includes certificates, tzdata, a tmp directory and a nonroot tag. Use scratch if you want to control every byte, and distroless static for sane defaults.
Build a static binary first
Both options need a binary with no libc dependency. The distroless documentation shows the standard approach: build with CGO disabled so the result does not need libc, then copy it into the runtime image.
FROM golang:1.25 AS build
WORKDIR /go/src/app
COPY . .
RUN go mod download
RUN CGO_ENABLED=0 go build -trimpath -o /go/bin/app
FROM gcr.io/distroless/static-debian13:nonroot
COPY --from=build /go/bin/app /
CMD ["/app"]
Use the Go version your project supports. If your code or a dependency needs cgo, a static binary is not possible in the usual way, and the distroless docs point those applications to the base image that includes glibc.
What distroless static contains
According to the distroless project, the static image includes ca-certificates, an etc/passwd entry for a root user, a tmp directory and tzdata. Tags include latest, nonroot, debug and debug-nonroot. The project describes the smallest image as around 2 MiB.
What scratch is missing
Scratch has nothing. If your program makes HTTPS calls, it needs certificates to verify servers. If it formats times in a named zone, it needs time zone data. Go offers a way around the second: the time/tzdata package provides an embedded copy of the timezone database, used if the time package cannot find tzdata files on the system. Importing it increases the program size by about 450 KB, according to the package documentation.
| Need | Scratch | Distroless static |
|---|---|---|
| CA certificates | Copy from build stage | Included |
| Time zone data | Embed with time/tzdata or copy files | Included |
| Non-root user | Create passwd entry and numeric USER | nonroot tag |
| A tmp directory | Create it | Included |
| Shell for debugging | None | None, use debug tag |
| Update owner for those files | You | The distroless project, you rebuild |
Which to choose
Scratch makes sense when the binary is the only thing in the image and you are willing to maintain the certificate bundle yourself. A stale certificate bundle in a frozen scratch image is a quiet failure that shows up as TLS errors later.
Distroless static trades a little size for defaults that are tracked upstream. The project says it tracks upstream Debian releases and uses automation to open pull requests when updates are available. You still need to rebuild your image to pick them up.
Verify what you ship
Run the built image and check the user. Check that outbound HTTPS works. Check the time zone behaviour if you rely on it. Then verify the signature of the base. The distroless project says its images are signed with cosign using keyless signing, and recommends verifying before you build on them.
Where Safeguard fits
A static Go binary hides its dependency list from tools that only read operating system packages. Safeguard scans Go modules from your repository and builds an SBOM, with policy gates on pull requests. Try the free scan.
Quick check
Why does my scratch based Go program fail on HTTPS calls?
It has no CA certificates to verify the server. Copy a certificate bundle from the build stage or use an image that includes one.
Is a scratch image automatically free of vulnerabilities?
No. It has no operating system packages to report, but the Go standard library version and your module dependencies are still in the binary.
Sources
- Distroless images, GoogleContainerTools
- Distroless base and static image contents
- time/tzdata package, Go documentation
Last reviewed: 19 October 2026.