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.

Apr 2, 2026·4 min readAdvanced·By the SecOpsLog team · command-tested

Most container break-ins follow a script: get code execution, spawn a shell, curl a second stage, enumerate with coreutils. Distroless images remove that script's props — your app, glibc or static runtime, and CA certs — but no sh, no package manager, and no wget. You trade convenience for an attack surface measured in megabytes instead of hundreds.

This walkthrough builds a Go service on distroless/static, shows why docker run … sh fails (on purpose), and covers debug tags without baking tools into prod. Pair it with multi-stage patterns from Docker in depth if you are still shipping compilers in the same stage as your binary.

From compiler image to distroless runtime

Distroless is always the second stage. You cannot apk-install into it — everything the process needs must be copied in or embedded at build time.

1Build stagefull toolchain image2Compile staticCGO_ENABLED=03Pick variantstatic / base / cc4COPY binaryinto distroless5USER nonrootnumeric UID 655326Exec-form CMDno shell wrapper7Debug via :debugnot production tag

What is gone, and why defenders care

A debian:bookworm-slim base still ships bash, dpkg, and dozens of packages you will never patch individually. Distroless flips the model: Google maintains minimal bases with regular rebuilds, and your scanner stops reporting CVEs in tools you never invoked. You still patch your app and any copied libs — but the long tail of OS utilities disappears.

bash — there is no shelllive
docker run --rm -it gcr.io/distroless/static:nonroot sh
exec: "sh": executable file not found in $PATH
that failure is the feature — post-exploit tooling is missing
docker run --rm gcr.io/distroless/static:nonroot /app
app starts when ENTRYPOINT targets your binary directly

A production-shaped Dockerfile

Choose the variant that matches your linkage: static for pure Go, base for glibc dynamic binaries, java for JRE-only workloads. Tag :nonroot unless you have a rare reason to run as root — the default user is 65532:65532.

Dockerfile
FROM golang:1.22 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -o /app ./cmd/server
FROM gcr.io/distroless/static:nonroot
COPY --from=build /app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]

Operational tradeoffs you must accept

Shell-form CMD curl -f http://localhost/health breaks — health checks must exec your binary or an HTTP probe built into the app. docker exec debugging is gone; use kubectl debug ephemeral containers, the :debug tag (busybox sidecar), or centralized logs. Distroless rewards teams that invest in observability instead of SSH-within-a-container.

Language-specific distroless tags matter: gcr.io/distroless/java17-debian12 ships a JRE without apt; nodejs20-debian12 exists for interpreted workloads where copying only /app is not enough. Read the image README for CA cert and timezone needs — static Go binaries often need nothing beyond the binary itself, while JDBC clients may need additional trust bundles copied explicitly.

Attack surface comparison
Typical slim base
bash / sh
apt or apk
curl, wget
coreutils
100+ packages to track
Distroless nonroot
no shell
no package mgr
no network CLI
libc + certs + app
handful of CVEs
Distroless does not fix bad apps
Running as root on distroless, copying secret files into the image, or mounting the Docker socket still loses the game. Minimal base plus non-root user plus read-only rootfs is the combo — not the base alone.

Where this goes next

Distroless is the runtime half of a secure image story; multi-stage is the build half. Add admission policies that reject :latest and root UID 0, and study escape paths so you know what minimal images actually block. Advanced container security walks seccomp, capabilities, and breakout techniques hands-on in a lab cluster.

Go deeper in a courseAdvanced container securityMinimal images, runtime hardening, and the escapes they stop.View course

Related posts