Distroless & scratch
No shell, no package manager, nothing to pivot with.
A distroless image contains your application and its runtime dependencies — and deliberately nothing else. No shell, no package manager, no coreutils, no /bin at all. Google’s distroless images are the common source: a base/static image is a few megabytes with just CA certificates, tzdata, and libc. The security payoff is enormous: most post-exploitation depends on tools that are simply absent, so an RCE that would normally drop a shell finds there is no shell to drop.
FROM gcr.io/distroless/base-debian12:nonrootCOPY --from=build /out/server /serverUSER nonroot # distroless "nonroot" variant runs as uid 65532ENTRYPOINT ["/server"]# variants: static (fully static bins) · base (glibc) · cc (C deps) · java · python
What "no shell" changes
The absence of a shell reshapes the attacker’s options. There is no /bin/sh to exec, so shell-based payloads fail; no apt or apk to install tools; no curl or wget to pull a second stage; no cat, ls, or find to explore. An attacker who lands code execution in a distroless container is stuck with only what your binary itself can do — a dramatically smaller blast radius. It also means far fewer packages, so far fewer CVEs to track and patch.
$ docker run --rm gcr.io/distroless/static-debian12 shdocker: Error response from daemon: exec: "sh": executable file not found in $PATH$ docker run --rm gcr.io/distroless/static-debian12 lsdocker: Error ... "ls": executable file not found # nothing to live off
Debugging without a shell
The obvious objection — “how do I debug it?” — has two clean answers. Distroless ships :debug variants that add a BusyBox shell for troubleshooting, so you keep a shell-less image in production and run the debug tag when you need to poke. Better, use kubectl debug or docker debug ephemeral/attached containers to bring tools alongside the running container without ever baking them into the image. The production image stays inert.
# attach a throwaway toolbox to a running distroless container — image stays clean$ docker debug svc-container # or: kubectl debug -it pod --image=busybox --target=app# use the :debug tag only when you specifically need an in-image shell:# FROM gcr.io/distroless/static-debian12:debug-nonroot