Distroless images¶
A distroless image contains only the application and its runtime dependencies. It has no package manager, no shell, and no standard Linux tools.
A smaller image has fewer packages, so it has fewer CVEs to scan, patch and audit. An attacker who gets into the container cannot use a shell or install tools. The cost is harder debugging, because you cannot exec into a shell.
Distroless images (no shell, no package manager)¶
| Image | Maintainer | Registry | Base | libc |
|---|---|---|---|---|
| scratch | Docker | reserved name, not pullable | empty | none |
| Google distroless | gcr.io/distroless/ |
Debian 13 | glibc | |
| Chainguard | Chainguard | cgr.dev/ |
Wolfi | glibc (some musl) |
| Docker Hardened Images | Docker | dhi.io/ |
Debian or Alpine | glibc or musl |
| Ubuntu chiseled | Canonical, Microsoft | ubuntu/ on Docker Hub, mcr.microsoft.com/dotnet/ |
Ubuntu | glibc |
| UBI Micro | Red Hat | registry.access.redhat.com/ubi10/ubi-micro |
RHEL | glibc |
| Azure Linux distroless | Microsoft | mcr.microsoft.com/azurelinux/distroless/ |
Azure Linux | glibc |
scratch¶
FROM scratch starts from an empty file system. Use it only for static binaries. If the application needs CA certificates, time zone data or /etc/passwd, you must copy them in.
Google distroless¶
The current images use Debian 13 (trixie). Each image has the tags latest, nonroot, debug and debug-nonroot. The debug tags add a BusyBox shell.
| Image | Content |
|---|---|
static-debian13 |
CA certificates, tzdata, /etc/passwd. About 2 MiB |
base-nossl-debian13 |
static plus glibc |
base-debian13 |
base-nossl plus OpenSSL |
cc-debian13 |
base plus libgcc and libstdc++ |
java17, java21, java25 |
-debian13 suffix. OpenJDK runtime |
nodejs22, nodejs24, nodejs26 |
-debian13 suffix. Node.js runtime |
python3-debian13 |
Python 3 runtime |
Google does not publish .NET images. For .NET, use the chiseled images.
Chainguard Containers¶
Chainguard builds its images from Wolfi. Wolfi is an "undistro" made only for containers, with no kernel. Chainguard rebuilds the images every day and signs them. Each image includes an SBOM (software bill of materials).
The standard tag is distroless. The -dev tag (for example latest-dev) adds a shell and apk. Use it only in the build stage.
The free Catalog Starter plan gives five images of your choice, with the path cgr.dev/<organization>/<image>. Version tags and FIPS images need a paid plan.
Links: Image directory, Documentation, Wolfi.
Docker Hardened Images (DHI)¶
Docker launched DHI in 2025-05 and made them free and open source (Apache 2.0) in 2025-12. The runtime images have no shell and no package manager, and they run as a non-root user. Each image includes an SBOM, VEX data and SLSA Build Level 3 provenance. The -dev tags add build tools.
Run docker login dhi.io with a Docker account before you pull. The paid Enterprise tier adds a CVE fix SLA, FIPS and STIG images.
Links: Catalog, Documentation.
Ubuntu chiseled¶
Chisel cuts Ubuntu packages into small "slices" and installs only the files that the application needs. Canonical publishes ubuntu/jre, ubuntu/dotnet-runtime, ubuntu/dotnet-aspnet and ubuntu/dotnet-deps. The old ubuntu/chiselled-jre repository is archived.
On Ubuntu 24.04 and later, these images are "rocks" with Pebble as the entrypoint. Microsoft publishes chiseled .NET images too, for example mcr.microsoft.com/dotnet/runtime:10.0-noble-chiseled.
Links: Official page, Chisel documentation, Docker Hub.
Red Hat UBI Micro¶
UBI Micro is the smallest UBI image. It has no package manager. You install packages from the host with Buildah or with a multi-stage build. The UBI 9 image is about 23 MB.
Links: UBI Micro introduction, Types of container images (RHEL 10).
Minimal images with a shell or a package manager¶
These images are small but not distroless. If you need a shell, or if the build stage needs a package manager, use one of these images.
| Image | Size (approx.) | Shell | Package manager | libc |
|---|---|---|---|---|
| BusyBox | a few MB | yes (sh) |
none | musl, glibc or uclibc |
| Alpine | 3.6 MB compressed | yes (BusyBox) | apk |
musl |
Debian slim (trixie-slim) |
75 MB uncompressed | yes (bash) | apt |
glibc |
| UBI Minimal | 92 MB on disk, 32 MB compressed | yes (bash) | microdnf |
glibc |
| Amazon Linux 2023 minimal | small | yes (bash) | microdnf |
glibc |
Some binaries need glibc and do not work with musl. The musl memory allocator can also be slower than the glibc allocator for multi-threaded workloads. Test the application before you move it to Alpine.
Links: BusyBox, Alpine, Debian, Red Hat catalog.
Java runtime images¶
| Image | Base | Notes |
|---|---|---|
bellsoft/liberica-runtime-container |
Alpaquita Linux (musl or glibc) | slim tags have no apk and one layer. Example: jre-21-slim-musl |
amazoncorretto |
Amazon Linux 2023 (default), Alpine, Debian | Not distroless. -al2023-headless tags remove GUI libraries |
gcr.io/distroless/java21-debian13 |
Debian 13 | Distroless OpenJDK |
ubuntu/jre |
Ubuntu | Chiseled, no shell |
Links: Liberica Runtime Container, Amazon Corretto.
Choose a base image¶
| Application | Base image |
|---|---|
| Static binary, no TLS | scratch |
Static binary with TLS (Go with CGO_ENABLED=0, Rust with musl) |
distroless/static |
| Dynamic binary that needs glibc (Go with CGO) | distroless/base |
| C or C++ binary | distroless/cc |
| Java JAR | distroless/java21, ubuntu/jre, Liberica slim |
| Node.js or Python | distroless/nodejs24, distroless/python3, Chainguard, DHI |
| .NET | Ubuntu chiseled, Chainguard dotnet-runtime |
| Red Hat or OpenShift platform | UBI Micro |
Multi-stage build pattern¶
You build the application in a full image. Then you copy only the binary to the distroless image.
# Build stage
FROM golang:1.27 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o myapp
# Final stage
FROM gcr.io/distroless/static-debian13:nonroot
COPY --from=builder /app/myapp /myapp
ENTRYPOINT ["/myapp"]
ENTRYPOINT in exec form¶
If the image has no shell, write ENTRYPOINT and CMD in exec form (JSON array). The shell form needs /bin/sh.
# Correct - exec form
ENTRYPOINT ["/myapp", "--flag"]
# Wrong - shell form (requires /bin/sh)
ENTRYPOINT /myapp --flag
Debug a distroless container¶
Use the :debug tag only in development. In Kubernetes, attach an ephemeral container with a shell to the running pod:
kubectl debug -it <pod> --image=busybox --target=<container>