DEV Community

abdelino17
abdelino17

Posted on • Updated on • Originally published at blog.abdelfare.me

Package your FastAPI application with “Distroless” Docker Images

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
    )
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

The resulting image was around 25Mb.

Docker Image

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.

Latest comments (2)

Collapse
 
adamzr profile image
Adam Richeimer

Is there a way to make sure that the distroless image stays on a specific Python version?

Collapse
 
abdelino17 profile image
abdelino17 • Edited

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.