Python and distroless: A simplified guide
Published: 2023-10-12 01:07:32

While this guide's focus is on python, the general advice here applies to anyone trying to build docker images on top of [distroless](https://github.com/GoogleContainerTools/distroless) base images.

## No dependencies
If you have a simple python script with no dependencies, you can follow [this](https://github.com/GoogleContainerTools/distroless/tree/main/examples/python3) guide, whose Dockerfile is reproduced below.
```dockerfile
FROM python:3-slim AS build-env
COPY . /app
WORKDIR /app

FROM gcr.io/distroless/python3
COPY --from=build-env /app /app
WORKDIR /app
CMD ["hello.py", "/etc"]
```

## Pure-python dependencies
If you have a `requirements.txt` but no additional native dependencies you can follow [this](https://github.com/GoogleContainerTools/distroless/tree/main/examples/python3-requirements) guide
```dockerfile
# Build a virtualenv using the appropriate Debian release
# * Install python3-venv for the built-in Python3 venv module (not installed by default)
# * Install gcc libpython3-dev to compile C Python modules
# * In the virtualenv: Update pip setuputils and wheel to support building new packages
FROM debian:11-slim AS build
RUN apt-get update && \
    apt-get install --no-install-suggests --no-install-recommends --yes python3-venv gcc libpython3-dev && \
    python3 -m venv /venv && \
    /venv/bin/pip install --upgrade pip setuptools wheel

# Build the virtualenv as a separate step: Only re-execute this step when requirements.txt changes
FROM build AS build-venv
COPY requirements.txt /requirements.txt
RUN /venv/bin/pip install --disable-pip-version-check -r /requirements.txt

# Copy the virtualenv into a distroless image
FROM gcr.io/distroless/python3-debian11
COPY --from=build-venv /venv /venv
COPY . /app
WORKDIR /app
ENTRYPOINT ["/venv/bin/python3", "psutil_example.py"]
```

## Native dependencies
Finally if we have native dependencies, we can download them from a debian-based build stage, extract their contents to a temp directory (i.e. `/dpkg`), then copy those into the root distroless image during the final stage.

One way to generate a list of dependencies needed is to call `apt-cache depends <your-lib-here>`.
In our case we want to install the [psycopg2](https://pypi.org/project/psycopg2/) library, that has a native dependency on libpq. To find the package (and its dependencies) in debian, we'll first need the name of the package.
```
$ apt-get update
<snip>
Reading package lists... Done
$ apt-cache search libpq
<snip>
libpq5 - PostgreSQL C client library
```
Now that we have the name of the package (`libpq5`) we can see what dependencies it has.
```
$ apt-cache depends --recurse libpq5
libpq5
  Depends: libc6
  Depends: libgssapi-krb5-2
  Depends: libldap-2.4-2
  Depends: libssl1.1
```
We can see the pre-installed distroless dependencies listed in the [python](https://github.com/GoogleContainerTools/distroless/blob/main/experimental/python3/BUILD#L22) build file, the [cc](https://github.com/GoogleContainerTools/distroless/blob/main/cc/BUILD#L11) build file and the [base](https://github.com/GoogleContainerTools/distroless/blob/main/base/BUILD) build file. Looks like we'll need to add these dependencies.
NOTE: Dependencies might have dependencies, you can use the script [here](https://gist.github.com/tuananh/1e8e0f921410a830a7cd1161ff8bb189) (written by [@tuananh](https://gist.github.com/tuananh)) to get all dependencies.

### Final `Dockerfile`

```dockerfile
# Build a virtualenv using the appropriate Debian release
# * Install python3-venv for the built-in Python3 venv module (not installed by default)
# * Install gcc libpython3-dev to compile C Python modules
# * In the virtualenv: Update pip setuputils and wheel to support building new packages
FROM debian:11-slim AS build
RUN apt-get update && \
    apt-get install --no-install-suggests --no-install-recommends --yes python3-venv gcc libpython3-dev && \
    python3 -m venv /venv && \
    /venv/bin/pip install --upgrade pip setuptools wheel
RUN cd /tmp && \
    apt-get update && \
    apt-get install -y --no-install-recommends \
        # install only deps
        curl \
        ca-certificates \
        openssl \
        && \
    apt-get download \
        # ca-certificates \
        \
        # additional python dependencies
        libpq5 \
        && \
    mkdir -p /dpkg/var/lib/dpkg/status.d/ && \
    for deb in *.deb; do \
        package_name=$(dpkg-deb -I ${deb} | awk '/^ Package: .*$/ {print $2}'); \ 
        echo "Process: ${package_name}"; \
        dpkg --ctrl-tarfile $deb | tar -Oxf - ./control > /dpkg/var/lib/dpkg/status.d/${package_name}; \
        dpkg --extract $deb /dpkg || exit 10; \
    done

# Build the virtualenv as a separate step: Only re-execute this step when requirements.txt changes
FROM build AS build-venv
COPY requirements.txt /requirements.txt
RUN /venv/bin/pip install --disable-pip-version-check -r /requirements.txt

# Copy the virtualenv into a distroless image
FROM gcr.io/distroless/python3-debian11
COPY --from=build-venv /venv /venv
COPY --from=build/dpkg /
COPY . /app
WORKDIR /app
ENTRYPOINT ["/venv/bin/python3", "psutil_example.py"]
```