distroless icon indicating copy to clipboard operation
distroless copied to clipboard

support different python versions (3.12, 3.13)

Open gulldan opened this issue 1 year ago • 10 comments
trafficstars

Hello, is there any more allegiant solution for using python 3.12, 3.13 than https://github.com/GoogleContainerTools/distroless/issues/1543#issuecomment-2432044825 ?

now i do understand nothing about bazel to do it myself.

Maybe you can recommend some step by step documentation how to do it? readme doesnt help me

thank

gulldan avatar Oct 24 '24 09:10 gulldan

There's are a few suggestions in other issues. But it's not really our mission to provide this.

loosebazooka avatar Oct 24 '24 12:10 loosebazooka

Not sure if more elegant, but I ended up copying binaries and libs into Distroless CC from the official Python image, which also serves as local development image.

In my case production is always amd64 anyway, so didn't bother to make it work with arm.

I understand if the Distroless team doesn't want to maintain multiple Python images, but would be good to document which version it is (3.11.2) so people at least know what to expect.

# Development dependencies stage
FROM python:3.12.7-bookworm as deps

SHELL ["/bin/bash", "-o", "pipefail", "-c"]
WORKDIR /projects/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

ENTRYPOINT ["/bin/bash"]

# Production stage using CC Distroless
FROM --platform=linux/amd64 gcr.io/distroless/cc-debian12:nonroot AS prod

ARG PYTHON_VERSION=3.12
ENV PYTHONPATH=/usr/local/lib/python${PYTHON_VERSION}/site-packages
ENV PATH="/usr/local/bin:${PATH}"
ENV LD_LIBRARY_PATH=/usr/local/lib:/lib/x86_64-linux-gnu:${LD_LIBRARY_PATH:-}
WORKDIR /projects/app

# Copy Python dependencies from deps stage
COPY --from=deps /usr/local/lib/python${PYTHON_VERSION}/site-packages /usr/local/lib/python${PYTHON_VERSION}/site-packages
# Copy the Python interpreter with a specific name
COPY --from=deps /usr/local/bin/python${PYTHON_VERSION} /usr/local/bin/pythonapp
# Copy Python library files including shared libraries
COPY --from=deps /usr/local/lib/python${PYTHON_VERSION} /usr/local/lib/python${PYTHON_VERSION}
COPY --from=deps /usr/local/lib/libpython${PYTHON_VERSION}.so* /usr/local/lib/
# Copy system libraries for x86_64
COPY --from=deps /lib/x86_64-linux-gnu/lib* /lib/x86_64-linux-gnu/

# Copy application code
COPY . ./

USER nonroot

ENTRYPOINT ["/usr/local/bin/pythonapp"]
CMD ["/projects/app/main.py"]

daaain avatar Nov 11 '24 18:11 daaain

Not sure if more elegant, but I ended up copying binaries and libs into Distroless CC from the official Python image, which also serves as local development image.

In my case production is always amd64 anyway, so didn't bother to make it work with arm.

I understand if the Distroless team doesn't want to maintain multiple Python images, but would be good to document which version it is (3.11.2) so people at least know what to expect.

# Development dependencies stage
FROM python:3.12.7-bookworm as deps

SHELL ["/bin/bash", "-o", "pipefail", "-c"]
WORKDIR /projects/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

ENTRYPOINT ["/bin/bash"]

# Production stage using CC Distroless
FROM --platform=linux/amd64 gcr.io/distroless/cc-debian12:nonroot AS prod

ARG PYTHON_VERSION=3.12
ENV PYTHONPATH=/usr/local/lib/python${PYTHON_VERSION}/site-packages
ENV PATH="/usr/local/bin:${PATH}"
ENV LD_LIBRARY_PATH=/usr/local/lib:/lib/x86_64-linux-gnu:${LD_LIBRARY_PATH:-}
WORKDIR /projects/app

# Copy Python dependencies from deps stage
COPY --from=deps /usr/local/lib/python${PYTHON_VERSION}/site-packages /usr/local/lib/python${PYTHON_VERSION}/site-packages
# Copy the Python interpreter with a specific name
COPY --from=deps /usr/local/bin/python${PYTHON_VERSION} /usr/local/bin/pythonapp
# Copy Python library files including shared libraries
COPY --from=deps /usr/local/lib/python${PYTHON_VERSION} /usr/local/lib/python${PYTHON_VERSION}
COPY --from=deps /usr/local/lib/libpython${PYTHON_VERSION}.so* /usr/local/lib/
# Copy system libraries for x86_64
COPY --from=deps /lib/x86_64-linux-gnu/lib* /lib/x86_64-linux-gnu/

# Copy application code
COPY . ./

USER nonroot

ENTRYPOINT ["/usr/local/bin/pythonapp"]
CMD ["/projects/app/main.py"]

