Distroless Docker Images is a project proposed by Google in order to help building slimmer containers. The project description states it’s "Language focused docker images, minus the operating system". Sounds interesting, right?
Compared to common base images like ubuntu, alpine, debian which have lots of OS packages and libraries that might not be required for your application, Distroless Images have only what you need, to run your application.
It’s worth noting that Distroless containers are not always the most secured solution as explained by RedHat in this article.
Although it'd be hard to package a python application since its standard library relies on some higher-level OS capabilities, it’s possible to achieve that.
Python distroless containers require multi-stage building process because the gcr.io/distroless/python3
image has neither pip
nor even easy_install
.
Let’s try to package a FastAPI api with “Distroless” Docker Images.
Here is a sample api server:
import fastapi, uvicorn
from starlette.requests import Request
import prometheus_client
import os
api = fastapi.FastAPI()
REQUESTS = prometheus_client.Counter(
'requests', 'Application Request Count',
['endpoint']
)
@api.get('/ping')
def index(request: Request):
REQUESTS.labels(endpoint='/ping').inc()
return "pong"
@api.get('/metrics')
def metrics():
return fastapi.responses.PlainTextResponse(
prometheus_client.generate_latest()
)
if __name__ == "__main__":
print("Starting webserver...")
uvicorn.run(
api,
host="0.0.0.0",
port=int(os.getenv("PORT", 8080)),
debug=os.getenv("DEBUG", False),
log_level=os.getenv('LOG_LEVEL', "info"),
proxy_headers=True
)
We will use Pipenv as package manager. Here is our Pipfile:
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
fastapi = "==0.77.1"
uvicorn = "==0.17.6"
prometheus-client = "==0.14.1"
Jinja2 = "==3.1.2"
[dev-packages]
[requires]
python_version = "3.10"
So, I ended up with the following Dockerfile (commented) allowing me to package a FastAPI application with the distroless Python image.
First Stage:
FROM python:3.10-slim AS base
# Setup env
## Avoid to write .pyc files on the import of source modules
ENV PYTHONDONTWRITEBYTECODE 1
# Enable fault handler
ENV PYTHONFAULTHANDLER 1
Second Stage:
# Dependencies
FROM base AS python-deps
### Install pipenv and compilation dependencies
RUN pip install pipenv \
&& apt-get update \
&& apt-get install -y --no-install-recommends gcc
### Install python dependencies in /.venv
COPY Pipfile .
COPY Pipfile.lock .
# Allows to install the pipenv packages into the project instead of home user
# --deploy
RUN PIPENV_VENV_IN_PROJECT=1 pipenv install --deploy
Third Stage:
# Runtime
FROM gcr.io/distroless/python3
WORKDIR /app
# Copy the python packages because the distroless base image does
COPY --from=python-deps /.venv/lib/python3.10/site-packages /app/site-packages
# Set the Python path where the interpreter will look for the packages
ENV PYTHONPATH /app/site-packages
COPY . .
EXPOSE 8080
ENTRYPOINT ["python", "app.py"]
The resulting image was around 25Mb.
You could find all the code on my github repository.
If you have any points to or not to use “Distroless” for python applications, feel free to drop in comment.
Top comments (2)
Is there a way to make sure that the distroless image stays on a specific Python version?
Hi Adam,
I'm not sure that we can specify the Python version.
There are no custom tags here.
The current version is 3.11.
You can still search for the Commit ID to retrieve the Docker Tag of a specific version.