Skip to content

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 Google 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>