~/post/package-fastapi-with-distroless
Published on

Package your FastAPI application with “Distroless” Docker Images

3 mins read
Authors
  • Name
    Twitter

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.

Image description

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 get in touch with me.