Package your FastAPI application with “Distroless” Docker Images
Tags: python, fastapi, docker, distroless
Published: 2022-07-19 08:16:02

[Distroless Docker Images](https://github.com/GoogleContainerTools/distroless) 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](https://www.redhat.com/en/blog/why-distroless-containers-arent-security-solution-you-think-they-are).

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](https://fastapi.tiangolo.com/) api with “Distroless” Docker Images.

Here is a sample api server:

```python
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:

```toml
[[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:**

```docker
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:**

```docker
# 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:**

```docker
# 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.

![Docker Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o7203ucn95qmsxqe3jq2.png)
 
You could find all the code on [my github repository]( https://github.com/abdelino17/python-api).


If you have any points to or not to use “**Distroless**” for python applications,  feel free to drop in comment.