From 4b57a562d30b979d1598e2b3f9cbfe5242e4eea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Rom=C3=A1n?= Date: Wed, 8 Oct 2025 15:08:07 -0700 Subject: [PATCH] feat: add Python 3.14 use MAJOR.MINOR.PATCH and MAJOR.MINOR for tagging 3.13 images --- .github/workflows/autoupdate.yml | 28 ++++- .github/workflows/publish-3.13.yml | 2 - .github/workflows/publish-3.14.yml | 68 ++++++++++++ 3.14/bookworm/Dockerfile | 118 +++++++++++++++++++++ 3.14/slim-bookworm/Dockerfile | 162 +++++++++++++++++++++++++++++ README.md | 12 ++- requirements/3.14/pip.in | 1 + requirements/3.14/pip.txt | 4 + 8 files changed, 385 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/publish-3.14.yml create mode 100644 3.14/bookworm/Dockerfile create mode 100644 3.14/slim-bookworm/Dockerfile create mode 100644 requirements/3.14/pip.in create mode 100644 requirements/3.14/pip.txt diff --git a/.github/workflows/autoupdate.yml b/.github/workflows/autoupdate.yml index 456032e..3d09f25 100644 --- a/.github/workflows/autoupdate.yml +++ b/.github/workflows/autoupdate.yml @@ -35,31 +35,51 @@ jobs: sed -i "s/ENV PYTHON_VERSION=.*/ENV PYTHON_VERSION=$python_version/" 3.13/slim-bookworm/Dockerfile sed -i "s/3\.13\.[0-9]\+\([-a-z]*\)/$python_version\1/g" README.md - - name: Update requirements + - name: Fetch latest Python 3.14 release + if: ${{ github.event.schedule == '0 18 10 2-12/2 *' || github.event_name == 'workflow_dispatch' }} + run: | + python_version=$(curl -s https://www.python.org/ftp/python/ | grep -oP '3\.14\.\d+/' | uniq | sort -V | tail -n 1 | tr -d '/') + + echo "python3.14: $python_version" + + sed -i "s/ENV PYTHON_VERSION=.*/ENV PYTHON_VERSION=$python_version/" 3.14/bookworm/Dockerfile + sed -i "s/ENV PYTHON_VERSION=.*/ENV PYTHON_VERSION=$python_version/" 3.14/slim-bookworm/Dockerfile + sed -i "s/3\.14\.[0-9]\+\([-a-z]*\)/$python_version\1/g" README.md + + - name: Update 3.12 requirements uses: coatl-dev/actions/uv-pip-compile-upgrade@v5 with: path: requirements/3.12 python-version: '3.12' - - name: Update requirements + - name: Update 3.13 requirements uses: coatl-dev/actions/uv-pip-compile-upgrade@v5 with: path: requirements/3.13 python-version: '3.13' + - name: Update 3.14 requirements + uses: coatl-dev/actions/uv-pip-compile-upgrade@v5 + with: + path: requirements/3.14 + python-version: '3.14' + - name: Extract package versions and update Dockerfile run: | pip312=$(grep '^pip==' requirements/3.12/pip.txt | cut -d'=' -f3) pip313=$(grep '^pip==' requirements/3.13/pip.txt | cut -d'=' -f3) + pip314=$(grep '^pip==' requirements/3.14/pip.txt | cut -d'=' -f3) echo "Extracted versions:" echo "pip3.12: $pip312" - echo "pip3.13: $pip313" - sed -i "s/ENV PYTHON_PIP_VERSION=.*/ENV PYTHON_PIP_VERSION=$pip312/" 3.12/bookworm/Dockerfile sed -i "s/ENV PYTHON_PIP_VERSION=.*/ENV PYTHON_PIP_VERSION=$pip312/" 3.12/slim-bookworm/Dockerfile + echo "pip3.13: $pip313" sed -i "s/ENV PYTHON_PIP_VERSION=.*/ENV PYTHON_PIP_VERSION=$pip313/" 3.13/bookworm/Dockerfile sed -i "s/ENV PYTHON_PIP_VERSION=.*/ENV PYTHON_PIP_VERSION=$pip313/" 3.13/slim-bookworm/Dockerfile + echo "pip3.14: $pip314" + sed -i "s/ENV PYTHON_PIP_VERSION=.*/ENV PYTHON_PIP_VERSION=$pip314/" 3.14/bookworm/Dockerfile + sed -i "s/ENV PYTHON_PIP_VERSION=.*/ENV PYTHON_PIP_VERSION=$pip314/" 3.14/slim-bookworm/Dockerfile - name: Detect changes id: git-diff diff --git a/.github/workflows/publish-3.13.yml b/.github/workflows/publish-3.13.yml index da95912..4fd724b 100644 --- a/.github/workflows/publish-3.13.yml +++ b/.github/workflows/publish-3.13.yml @@ -49,13 +49,11 @@ jobs: - version: '3.13' variant: bookworm tags: | - ${{ needs.tagger.outputs.major }} ${{ needs.tagger.outputs.major-minor }} ${{ needs.tagger.outputs.version }} - version: '3.13' variant: slim-bookworm tags: | - ${{ needs.tagger.outputs.major }}-slim ${{ needs.tagger.outputs.major-minor }}-slim ${{ needs.tagger.outputs.version }}-slim with: diff --git a/.github/workflows/publish-3.14.yml b/.github/workflows/publish-3.14.yml new file mode 100644 index 0000000..5afa9db --- /dev/null +++ b/.github/workflows/publish-3.14.yml @@ -0,0 +1,68 @@ +name: publish-3.14 + +on: + push: + branches: + - coatl + paths: + - .github/workflows/publish.yml + - .github/workflows/publish-3.14.yml + - 3.14/** + schedule: + - cron: '0 8 17 * *' + +jobs: + tagger: + runs-on: ubuntu-latest + outputs: + major: ${{ steps.tags.outputs.major }} + major-minor: ${{ steps.tags.outputs.major-minor }} + version: ${{ steps.tags.outputs.version }} + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Extract version number + id: tags + run: | + # Extract the Python version from the Dockerfile + VERSION=$(grep 'ENV PYTHON_VERSION' 3.14/bookworm/Dockerfile | cut -d '=' -f 2) + echo "VERSION=${VERSION}" + # Trim the version to the first two segments (major.minor) + MAJOR_MINOR=$(echo $VERSION | cut -d'.' -f1-2) + echo "MAJOR_MINOR=${MAJOR_MINOR}" + # Trim the version to the first segment (major) + MAJOR=$(echo $VERSION | cut -d'.' -f1-1) + echo "MAJOR=${MAJOR}" + # Export the extracted version to GITHUB_OUTPUT + echo "major=${MAJOR}" >> $GITHUB_OUTPUT + echo "major-minor=${MAJOR_MINOR}" >> $GITHUB_OUTPUT + echo "version=${VERSION}" >> $GITHUB_OUTPUT + + publish: + needs: tagger + uses: ./.github/workflows/publish.yml + strategy: + matrix: + image: + - version: '3.14' + variant: bookworm + tags: | + ${{ needs.tagger.outputs.major }} + ${{ needs.tagger.outputs.major-minor }} + ${{ needs.tagger.outputs.version }} + - version: '3.14' + variant: slim-bookworm + tags: | + ${{ needs.tagger.outputs.major }}-slim + ${{ needs.tagger.outputs.major-minor }}-slim + ${{ needs.tagger.outputs.version }}-slim + with: + image: coatldev/python + version: ${{ matrix.image.version }} + variant: ${{ matrix.image.variant }} + tags: ${{ matrix.image.tags }} + username: ${{ vars.DOCKERHUB_USERNAME }} + secrets: + password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/3.14/bookworm/Dockerfile b/3.14/bookworm/Dockerfile new file mode 100644 index 0000000..a9e206f --- /dev/null +++ b/3.14/bookworm/Dockerfile @@ -0,0 +1,118 @@ +FROM buildpack-deps:bookworm + +LABEL \ + maintainer="César Román " \ + repository="https://github.com/coatl-dev/docker-python" \ + vendor="coatl.dev" + +ENV DEBIAN_FRONTEND=noninteractive + +ENV PIP_NO_CACHE_DIR=1 +ENV PIP_ROOT_USER_ACTION=ignore + +# ensure local python is preferred over distribution python +ENV PATH=/usr/local/bin:$PATH + +# runtime dependencies +RUN set -eux; \ + apt-get update; \ + apt-get upgrade --yes; \ + apt-get install -y --no-install-recommends \ + libbluetooth-dev \ + tk-dev \ + uuid-dev \ + ; \ + rm -rf /var/lib/apt/lists/* + +ENV PYTHON_VERSION=3.14.0 + +RUN set -eux; \ + \ + wget -q -O python.tar.xz "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz"; \ + mkdir -p /usr/src/python; \ + tar --extract --directory /usr/src/python --strip-components=1 --file python.tar.xz; \ + rm python.tar.xz; \ + \ + cd /usr/src/python; \ + gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \ + ./configure \ + --build="$gnuArch" \ + --enable-loadable-sqlite-extensions \ + --enable-optimizations \ + --enable-option-checking=fatal \ + --enable-shared \ + --with-lto \ + --without-ensurepip \ + ; \ + nproc="$(nproc)"; \ + EXTRA_CFLAGS="$(dpkg-buildflags --get CFLAGS)"; \ + LDFLAGS="$(dpkg-buildflags --get LDFLAGS)"; \ + EXTRA_CFLAGS="${EXTRA_CFLAGS:-} -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer"; \ + make -j "$nproc" \ + "EXTRA_CFLAGS=${EXTRA_CFLAGS:-}" \ + "LDFLAGS=${LDFLAGS:-}" \ + ; \ +# https://github.com/docker-library/python/issues/784 +# prevent accidental usage of a system installed libpython of the same version + rm python; \ + make -j "$nproc" \ + "EXTRA_CFLAGS=${EXTRA_CFLAGS:-}" \ + "LDFLAGS=${LDFLAGS:--Wl},-rpath='\$\$ORIGIN/../lib'" \ + python \ + ; \ + make install; \ + \ +# enable GDB to load debugging data: https://github.com/docker-library/python/pull/701 + bin="$(readlink -ve /usr/local/bin/python3)"; \ + dir="$(dirname "$bin")"; \ + mkdir -p "/usr/share/gdb/auto-load/$dir"; \ + cp -vL Tools/gdb/libpython.py "/usr/share/gdb/auto-load/$bin-gdb.py"; \ + \ + cd /; \ + rm -rf /usr/src/python; \ + \ + find /usr/local -depth \ + \( \ + \( -type d -a \( -name test -o -name tests -o -name idle_test \) \) \ + -o \( -type f -a \( -name '*.pyc' -o -name '*.pyo' -o -name 'libpython*.a' \) \) \ + \) -exec rm -rf '{}' + \ + ; \ + \ + ldconfig; \ + \ + export PYTHONDONTWRITEBYTECODE=1; \ + python3 --version + +# make some useful symlinks that are expected to exist ("/usr/local/bin/python" and friends) +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +RUN set -eux; \ + for src in idle3 pydoc3 python3 python3-config; do \ + dst="$(echo "$src" | tr -d 3)"; \ + [ -s "/usr/local/bin/$src" ]; \ + [ ! -e "/usr/local/bin/$dst" ]; \ + ln -svT "$src" "/usr/local/bin/$dst"; \ + done + +# if this is called "PIP_VERSION", pip explodes with "ValueError: invalid truth value ''" +ENV PYTHON_PIP_VERSION=25.2 +# https://github.com/pypa/get-pip +ENV PYTHON_GET_PIP_URL=https://raw.githubusercontent.com/pypa/get-pip/HEAD/public/get-pip.py + +RUN set -eux; \ + \ + wget -q -O get-pip.py "$PYTHON_GET_PIP_URL"; \ + \ + export PYTHONDONTWRITEBYTECODE=1; \ + \ + python get-pip.py \ + --disable-pip-version-check \ + --no-compile \ + "pip==$PYTHON_PIP_VERSION" \ + --no-setuptools \ + --no-wheel \ + ; \ + rm -f get-pip.py; \ + \ + pip --version + +CMD ["python3"] \ No newline at end of file diff --git a/3.14/slim-bookworm/Dockerfile b/3.14/slim-bookworm/Dockerfile new file mode 100644 index 0000000..0ec1102 --- /dev/null +++ b/3.14/slim-bookworm/Dockerfile @@ -0,0 +1,162 @@ +FROM debian:bookworm-slim + +LABEL \ + maintainer="César Román " \ + repository="https://github.com/coatl-dev/docker-python" \ + vendor="coatl.dev" + +ENV DEBIAN_FRONTEND=noninteractive + +ENV PIP_NO_CACHE_DIR=1 +ENV PIP_ROOT_USER_ACTION=ignore + +# ensure local python is preferred over distribution python +ENV PATH=/usr/local/bin:$PATH + +# runtime dependencies +RUN set -eux; \ + apt-get update; \ + apt-get upgrade --yes; \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + netbase \ + tzdata \ + ; \ + rm -rf /var/lib/apt/lists/* + +ENV PYTHON_VERSION=3.13.6 + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +RUN set -eux; \ + \ + savedAptMark="$(apt-mark showmanual)"; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + dpkg-dev \ + gcc \ + gnupg \ + libbluetooth-dev \ + libbz2-dev \ + libc6-dev \ + libdb-dev \ + libexpat1-dev \ + libffi-dev \ + libgdbm-dev \ + liblzma-dev \ + libncursesw5-dev \ + libreadline-dev \ + libsqlite3-dev \ + libssl-dev \ + make \ + tk-dev \ + uuid-dev \ + wget \ + xz-utils \ + zlib1g-dev \ + ; \ + \ + wget -q -O python.tar.xz "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz"; \ + mkdir -p /usr/src/python; \ + tar --extract --directory /usr/src/python --strip-components=1 --file python.tar.xz; \ + rm python.tar.xz; \ + \ + cd /usr/src/python; \ + gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \ + ./configure \ + --build="$gnuArch" \ + --enable-loadable-sqlite-extensions \ + --enable-optimizations \ + --enable-option-checking=fatal \ + --enable-shared \ + --with-lto \ + --without-ensurepip \ + ; \ + nproc="$(nproc)"; \ + EXTRA_CFLAGS="$(dpkg-buildflags --get CFLAGS)"; \ + LDFLAGS="$(dpkg-buildflags --get LDFLAGS)"; \ + LDFLAGS="${LDFLAGS:--Wl},--strip-all"; \ + EXTRA_CFLAGS="${EXTRA_CFLAGS:-} -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer"; \ + make -j "$nproc" \ + "EXTRA_CFLAGS=${EXTRA_CFLAGS:-}" \ + "LDFLAGS=${LDFLAGS:-}" \ + ; \ +# https://github.com/docker-library/python/issues/784 +# prevent accidental usage of a system installed libpython of the same version + rm python; \ + make -j "$nproc" \ + "EXTRA_CFLAGS=${EXTRA_CFLAGS:-}" \ + "LDFLAGS=${LDFLAGS:--Wl},-rpath='\$\$ORIGIN/../lib'" \ + python \ + ; \ + make install; \ + \ + cd /; \ + rm -rf /usr/src/python; \ + \ + find /usr/local -depth \ + \( \ + \( -type d -a \( -name test -o -name tests -o -name idle_test \) \) \ + -o \( -type f -a \( -name '*.pyc' -o -name '*.pyo' -o -name 'libpython*.a' \) \) \ + \) -exec rm -rf '{}' + \ + ; \ + \ + ldconfig; \ + \ + apt-mark auto '.*' > /dev/null; \ + apt-mark manual $savedAptMark; \ + find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec ldd '{}' ';' \ + | awk '/=>/ { so = $(NF-1); if (index(so, "/usr/local/") == 1) { next }; gsub("^/(usr/)?", "", so); printf "*%s\n", so }' \ + | sort -u \ + | xargs -r dpkg-query --search \ +# https://manpages.debian.org/bookworm/dpkg/dpkg-query.1.en.html#S (we ignore diversions and it'll be really unusual for more than one package to provide any given .so file) + | cut -d: -f1 \ + | sort -u \ + | xargs -r apt-mark manual \ + ; \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ + rm -rf /var/lib/apt/lists/*; \ + \ + export PYTHONDONTWRITEBYTECODE=1; \ + python3 --version + +# make some useful symlinks that are expected to exist ("/usr/local/bin/python" and friends) +RUN set -eux; \ + for src in idle3 pydoc3 python3 python3-config; do \ + dst="$(echo "$src" | tr -d 3)"; \ + [ -s "/usr/local/bin/$src" ]; \ + [ ! -e "/usr/local/bin/$dst" ]; \ + ln -svT "$src" "/usr/local/bin/$dst"; \ + done + +# if this is called "PIP_VERSION", pip explodes with "ValueError: invalid truth value ''" +ENV PYTHON_PIP_VERSION=25.2 +# https://github.com/pypa/get-pip +ENV PYTHON_GET_PIP_URL=https://raw.githubusercontent.com/pypa/get-pip/HEAD/public/get-pip.py + +RUN set -eux; \ + \ + savedAptMark="$(apt-mark showmanual)"; \ + apt-get update; \ + apt-get install -y --no-install-recommends wget; \ + \ + wget -q -O get-pip.py "$PYTHON_GET_PIP_URL"; \ + \ + apt-mark auto '.*' > /dev/null; \ + [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark > /dev/null; \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ + rm -rf /var/lib/apt/lists/*; \ + \ + export PYTHONDONTWRITEBYTECODE=1; \ + \ + python get-pip.py \ + --disable-pip-version-check \ + --no-compile \ + "pip==$PYTHON_PIP_VERSION" \ + --no-setuptools \ + --no-wheel \ + ; \ + rm -f get-pip.py; \ + \ + pip --version + +CMD ["python3"] \ No newline at end of file diff --git a/README.md b/README.md index 37d4a6e..eaab081 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,10 @@ ## Supported tags and respective `Dockerfile` links -- [`3`, `3.13`, `3.13.6`] -- [`3-slim`, `3.13-slim`, `3.13.6-slim`] +- [`3`, `3.14`, `3.14.0`] +- [`3-slim`, `3.14-slim`, `3.14.0-slim`] +- [`3.13`, `3.13.6`] +- [`3.13-slim`, `3.13.6-slim`] - [`3.12`, `3.12.11`] - [`3.12-slim`, `3.12.11-slim`] - [`2`, `2.7`, `2.7.18`] @@ -125,5 +127,7 @@ compile extension modules written in other languages. Possible solutions if a [`2-slim`, `2.7-slim`, `2.7.18-slim`]: https://github.com/coatl-dev/docker-python/blob/coatl/2.7/slim-bookworm/Dockerfile [`3.12`, `3.12.11`]: https://github.com/coatl-dev/docker-python/blob/coatl/3.12/bookworm/Dockerfile [`3.12-slim`, `3.12.11-slim`]: https://github.com/coatl-dev/docker-python/blob/coatl/3.12/slim-bookworm/Dockerfile -[`3`, `3.13`, `3.13.6`]: https://github.com/coatl-dev/docker-python/blob/coatl/3.13/bookworm/Dockerfile -[`3-slim`, `3.13-slim`, `3.13.6-slim`]: https://github.com/coatl-dev/docker-python/blob/coatl/3.13/slim-bookworm/Dockerfile +[`3.13`, `3.13.6`]: https://github.com/coatl-dev/docker-python/blob/coatl/3.13/bookworm/Dockerfile +[`3.13-slim`, `3.13.6-slim`]: https://github.com/coatl-dev/docker-python/blob/coatl/3.13/slim-bookworm/Dockerfile +[`3`, `3.14`, `3.14.0`]: https://github.com/coatl-dev/docker-python/blob/coatl/3.13/bookworm/Dockerfile +[`3-slim`, `3.14-slim`, `3.14.0-slim`]: https://github.com/coatl-dev/docker-python/blob/coatl/3.13/slim-bookworm/Dockerfile diff --git a/requirements/3.14/pip.in b/requirements/3.14/pip.in new file mode 100644 index 0000000..4d6aa0d --- /dev/null +++ b/requirements/3.14/pip.in @@ -0,0 +1 @@ +pip \ No newline at end of file diff --git a/requirements/3.14/pip.txt b/requirements/3.14/pip.txt new file mode 100644 index 0000000..c9e6003 --- /dev/null +++ b/requirements/3.14/pip.txt @@ -0,0 +1,4 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile --output-file=pip.txt pip.in +pip==25.2 + # via -r pip.in