diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..050fa4c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +.git +.github +*.md +LICENSE diff --git a/.github/workflows/build-and-push.yml b/.github/workflows/build-and-push.yml index 452c80b..f6bd9ae 100644 --- a/.github/workflows/build-and-push.yml +++ b/.github/workflows/build-and-push.yml @@ -11,6 +11,21 @@ jobs: permissions: contents: read packages: write + strategy: + matrix: + include: + - python_version: "3.9" + debian_version: "bullseye" + - python_version: "3.10" + debian_version: "bullseye" + - python_version: "3.11" + debian_version: "bookworm" + - python_version: "3.12" + debian_version: "bookworm" + - python_version: "3.13" + debian_version: "bookworm" + - python_version: "3.14" + debian_version: "bookworm" steps: - name: Checkout repository uses: actions/checkout@v4 @@ -37,6 +52,11 @@ jobs: file: Dockerfile platforms: linux/amd64,linux/arm64 push: true - tags: ghcr.io/${{ github.repository_owner }}/python-container-builder:latest + build-args: | + DEBIAN_VERSION=${{ matrix.debian_version }} + PYTHON_VERSION=${{ matrix.python_version }} + tags: | + ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }} + ${{ matrix.python_version == '3.14' && format('ghcr.io/{0}/python-container-builder:latest', github.repository_owner) || '' }} provenance: false outputs: type=image,name=python-container-builder,annotation-index.org.opencontainers.image.description=build your Python distroless containers with this \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index ae54ac2..4831c45 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,39 @@ # Based on: https://github.com/GoogleContainerTools/distroless/blob/main/examples/python3-requirements/Dockerfile -# Bugfix for apt-get update errors: https://stackoverflow.com/questions/63526272/release-file-is-not-valid-yet-docker # Installing uv: https://docs.astral.sh/uv/guides/integration/docker/#installing-uv -FROM debian:bookworm-slim +ARG PYTHON_VERSION=3.14 +ARG DEBIAN_VERSION=bookworm + +# Extract Python from official image +FROM python:${PYTHON_VERSION}-slim-${DEBIAN_VERSION} AS python-source + +# Final stage - clean Debian base with Python copied in +FROM debian:${DEBIAN_VERSION}-slim +ARG PYTHON_VERSION + LABEL MAINTAINER="jskii " -RUN echo "Acquire::Check-Valid-Until \"false\";\nAcquire::Check-Date \"false\";" | cat > /etc/apt/apt.conf.d/10no--check-valid-until -RUN apt-get update && \ - apt-get install --no-install-suggests --no-install-recommends --yes python3-venv libpython3-dev python3-pip +LABEL python_version="${PYTHON_VERSION}" + +# Install minimal runtime dependencies +# Add any additional packages you need here in the future +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Copy Python installation from official image +COPY --from=python-source /usr/local /usr/local + +# Copy shared libraries needed by Python +COPY --from=python-source /usr/lib /usr/lib +COPY --from=python-source /lib /lib + +# Update library cache +RUN ldconfig + +# Copy uv for fast package installation COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ + +# Create virtual environment using uv (faster than python -m venv) RUN uv venv + ENV VIRTUAL_ENV=/.venv ENV PATH="/bin:$VIRTUAL_ENV/bin:$PATH" \ No newline at end of file diff --git a/README.md b/README.md index eb48e37..dd1cd8a 100644 --- a/README.md +++ b/README.md @@ -3,20 +3,48 @@ A shortcut to packaging your Python code with requirement dependencies into a Distroless image. +## Supported Python Versions + +This project provides pre-built images for multiple Python versions, using a hybrid approach that extracts Python from official images and installs it on a clean, customizable Debian base: + +| Python Version | Image Tag | Python Source | Debian Base | Distroless Runtime | +|----------------|-----------|---------------|-------------|-------------------| +| 3.9 | `:3.9` | `python:3.9-slim-bullseye` | `debian:bullseye-slim` | `gcr.io/distroless/python3-debian11` | +| 3.10 | `:3.10` | `python:3.10-slim-bullseye` | `debian:bullseye-slim` | `gcr.io/distroless/python3-debian11` | +| 3.11 | `:3.11` | `python:3.11-slim-bookworm` | `debian:bookworm-slim` | `gcr.io/distroless/python3-debian12` | +| 3.12 | `:3.12` | `python:3.12-slim-bookworm` | `debian:bookworm-slim` | `gcr.io/distroless/python3-debian12` | +| 3.13 | `:3.13` | `python:3.13-slim-bookworm` | `debian:bookworm-slim` | `gcr.io/distroless/python3-debian12` | +| 3.14 | `:3.14` or `:latest` | `python:3.14-slim-bookworm` | `debian:bookworm-slim` | `gcr.io/distroless/python3-debian12` | + +All images support both `linux/amd64` and `linux/arm64` architectures. + +### Build Approach + +These images use a **hybrid multi-stage build**: +1. Extract Python from official `python:X.Y-slim-debian` images (ensures reliability and latest patches) +2. Copy Python into a clean `debian:X-slim` base (full control over dependencies) +3. Add `uv` for fast package installation +4. Pre-create a virtual environment at `/.venv` + +This approach gives you the reliability of official Python builds while maintaining full control over the base system and dependencies. + ## Goals This project seeks to: - Simplify the build/packaging process for simple Python projects. - Reduce usage of Github Actions free tier minutes for said projects by doing the most time-intensive part up front here instead. -- Offer an base image that supports packaging into a final distroless runtime. -- Keep up to date with package updates to the Debian-based build image without having to think about it. -- Support both `linux/arm64` and `linux/amd64` build options. +- Offer a base image that supports packaging into a final distroless runtime. +- Provide a clean, customizable Debian base with reliable Python builds. +- Keep up to date with package updates automatically via nightly builds. +- Support both `linux/arm64` and `linux/amd64` architectures. - Be publicly available for use without needing to login to a registry. -- Include `uv` as I like it in my Python toolchain. +- Include `uv` for fast, modern Python package management. ## Getting Started ### Quickstart Example > I have a standalone Python file, `main.py`, in the root folder of my repo requiring dependencies that are declared in `requirements.txt`. -``` + +**Using Python 3.14 (latest):** +```dockerfile FROM ghcr.io/jski/python-container-builder:latest as build-venv COPY requirements.txt /requirements.txt RUN uv pip install -r /requirements.txt @@ -28,10 +56,38 @@ WORKDIR /app ENTRYPOINT ["/.venv/bin/python3", "-u", "main.py"] ``` +**Using Python 3.12 (stable):** +```dockerfile +FROM ghcr.io/jski/python-container-builder:3.12 as build-venv +COPY requirements.txt /requirements.txt +RUN uv pip install -r /requirements.txt + +FROM gcr.io/distroless/python3-debian12 +COPY --from=build-venv /.venv /.venv +COPY /main.py /app/main.py +WORKDIR /app +ENTRYPOINT ["/.venv/bin/python3", "-u", "main.py"] +``` + +**Using Python 3.10 (for older projects):** +```dockerfile +FROM ghcr.io/jski/python-container-builder:3.10 as build-venv +COPY requirements.txt /requirements.txt +RUN uv pip install -r /requirements.txt + +FROM gcr.io/distroless/python3-debian11 +COPY --from=build-venv /.venv /.venv +COPY /main.py /app/main.py +WORKDIR /app +ENTRYPOINT ["/.venv/bin/python3", "-u", "main.py"] +``` + +> **Note**: When using Python 3.9 or 3.10, make sure to use `gcr.io/distroless/python3-debian11` as your runtime image. For Python 3.11 and above, use `gcr.io/distroless/python3-debian12`. + ### Usage/Explanation -1. Declare the base image as the top FROM line in your Dockerfile. -2. Copy your requirements or configuration files from your application repo, and run pip install from a virtualenv. -3. Declare the final distroless container image you'll use for runtime. +1. Choose your Python version and declare the corresponding base image as the top FROM line in your Dockerfile (e.g., `:3.12`, `:3.14`, or `:latest`). +2. Copy your requirements or configuration files from your application repo, and run `uv pip install` (or `pip install`) from a virtualenv. +3. Declare the final distroless container image you'll use for runtime, matching the Debian version to your Python version (see table above). 4. Copy the virtualenv you built in the first phase of the build. 5. Move your application files to the proper location on the filesystem, and setup your workdir and entrypoint. All done!