Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.git
.github
*.md
LICENSE
22 changes: 21 additions & 1 deletion .github/workflows/build-and-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
37 changes: 32 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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 <blackdanieljames@gmail.com>"
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"
72 changes: 64 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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!

Expand Down