ty also i can find a different way to do same

FROM python:3.13-slim-bookworm AS builder
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy UV_PYTHON_INSTALL_DIR=/python
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
RUN uv python install 3.13

WORKDIR /app
RUN --mount=type=bind,source=uv.lock,target=uv.lock \
    --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
    uv sync --frozen --no-dev --no-cache --no-editable --no-install-project --python-preference only-managed
ADD . /app

# Then, use a final image without uv
FROM --platform=linux/amd64 gcr.io/distroless/cc-debian12:nonroot as prod
ARG PYTHON_VERSION=3.13
ENV PATH="/usr/local/bin:${PATH}"

# Copy system libraries for x86_64
COPY --from=builder /lib/x86_64-linux-gnu/lib* /lib/x86_64-linux-gnu/
COPY --from=builder --chown=python:python /python /python
# Copy the application from the builder
COPY --from=builder --chown=app:app /app /app

# Place executables in the environment at the front of the path
ENV PATH="/app/.venv/bin:$PATH"

# Run the FastAPI application by default
ENTRYPOINT ["granian", "--interface", "asgi", "/app/main:app", "--port", "8000", "--host", "0.0.0.0"]

gulldan avatar Nov 12 '24 15:11 gulldan

Oh cool, so by installing Python with uv you can get all the binaries and libraries under one directory (UV_PYTHON_INSTALL_DIR) so it's fewer paths to copy? It's probably much less disk space too because you only get stuff that Python will actually use?

Do you even need the Python image for the builder or a C (or even base Debian) one might be enough because you're copying uv from another image anyway?

I just realised that my Dockerfile might need another stage as currently the dev dependencies will get copied into the prod image too 🤔

daaain avatar Nov 12 '24 16:11 daaain

im not sure will need investigate.

Oh cool, so by installing Python with uv you can get all the binaries and libraries under one directory (UV_PYTHON_INSTALL_DIR) so it's fewer paths to copy? It's probably much less disk space too because you only get stuff that Python will actually use?

Do you even need the Python image for the builder or a C (or even base Debian) one might be enough because you're copying uv from another image anyway?

I just realised that my Dockerfile might need another stage as currently the dev dependencies will get copied into the prod image too 🤔

gulldan avatar Nov 12 '24 16:11 gulldan

Not sure if more elegant, but I ended up copying binaries and libs into Distroless CC from the official Python image, which also serves as local development image.

In my case production is always amd64 anyway, so didn't bother to make it work with arm.

I understand if the Distroless team doesn't want to maintain multiple Python images, but would be good to document which version it is (3.11.2) so people at least know what to expect.

# Development dependencies stage
FROM python:3.12.7-bookworm as deps

SHELL ["/bin/bash", "-o", "pipefail", "-c"]
WORKDIR /projects/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

ENTRYPOINT ["/bin/bash"]

# Production stage using CC Distroless
FROM --platform=linux/amd64 gcr.io/distroless/cc-debian12:nonroot AS prod

ARG PYTHON_VERSION=3.12
ENV PYTHONPATH=/usr/local/lib/python${PYTHON_VERSION}/site-packages
ENV PATH="/usr/local/bin:${PATH}"
ENV LD_LIBRARY_PATH=/usr/local/lib:/lib/x86_64-linux-gnu:${LD_LIBRARY_PATH:-}
WORKDIR /projects/app

# Copy Python dependencies from deps stage
COPY --from=deps /usr/local/lib/python${PYTHON_VERSION}/site-packages /usr/local/lib/python${PYTHON_VERSION}/site-packages
# Copy the Python interpreter with a specific name
COPY --from=deps /usr/local/bin/python${PYTHON_VERSION} /usr/local/bin/pythonapp
# Copy Python library files including shared libraries
COPY --from=deps /usr/local/lib/python${PYTHON_VERSION} /usr/local/lib/python${PYTHON_VERSION}
COPY --from=deps /usr/local/lib/libpython${PYTHON_VERSION}.so* /usr/local/lib/
# Copy system libraries for x86_64
COPY --from=deps /lib/x86_64-linux-gnu/lib* /lib/x86_64-linux-gnu/

# Copy application code
COPY . ./

USER nonroot

ENTRYPOINT ["/usr/local/bin/pythonapp"]
CMD ["/projects/app/main.py"]

Thank you it worked for me! But do you think there's a way to customize the BUILD file for the original distroless python to adapt it to an higher python version like 3.12? I'm trying to use this distroless python image, because it's smaller than your cc version. Thank you!

CKunath-ck avatar Nov 15 '24 15:11 CKunath-ck

Thank you it worked for me! But do you think there's a way to customize the BUILD file for the original distroless python to adapt it to an higher python version like 3.12? I'm trying to use this distroless python image, because it's smaller than your cc version. Thank you!

