Distroless Images: Minimal Attack Surface
A standard ubuntu image contains ~25,000 files.
An alpine image contains ~500 files.
A Distroless image contains… your app and its dependencies. Nothing else.
Distroless images (maintained by Google) contain NO shell (/bin/sh), NO package manager (apt, apk), and NO useful tools (curl, wget, ls).
Why? Security. If an attacker breaks into your container, they can’t run wget http://malware.com, they can’t run apt-get install nmap, and they can’t even list files. They are trapped in a featureless void.
1. Interactive: Attack Surface Comparator
See what tools differ across image types. Fewer tools = Smaller Attack Surface.
2. Implementation: Multi-Stage Builds
Since Distroless has no package manager, you cannot install dependencies in it. You must build your app in a “Builder” stage (full OS) and copy the binary to the “Runner” stage (Distroless).
Go Example
# STAGE 1: Build
FROM golang:1.21 as builder
WORKDIR /app
COPY . .
# Statically compile the binary
RUN CGO_ENABLED=0 go build -o myapp main.go
# STAGE 2: Run
# gcr.io/distroless/static-debian11 is for static binaries (Go, Rust)
FROM gcr.io/distroless/static-debian11
COPY --from=builder /app/myapp /
CMD ["/myapp"]
Java Example
# STAGE 1: Build
FROM maven:3.8-openjdk-17 as builder
WORKDIR /app
COPY . .
RUN mvn package
# STAGE 2: Run
# gcr.io/distroless/java17-debian11 includes the JVM!
FROM gcr.io/distroless/java17-debian11
COPY --from=builder /app/target/app.jar /app.jar
CMD ["/app.jar"]
3. How to Debug?
“But wait, if there is no shell, how do I docker exec in to debug?”
You don’t.
- Logs: Rely on
docker logs. - Ephemeral Containers: Since Kubernetes v1.23, you can attach a “debug container” that shares the process namespace.
kubectl debug -it my-pod --image=busybox --target=my-container - Use :debug tag: Google provides debug versions of distroless (e.g.,
base:debug) that include a busybox shell. Use them only for local debugging.
4. Distroless Flavors
| Image | Use Case |
|---|---|
gcr.io/distroless/static |
Go, Rust, C++ (Statically linked) |
gcr.io/distroless/base |
C/C++ (Dynamically linked, includes glibc) |
gcr.io/distroless/java17 |
Java applications (includes JVM) |
gcr.io/distroless/nodejs20 |
Node.js applications |
gcr.io/distroless/python3 |
Python applications (Experimental) |
[!IMPORTANT] Why not Alpine? Alpine uses
musllibc, while most apps are compiled againstglibc. Distroless is based on Debian (glibc), offering better compatibility without the bloat.