Hardened and Minimal Images

Image hardening starts with the base image. A hardened application image has fewer packages, fewer shells and utilities, signed metadata, a known build process, and a smaller patch surface.

Minimal image choices

There are several common base-image styles.

Base image choices

Choice

Use it when

Tradeoff

Full distribution

You need many operating system tools or are still discovering dependencies.

Larger attack surface and more CVE noise.

Slim distribution

You need compatibility with a familiar distribution but not every package.

Still includes more than a pure runtime needs.

Alpine-style image

The stack works with musl libc and small packages.

Some language runtimes and native libraries expect glibc.

Distroless image

The application needs only runtime libraries and no shell or package manager.

Debugging must use external tools such as docker debug.

Docker Hardened Image

The organization wants maintained, minimal, signed, compliance-ready base images.

Availability and access depend on Docker’s product offering.

Migration path

Start with the current official image when learning or prototyping.

1FROM python:3-slim

Move build tools into a separate stage.

 1# syntax=docker/dockerfile:1
 2FROM python:3-slim AS build
 3WORKDIR /src
 4COPY requirements.txt .
 5RUN --mount=type=cache,target=/root/.cache/pip \
 6    pip wheel --wheel-dir /wheels -r requirements.txt
 7
 8FROM python:3-slim AS runtime
 9WORKDIR /app
10COPY --from=build /wheels /wheels
11RUN pip install --no-cache-dir /wheels/* && rm -rf /wheels
12COPY . .
13CMD ["python", "app.py"]

Then move to a more minimal or hardened runtime base when the dependency set is understood. Re-test native extensions, TLS certificates, timezone data, DNS behavior, and shell-dependent startup scripts.

glibc versus musl

The libc choice matters.

  • Debian, Ubuntu, and many slim images use glibc.

  • Alpine uses musl.

  • Many Python wheels, Node native modules, database clients, and vendor SDKs are tested first against glibc.

  • Alpine can be excellent for small static binaries, but it is not automatically the safest or fastest option for every stack.

Distroless runtime pattern

Distroless images intentionally omit shells and package managers. That is the point. The runtime image should contain the application and the libraries needed to run it.

 1# syntax=docker/dockerfile:1
 2FROM golang:alpine AS build
 3WORKDIR /src
 4COPY . .
 5RUN go build -o /out/server ./cmd/server
 6
 7FROM gcr.io/distroless/static-debian12
 8COPY --from=build /out/server /server
 9USER 65532:65532
10ENTRYPOINT ["/server"]

Debugging minimal images

Do not add bash, curl, vim, or sudo back into the runtime image just to make debugging easier. Use external debugging tools, sidecar diagnostics, test images, or docker debug.

1docker debug api
2docker logs api
3docker inspect api

Release requirements

A hardened-image release should provide:

  • A pinned base image or digest.

  • SBOM for the final image.

  • Provenance attestation for the build.

  • Vulnerability scan result.

  • Clear ownership for remediation.

  • A rebuild policy for base-image updates.

  • Runtime proof that the image runs without root.

Practical rules

  • Use slim or hardened bases for runtime stages.

  • Keep package managers and compilers out of runtime images.

  • Choose glibc or musl based on dependency compatibility, not image size alone.

  • Use distroless only when the application and operations model are ready for no-shell debugging.

  • Prefer signed SBOM, provenance, and digest-pinned releases for production.

References