BlogCI/CD
Distroless container images: shipping without a shell
Ship images with no package manager, no shell, and almost no attack surface — and still debug when you need to.
A distroless image contains your application and its runtime dependencies — and nothing else. No shell, no package manager, no coreutils. When an attacker lands a remote-code-execution bug, there is no sh, no curl, and no cat /etc/passwd to pivot with.
docker run --rm -it gcr.io/distroless/static:nonroot shexec: "sh": executable file not found in $PATHexactly the point — the toolbox an attacker reaches for is goneWhat's gone, and why that helps
Typical base
bash / sh
apt / apk
curl, wget
coreutils
100+ CVEs to track
Distroless
no shell
no package mgr
no net tools
libc + your app
a handful of CVEs
Build on distroless
Dockerfile
FROM golang:1.22 AS buildWORKDIR /srcCOPY . .RUN CGO_ENABLED=0 go build -o /app ./cmd/appFROM gcr.io/distroless/static:nonrootCOPY --from=build /app /appUSER nonrootENTRYPOINT ["/app"]
You can still debug
Use the :debug tag (adds busybox) or kubectl debug to attach an ephemeral container with tools — without baking them into production.
The tradeoffs
No shell means shell-form RUN/CMD will not work — use exec form. Health checks must call your binary, not curl. For most services that is a small price for a dramatically smaller attack surface.