I'm not sure if I understood the question, but the purpose of this setup is that you can use different Python versions in the first stage (either by changing the version of the base image in my solution or downloading a different one with uv in gulldan's version) and then copy those binaries into the distroless image. I don't know if there can be a problem with the C version being different in the distroless C/C++ image to the one in the builder image, you probably need to align the images so there aren't several years between when they were created.

daaain avatar Nov 15 '24 15:11 daaain

Interested by this too, because we have some requirement on python3.12 and distroless image only runs 3.11 atm.

Solutions are interesting but we have to support both x86_64 and aarch64, so moving system files is not a good idea and makes the file less maintainable.

🎏

davinkevin avatar Jan 29 '25 11:01 davinkevin

I've tried using uv to install specific python version. This minimal version seems to be working. I will try on larger apps

FROM debian:12-slim AS builder

COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
ENV UV_PYTHON_INSTALL_DIR=/tmp/python

COPY .python-version .

RUN uv python install \
 && cd $UV_PYTHON_INSTALL_DIR \
 && version_dir=$(ls $UV_PYTHON_INSTALL_DIR) \
 && mkdir -p /opt/python \
 && mv $version_dir/* /opt/python

FROM gcr.io/distroless/base-debian12

COPY --from=builder /opt/python /opt/python

ENV PATH=/opt/python/bin:$PATH
ENV PYTHONHOME=/opt/python

WORKDIR /app
COPY main.py .

ENTRYPOINT ["python3"]

enkhjile avatar May 10 '25 02:05 enkhjile

@enkhjile Consider using gcr.io/distroless/cc for runtime since it includes libgcc.
This would solve the ImportError: libgcc_s.so.1: cannot open shared object file: No such file or directory errors.

The example below uses /usr/local for Python interpreter, dependencies, and app package installation. There is no venv.
uv is invoked twice — once to install the interpreter, and a second time to install the project's dependencies and create module shims.

In my pyproject.toml, I use hatchling as a build-backend and specify entry points of the package in the [project.scripts] section.

FROM debian:12-slim AS builder

RUN --mount=type=bind,source=.,target=/app \
    --mount=from=ghcr.io/astral-sh/uv,source=/uv,target=/usr/bin/uv \
    --mount=type=cache,target=/root/.cache/uv \
    <<EOF

    set -Eeux

    # Install specific Python version from .python-version
    uv python install --project=/app --managed-python --install-dir=/tmp/python

    # Move Python into /usr/local
    (cd /tmp/python/* && tar -cf- .) | (cd /usr/local && tar -xf-)
    rm -r /tmp/python

    # Install dependencies and the package
    export UV_PROJECT_ENVIRONMENT=/usr/local
    uv sync --project=/app --frozen --compile-bytecode --link-mode=copy --no-dev --no-editable --no-managed-python
EOF

# Using distroless as a main runtime image
# (add "debug-" tag prefix if you need a shell/busybox binary)
FROM gcr.io/distroless/cc-debian12:nonroot

# Copy Python interpreter and the package from the builder stage
COPY --from=builder /usr/local /usr/local

# Run as non-root
USER nonroot

ENTRYPOINT ["/usr/local/bin/py-hello-cmd"]

realdimas avatar May 12 '25 03:05 realdimas

Any progress for this?

all major OSs are using python3.12 by default, it will be nice to support this as well directly gcr.io/distroless/python3.12-debian12. (3.13 is latest now)

larrycai avatar Jul 17 '25 08:07 larrycai

Python is provided as is from what debian gives us. If someone wants to mirror what we do for java/node here, happy to accept and review a PR.

loosebazooka avatar Jul 17 '25 14:07 loosebazooka

as Debian Trixie contains 3.13, soon it will be available for gcr.io/python3-debian13 which use python3.13.

Any plan or related tickets to follow?

larrycai avatar Aug 10 '25 18:08 larrycai

Any news on gcr.io/python3-debian13 ?

Kerwood avatar Aug 28 '25 19:08 Kerwood

I maintain a fork of this project a work. We build for Python 3.10, 3.11, 3.12, 3.13. We use the Docker Debian Python image and extract the binaries out, then we have created a bazel rule package them for distroless. We tag and republish them for mainly NVIDIA usecase. There are a few changes if you poke around, such as rootless. Other language flavors are supported too.

NVIDIA distroless v3 = debian 12. Go = base, the rest remains the same. We plan on adding v4 = debian 13 when it's released. Only amd64 and arm64 is supported and it will remain that way.

@loosebazooka I'm happy to contribute this feature out, but we need an package registry to host the packaged Python zip files.

ding-ma avatar Sep 25 '25 23:09 ding-ma