Skip to content
advanced Phase 15 · Docker Security Advanced

Distroless & Minimal Images

Use distroless base images for minimal attack surface.

45m
0 problems
Topic Progress 0%

Why Distroless

Why Distroless

Why run a full Ubuntu image when your service only needs a static binary? A typical runtime image ships a shell, a package manager, and dozens of utilities — each one a path for an attacker who has already reached your container. Distroless images keep only the application and its runtime dependencies: no shell, no ssh, no package manager, no curl. Google maintains them (gcr.io/distroless/...) with variants per runtime (static, :python3, :nodejs20) plus :nonroot tags that execute as UID 65532 instead of root. The payoff is a much smaller image, far fewer CVEs to triage, and a container where docker exec gets an intruder nowhere.

Building Distroless Images

Building Distroless Images

Distroless bases contain no compiler, so you build in a builder stage and copy the artifact:

FROM golang:1.22 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /app ./cmd/api

FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /app /app
ENTRYPOINT ["/app"]

CGO_ENABLED=0 produces a static binary the static base can run; dynamic binaries need a -cc variant with glibc. Language runtimes use their matching bases instead. Pin bases by digest in production so deploys are reproducible, and reach for the :debug tag — which adds a busybox shell — only while developing.

Debugging and Pitfalls

Debugging and Pitfalls

No shell changes operations. docker exec -it app sh fails immediately — that is the point. Debug locally with the debug tag: docker run --rm -it --entrypoint sh myapp:debug. Logs must go to stdout; there is no syslog or log directory to inspect inside the container.

Files are written as nonroot. The :nonroot variant runs as UID 65532, so writable paths need COPY --chown or a mounted volume.

Not a complete security strategy. Distroless removes OS tooling, not vulnerable libraries in your runtime or kernel bugs.

Interview answer: state the trade — smaller image and fewer entry points versus the loss of in-container debugging — and name the debug tag as your mitigation.

Quiz

1. What is the primary security benefit of a distroless image?

Question 1 options

2. How do you interactively debug a distroless container?

Question 2 options

3. Your Go binary links against libc. Which base should you use?

Question 3 options

Flashcards

Question

What a distroless image contains

Answer

Only the app and its runtime libraries — no shell, package manager, or utilities.

Question

Distroless nonroot user

Answer

UID 65532; use :nonroot tags or set a USER so the process never runs as root.

Question

How to build for distroless

Answer

Multi-stage Dockerfile: compile in a builder image, COPY the binary into the distroless stage.

Question

Debug tag purpose

Answer

:debug adds busybox (sh) for troubleshooting; keep it out of production tags.

Question

What distroless does NOT fix

Answer

Vulnerable app dependencies, kernel CVEs, and secrets in your code.

Revision Notes

Key Takeaways

  • 1. Strip shells and package managers to remove post-exploitation tooling
  • 2. Multi-stage builds are mandatory because distroless ships no compiler
  • 3. Log to stdout — there is nothing inside the container to inspect

Interview Tips

  • • Frame distroless as a tradeoff: smaller attack surface versus lost debugging tooling, and say how you compensate (:debug tag)
  • • Mention UID 65532 / nonroot execution when asked how you remove root from containers

Cheat Sheet

Distroless = app + runtime only; no shell/pkg manager. Build via multi-stage; static → :static, dynamic → -cc; run :nonroot (UID 65532); debug with :debug tag; logs to stdout only.

Continue Learning