Distroless: Balancing Security with Practicality
Distroless images are a type of Docker image designed to be as minimal as possible, containing only the essential components required to run a specific application.
Advocates of Distroless claim that this minimalism enhances security by reducing the attack surface. But does this approach truly simplify security, or does it add unnecessary complexity for limited gains? In this post, we’ll weigh the pros and cons of Distroless images and explore where they are best utilized — and where they might be more trouble than they’re worth.
Container image vulnerabilities
Container image vulnerabilities are security flaws in base images, often due to outdated or unpatched packages.
Most Docker images are based on a Linux distro, including many of the same packages and libraries as VM images, which contributes to their larger size.
These image packages may already have known vulnerabilities (CVEs). The security team reports these vulnerabilities to the product team, who then upgrades the distro or packages. Over time, new vulnerabilities emerge, and this reporting-and-fixing cycle continues.
Distroless images came into the picture because they claim that as they have fewer packages, the known vulnerabilities in the image are also very few.
We'll select a random open-source project from GitHub and build it using Debian, Alpine, and Distroless base images. The results will then be compared in terms of size and security vulnerabilities.
Prioritizing the fixing of container image vulnerabilities
Container image vulnerabilities are generally less critical than SCA, SAST, and DAST issues. To understand this, it's important to clarify the distinction between SCA and image (Container) vulnerabilities.
Image vulnerabilities are found in the base container image, often in packaged components or dependencies that the application doesn’t actively use. Since these components may not be running or exposed, they are less likely to be exploited, even if the application is accessible externally.
In contrast, SCA bugs refer to vulnerabilities in libraries directly used by the application. If the application is exposed, these vulnerabilities can be exploited externally.
If you ask if it is needed to fix the image vulnerabilities, I suggest focusing on SCA and SAST before the image vulnerabilities.
Building the sample app
Selection of the sample project:
The criteria of the selection are that the project given below
It should be a web app
it should be written in an interpreted language
It should be a decently maintained and project
Why did I choose an interpreted language-based project?
Two main reasons to choose are that, as far as I saw, most of today’s web apps are built in interpreted languages, and it is easy to show the problems that occur when building and starting the application.
With those criteria, wagtail was selected randomly using a GitHub search.
The image is built using four different docker base images for comparison
The dockerfile for each image is given in the Appendices section
Size and vulnerabilities comparison
Image Base | Final Image Size | Vulnerabilities in image |
python:3.13-slim (Debian) | 263MB | Total: 82 (LOW: 62, MEDIUM: 17, HIGH: 2, CRITICAL: 1) |
python:3.13-alpine | 170MB | Total: 2 (LOW: 2, MEDIUM: 0, HIGH: 0, CRITICAL: 0) |
python3-debian12 (Google’s Distroless) | 184MB | Total: 58 (LOW: 32, MEDIUM: 13, HIGH: 12, CRITICAL: 1) |
python:latest (Chainguard’s Distroless) | 182MB | Total: 0 (LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0) |
Unexpectedly, the alpine base image had the smallest image size after the build. Based on the number of vulnerabilities, chainguard’s image was good, but Alpine also performed well.
Difficulties in using distroless
While building the sample app, the Debian and Alpine bases build was pretty straightforward and didn’t create any issues in both build and runtime.
Google’s Distroless image had problems in build time; by fixing it, the dockerfile grew larger and became much more complex, and it will be a problem in the long term to maintain it.
On the other hand, Chainguard’s Distroless image didn’t create problems in build time, but in run time, an import error occurred for the tzdata
library, which was missing.
While building with a distroless base, the following issues usually occur
Shared system libs missing in the final image
Codebase or dependencies are using shell commands
Issues in migrating old projects
Community support
Shared library
The most common issue in using distroless base images is shared library issues. As prod distroless images will not have shell and package manager, the multi-stage build is used in docker. Usually, the first stage will be a dev distroless image or a general Linux distro.
Some libraries will use the system’s shared library. It will check the dependent shared library while installing. However, in the multi-stage build, the shared library will not be present in the final distroless image. So, while running the application, It will break as it doesn’t have a shared library in the final image. This problem mostly occurs in interpreted language applications.
Shell commands
This issue is tricky because it will break the application when a specific function or a library is called. Think of an application codebase with more than 10k lines of code and 100 dependencies. We will never know which dependency or which line has shell commands, and it is very important to note that busy box tools will not be present in the prod distroless images, so /bin/sh won’t work.
So even an application calling a simple bash command like python —version
will break even though Python is present in the image (It is pro as well; system command Injection won’t work)
So, the application requires thorough QA testing.
Issues in migrating old projects
Old projects mostly would have written assuming shell will always be present, mostly in the initialization, DB migration, and Debugging shell commands might be used, and I have seen in some products that the child process will be running in the same container for exporting logs, or as a background process.
To move this kind of project to distroless, the developer team’s efforts and knowledge of product design/working are absolutely necessary. In addition to that, to maintain it, DevOps should have the knowledge to debug the application without being able to enter the container, and the application should have a robust logging mechanism.
Community support
One of the important problems is community support. The errors that occur during the build and run-time are mostly not straightforward. It is very normal to see the same code run in a distro docker image but not in a distroless one. Community discussions and the posts in the forums related to it are also very few. (You won’t get the solution in the ChatGPT as well for now)
Chainguard images are not fully open source, and we will never know when Google will discontinue maintaining Google’s distroless images.
Advantages of distroless
It is minimal in size, has fewer vulnerabilities, and prevents command injection. As the size is smaller, it reduces the cost and image-pulling time.
System Command Injection
As the distroless doesn’t have /bin/sh, command injection won’t work even if the application gets input from a user and executes it as a shell command (actually, the application also can not use shell code, So the application should not have shell code if it is running in distroless),
However, it is important to note that argument injection and language-based injections, such as SSTI and EL injections, will still work.
Conclusion:
Distroless images are useful in terms of reducing image vulnerabilities, but it is important to prioritize when to spend resources and time to work on image vulnerabilities.
To move an existing application to distroless, the efforts of developers, QA, and security engineering teams are necessary. If that much effort can not be spent, it is better to move to a slim distroless image like the debian-type slim or alpine images.
As we can see from the comparison, alpine performed better in size and number of vulnerabilities than Google’s distroless. It changes case by case, especially when using static-type distroless images for compiled binaries. Still, it is good to keep in mind that a multi-stage and slim base image selection will also reduce vulnerabilities and size.
If the product is new or has just started developing, or the code base and complexity are much less, that is the best time to use distroless.
Appendices
Dockerfile for debian-based build
FROM python:3.13-slim AS build_env
RUN pip install wagtail &&\
wagtail start mysite
WORKDIR /mysite
RUN pip install -r requirements.txt -t . &&\
python manage.py migrate &&\
python manage.py createsuperuser \
--username root \
--email reachme@merb.in \
--no-input
FROM python:3.13-slim
WORKDIR /mysite
COPY --from=build_env /mysite /mysite
ENTRYPOINT ["python","manage.py","runserver","0.0.0.0:8000"]
Dockerfile for alpine-based build
FROM python:3.13-alpine AS build_env
RUN pip install wagtail &&\
wagtail start mysite
WORKDIR /mysite
RUN pip install -r requirements.txt -t . &&\
python manage.py migrate &&\
python manage.py createsuperuser \
--username root \
--email reachme@merb.in \
--no-input
FROM python:3.13-alpine
WORKDIR /mysite
COPY --from=build_env /mysite /mysite
ENTRYPOINT ["python","manage.py","runserver","0.0.0.0:8000"]
Dockerfile for google’s distroless-based build
FROM python:3.11-slim AS pip_builder
# Install the required packages
RUN pip install --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org --upgrade pip
RUN pip install --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org requests simplejson python-json-logger
FROM gcr.io/distroless/python3-debian12:debug AS sh_copier
# Switch to the distroless image
FROM gcr.io/distroless/python3-debian12:debug AS build_env
# Copy the installed packages
COPY --from=pip_builder /usr/local/lib/python3.11/site-packages /usr/lib/python3.11/site-packages
COPY --from=pip_builder /usr/local/bin /usr/local/bin
COPY --from=pip_builder /usr/local/lib/ /usr/local/lib/
COPY --from=sh_copier /busybox/sh /bin/sh
RUN pip install wagtail &&\
wagtail start mysite
WORKDIR /mysite
RUN pip install -r requirements.txt -t . &&\
python manage.py migrate &&\
python manage.py createsuperuser \
--username root \
--email reachme@merb.in \
--no-input
FROM gcr.io/distroless/python3-debian12:latest
WORKDIR /mysite
COPY --from=build_env /mysite /mysite
ENTRYPOINT ["python","manage.py","runserver","0.0.0.0:8000"]
Dockerfile for chainguard’s distroless-based build
FROM cgr.dev/chainguard/python:latest-dev AS build_env
USER root
RUN pip install wagtail &&\
wagtail start mysite
WORKDIR /mysite
RUN pip install -r requirements.txt -t . &&\
pip install tzdata -t . &&\
python manage.py migrate &&\
python manage.py createsuperuser \
--username root \
--email reachme@merb.in \
--no-input
FROM cgr.dev/chainguard/python:latest
USER root
WORKDIR /mysite
COPY --from=build_env /mysite /mysite
ENTRYPOINT ["python","manage.py","runserver","0.0.0.0:8000"]
Subscribe to my newsletter
Read articles from Merbin Russel directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Merbin Russel
Merbin Russel
Security Engineer with expertise in application security, source code review, Design review, DevSecOps, Cloud Security and Detection Engineering(SecOps).