GoogleContainerTools / distroless

🥑 Language focused docker images, minus the operating system.
Apache License 2.0
18.59k stars 1.14k forks source link

Using official Python base images and packaging into distroless later on #1543

Open artursmet opened 5 months ago

artursmet commented 5 months ago

Hello,

I’ve been using official Docker images for python (eg. python:3.11-slim-bookworm) in my projects and I wanted to use multi stage build approach to package the final image into distroless one.

I’ve found an instruction of how to do it in this repo - https://github.com/GoogleContainerTools/distroless/tree/main/examples/python3-requirements but I wonder if it would be possible to use an official python docker image instead of installing an apt-based python distro from debian repos.

My attempt so far was like this:

FROM python:3.11-slim-bookworm AS build-venv

WORKDIR /opt/app/
ADD requirements.txt /opt/app/
RUN python3 -m venv /venv
RUN /venv/bin/pip install -r /opt/app/requirements.txt

FROM gcr.io/distroless/python3-debian12
COPY --from=build-venv /venv /venv
COPY . /app
WORKDIR /app
ENTRYPOINT ["/venv/bin/uvicorn", "app:app", "--host", "0.0.0.0", "--port", "9090"]
EXPOSE 9090

However, the virtualenv produced by official python image does not work, because python in that image is compiled, not sourced from official Debian repositories (it has an additional benefit of being able to deliver most recent python version, without waiting for Debian maintainers).

So the python binary is located in /usr/local/bin/python3.11 in this case. However, on the distoless image, the python binary is located in /usr/bin/python3.11. As an effect, the container does not boot because the binary path inside the virtualenv is incorrect. This is because the virtualenv configuration:

/app # cat /venv/pyvenv.cfg 
home = /usr/local/bin
include-system-site-packages = false
version = 3.11.8
executable = /usr/local/bin/python3.11
command = /usr/local/bin/python3 -m venv /venv
/app # 

I’ve tried to work around it and add a symlink into the final image, it worked fine in the debug image in interactive shell:


/app # /venv/bin/python --version
sh: /venv/bin/python: not found
/app # mkdir -p /usr/local/bin && ln -s /usr/bin/python3 /usr/local/bin/python3 && ln -s /usr/bin/python3 /usr/local/bin/python3.11
/app # /venv/bin/python --version
Python 3.11.2
/app # 

But after adding it into the Dockerfile:

RUN mkdir -p /usr/local/bin && ln -s /usr/bin/python3 /usr/local/bin/python3 && ln -s /usr/bin/python3 /usr/local/bin/python3.11

It produces the following build error:

failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "/bin/sh": stat /bin/sh: no such file or directory: unknown

Is there a way to use the official python base image and later on carry over the virtualenv from this image into a distroless base? In my opinion it will bring a lot of benefits and allow to use most recent python versions in distroless (even in my case - official python image has 3.11.8 while the debian-repo-based distroless has 3.11.2 - both use Debian 12 bookworm)

ding-ma commented 5 months ago

I made it worked this way

  1. Install pip/peotry into a distroless container (build container). Make sure to run in exec/vector mode as there is no shell.
  2. Build your project
  3. Copy the python sys dist-package (/usr/local/lib/python3.11/dist-packages) into a new distroless container (deploy)

That way you have one a container that only has your app with its dependencies - no build tools. Hope this helps

joonvena commented 2 months ago

I made it worked this way

1. Install pip/peotry into a distroless container (build container). Make sure to run in exec/vector mode as there is no shell.

2. Build your project

3. Copy the python sys dist-package (/usr/local/lib/python3.11/dist-packages) into a new distroless container (deploy)

That way you have one a container that only has your app with its dependencies - no build tools. Hope this helps

@ding-ma Could you provide simple example Dockerfile for this implementation?

cstruck commented 2 months ago

ARG PYTHON_VERSION=3.11
FROM python:${PYTHON_VERSION}-slim AS requirements-stage

ARG POETRY_VERSION=1.7.1

WORKDIR /tmp
RUN pip install --no-cache-dir "poetry==${POETRY_VERSION}"
COPY ./pyproject.toml ./poetry.lock* /tmp/

RUN poetry export -f requirements.txt --output requirements.txt --without-hashes \
  && pip install --no-cache-dir --upgrade -r requirements.txt
  && chmod 0755 /usr/local/bin/uvicorn 

FROM gcr.io/distroless/python3-debian12:nonroot
COPY --from=requirements-stage /usr/local/lib/python${PYTHON_VERSION}/site-packages /usr/local/lib/python${PYTHON_VERSION}/site-packages
COPY --from=requirements-stage /usr/local/bin/uvicorn /app/uvicorn

COPY . /app
WORKDIR /app
CMD ["/app/uvicorn", "app:app", "--host", "0.0.0.0", "--port", "9090"]
ENV PYTHONPATH=/usr/local/lib/python${PYTHON_VERSION}/site-packages
...
ding-ma commented 1 month ago
ARG PYTHON_VERSION=3.11

FROM golang as builder
- download pip

FROM gcr.io/distroless/python3-debian12:latest as py-build
- user root:root  (need root to build)
- copy over pip
- RUN ["python", "/tmp/get-pip.py"]
- RUN ["pip", "install", "--no-cache-dir","poetry"]
- RUN ["poetry", "config", "virtualenvs.create", "false"]
- RUN ["poetry", "install", "--no-cache"]  --- poetry install rest of your deps
- NOTE: you can't chain them since there is no shell. You have to run them in the vector form show above

FROM gcr.io/distroless/python3-debian12:nonroot

# poetry installs under dist-packages
COPY --from=py-build /usr/local/lib/python${PYTHON_VERSION}/dist-packages /usr/local/lib/python${PYTHON_VERSION}/dist-packages
COPY --from=py-build /usr/local/bin/uvicorn /app/uvicorn

COPY . /app
WORKDIR /app
CMD ["/app/uvicorn", "app:app", "--host", "0.0.0.0", "--port", "9090"]
ENV PYTHONPATH=/usr/local/lib/python${PYTHON_VERSION}/site-packages

Hope that helps :) let me know if you have any other questions