Switching to distroless base images is often presented as the solution to container image security. No shell, no package manager, dramatically fewer packages — distroless images are obviously more secure than full Ubuntu images. Right?
The answer is: more secure by default, but not secure enough by design. And the gap between “more secure than Ubuntu” and “actually hardened for your specific application” is where most of the remaining risk lives.
What Distroless Images Actually Provide?
Google’s distroless project produces base images that contain only the minimal runtime requirements for specific language environments:
- No shell (sh, bash)
- No package manager (apt, pip)
- No standard Unix utilities (ls, grep, curl, wget)
- Language runtime only (JRE for Java, Python runtime for Python, etc.)
The CVE count of a distroless image is lower than a full Ubuntu or Debian image. This is a meaningful improvement. But lower CVE count is not zero CVE count, and the remaining CVEs are not trivially dismissible.
The residual CVE exposure in distroless images comes from two sources: the language runtime itself (JRE, Python, Node.js, Go runtime libraries all carry CVEs), and the application dependencies layered on top of the distroless base.
The Application Dependency Problem
The distroless base is only the beginning. Real applications add dependencies:
A Python web application on gcr.io/distroless/python3 will add packages via pip install for its framework, database clients, HTTP libraries, and business logic dependencies. A Java application on gcr.io/distroless/java will add JARs via its build system.
These dependencies bring CVEs. A Django application might install 30-50 Python packages as direct and transitive dependencies. Each carries its own CVE history. The distroless base’s minimal package count gets expanded substantially by the application’s own dependency tree.
The key insight: distroless solves the base image packaging problem (removing shells, system utilities, package managers). It does not solve the application dependency packaging problem — and in most applications, the application dependencies carry significant CVE exposure.
“Distroless removes the utilities you never needed. It does not remove the application dependencies you installed but might not actually use. That distinction is where the residual risk lives.”
The Unused Dependency Problem in Distroless Images
When a Python application specifies its dependencies in requirements.txt, it installs more than it uses. Transitive dependencies — packages that your direct dependencies require — are installed automatically. Not all of them are called by your application’s code paths.
A Flask web application might specify 12 direct dependencies. The installed package tree after pip install typically includes 40-60 packages due to transitive dependencies. Of those 60, your application code paths actively call maybe 25-30.
The other 30 packages are present in the container but not executed. On a distroless image, they still carry CVEs. And because distroless removes the standard system tools for analysis, the typical “look at what’s installed, remove the obvious ones” approach is harder to apply.
Runtime profiling resolves this precisely: the application runs, profiling captures which packages are actually imported and executed, and packages with zero execution evidence are removed. This approach works whether the base image is Ubuntu, Alpine, or distroless.
Alpine Linux: The Same Principle Applies
Alpine Linux is another common “minimal” base image choice. Smaller than Ubuntu/Debian, using musl libc and BusyBox, Alpine starts with fewer packages and correspondingly fewer CVEs.
But the same analysis applies: Alpine images still carry CVEs in their base package set, and applications layered on Alpine add further dependency exposure. Choosing Alpine over Ubuntu typically reduces CVE count by 40-60% compared to a full Ubuntu base. Runtime profiling and component removal achieves the same reduction percentage starting from an already-minimal Alpine base.
Hardened container images produced from Alpine base images often achieve lower absolute CVE counts than hardened Ubuntu images for equivalent applications, because they start from a smaller base. But the relative reduction percentage from hardening is similar.
What Hardening Adds on Top of Minimal Images?
Container image security that complements minimal base image selection:
Application dependency pruning: Runtime profiling identifies which of the 60 installed packages in a Python application are actually imported during execution. Packages with zero import evidence are removed from the final image even though they were installed by the dependency resolution process.
Language runtime minimization: Even the language runtime itself contains components that a specific application may not use. Python includes modules for email parsing, XML processing, and many other domains that a web application serving JSON APIs does not need. These can be removed.
Startup-only dependency removal: Some packages are needed during container initialization but not during steady-state operation. Profiling can identify this pattern and remove startup-only dependencies if they represent significant CVE exposure.
Regular base image updates: Even minimal base images receive CVE updates. Automated rebuild pipelines that pull updated base images and re-run hardening ensure that CVE reduction is maintained as the base image evolves.
Frequently Asked Questions
Is Distroless more secure than standard base images?
Distroless images are more secure by default because they omit shells, package managers, and standard Unix utilities, resulting in a meaningfully lower CVE count than a full Ubuntu or Debian base. However, distroless is not fully secure on its own — the language runtime and all application dependencies layered on top still carry CVEs. Container image security hardening through runtime profiling and component removal is needed to address the residual risk in both the runtime and application dependency layers.
Why do minimal base images still need security hardening?
Minimal base images like distroless or Alpine reduce the base package footprint, but real applications add 40–60 or more transitive dependencies on top. Many of those dependencies are never actually called by the application’s code paths. These unused packages carry CVEs and represent attack surface with no application value. Runtime profiling identifies which packages are genuinely executed, and removing the unused ones completes the security hardening that choosing a minimal base image begins.
How do you fix base image vulnerabilities in container images?
Fixing base image vulnerabilities requires a combination of two approaches: update the base image to a version where CVEs in system packages are patched, and remove application-layer packages that are not actually used by the application. Automated rebuild pipelines that pull updated base images and re-run runtime profiling-based hardening ensure that both base CVEs and unused dependency CVEs are continuously addressed as the image evolves.
What is the most effective container image security strategy for minimal base images?
The most effective strategy combines minimal base image selection with runtime profiling-based component removal. Choose the smallest compatible base — distroless if supported, Alpine if not — then use runtime profiling to identify which of the installed packages are actually executed during real application operation. Remove the unused packages, validate with functional tests, and automate the process so it repeats with every image update. This combination achieves the lowest possible CVE count for any given application.
The Practical Recommendation
For organizations choosing between base image options:
Use the smallest base image that is compatible with your application and operational requirements. Distroless if your application supports it. Alpine if not. Ubuntu/Debian only when the application requires it (some applications have system-level dependencies that require glibc or specific system libraries).
Then apply runtime profiling to every image regardless of base:
- Capture which packages are actually executed
- Remove unused packages (even from already-minimal bases)
- Validate with functional tests
- Automate to maintain the reduction as dependencies and base images update
The combination of minimal base image selection and runtime profiling-based component removal achieves the lowest possible CVE count for any given application. Minimal base selection alone is a significant improvement. Application-layer hardening on top of a minimal base is the complete answer.