Repository navigation
Conversation
potiuk
requested review from
amoghrajesh,
ashb,
bugraoz93,
choo121600,
ephraimbuddy,
gopidesupavan,
jason810496,
jedcunningham,
jscheffl and
vatsrahul1001
as code owners
September 12, 2026 17:26
1 task done
potiuk
force-pushed
the
hardened-python-base-images
branch
2 times, most recently
from
October 4, 2026 23:53
97e9fc1 to
a8356c1
Compare
1 task done
potiuk
force-pushed
the
hardened-python-base-images
branch
from
October 5, 2026 09:00
200581a to
658a6c3
Compare
Contributor
|
Quickest fix: git fetch upstream main && git rebase upstream/main
rm uv.lock && uv lock
git add uv.lock && git rebase --continue
git push --force-with-leaseAutomated nudge — ignore if you're not ready to rebase. This comment is updated in place on future |
Squashed from 19 commits (backup branch: backup/hardened-python-base-images-pre-rebase) before rebasing onto main. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014np1fVAxHmGaBFt3qebJ8x
The 3.4.x line keeps publishing the previous images - Python compiled from sources on bookworm - as a deprecated legacy flavor, so the PROD image has to be buildable both ways while the CI image only needs the hardened trixie one. A weekly canary builds and checks the legacy images so they do not rot between releases. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014np1fVAxHmGaBFt3qebJ8x
potiuk
force-pushed
the
hardened-python-base-images
branch
from
October 10, 2026 10:11
7ba3045 to
b4e7582
Compare
…egacy images like regular ones The Debian 13 hardened images install Python as Debian packages under /usr instead of /opt/python, so the image has to find the base Python's prefix and the system-Python guard must not mistake those packages for Debian's own Python. The legacy canary now runs the regular PROD image tests, not just a smoke check, so the legacy images are covered as well as the default ones. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014np1fVAxHmGaBFt3qebJ8x
The CI image is trixie-only now: libgcc-11-dev was a bookworm-era pin (build-essential brings the matching libgcc-14-dev) and software-properties-common was removed in Debian 13; nothing uses its add-apt-repository. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014np1fVAxHmGaBFt3qebJ8x
Microsoft signs its Debian 13 package repository with its 2025 general signing key rather than the 2015 release key, so apt on trixie rejected the repository as unsigned. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014np1fVAxHmGaBFt3qebJ8x
The Debian 13 hardened images ship libpam-runtime and passwd already installed but without their /etc/pam.d files, so installing them restored nothing and adding the CI image's airflow user failed with pam_start() errors. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014np1fVAxHmGaBFt3qebJ8x
The trixie hardened images add Docker's own package repository, whose rebuilt packages take precedence over Debian's - users extending the image should know where their packages come from, and that no credentials are needed for it. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014np1fVAxHmGaBFt3qebJ8x
On trixie, libenchant-2-dev's build dependencies need python3, which Docker's package repository satisfies with its own python-3.13 - a second interpreter in the 3.12 image that broke building C extensions. pyenchant only needs the runtime library, which it finds through the ldconfig cache, and the guard now fails early on any hardened Python other than the image's own. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014np1fVAxHmGaBFt3qebJ8x
Dropping the packages that only existed to compile Python left the legacy flavor compiling a Python without sqlite3, lzma or readline - CPython builds those modules only when their headers are present, and the image could not even run airflow. The build now also fails when one of them is missing. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014np1fVAxHmGaBFt3qebJ8x
The hardened images ship libc-bin without its dpkg trigger, so libraries installed by apt stayed invisible to ctypes.util.find_library() - the CI image's docs build failed with "The 'enchant' C library was not found". A dpkg post-invoke hook runs ldconfig after every apt run, also in images extending ours. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014np1fVAxHmGaBFt3qebJ8x
Extending a hardened image with apt packages that depend on python3 silently installs another Python next to the image's own, and C extensions then build against the wrong one - users need to know to check before adding such packages. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014np1fVAxHmGaBFt3qebJ8x
Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014np1fVAxHmGaBFt3qebJ8x
OpenSSH 10 in trixie prints a '# host:port' banner comment per connection to ssh-keyscan's stdout, so the line count never matched the three host keys and every test job waited until it timed out. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014np1fVAxHmGaBFt3qebJ8x
The uv cache restored into CI image builds held wheels compiled against bookworm's libraries - an xmlsec wheel built against bookworm's MD5-enabled libxmlsec1 failed to import on trixie, which dropped MD5. The cache key does not encode the base OS, so it has to be bumped when that changes. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014np1fVAxHmGaBFt3qebJ8x
Debian trixie ships no Java 17. The Java SDK e2e image keeps Java 17 for Spark 3.5 by taking it from the Temurin repository, and the docs example uses trixie's Java 21. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014np1fVAxHmGaBFt3qebJ8x
GCC 14 turns incompatible pointer types, integer conversions and implicit declarations into errors, and the hardened Python passes -Werror=implicit-function-declaration on to extensions - the lowest-dependency tests' old sdists (xmlsec 1.3.14, ibm-db 3.2.0) only built because those were warnings. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014np1fVAxHmGaBFt3qebJ8x
Users extending the image with old source distributions hit the same GCC 14 errors as CI, and need to know how to relax them. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014np1fVAxHmGaBFt3qebJ8x
…out test With a 3s timeout only 2s were left for spawning the extraction process next to its 1s on-start stall, which the hardened Python - built without PGO - no longer always met on a loaded runner; 4s still kills the 5s on-complete stall. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014np1fVAxHmGaBFt3qebJ8x
A failing lang-SDK helm upgrade raised past the log export that every other step of the complete K8S test run does, so the cluster was deleted with nothing saved and the job's KinD logs artifact stayed empty. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014np1fVAxHmGaBFt3qebJ8x
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The CI image and the default PROD images now consume a Docker Hardened Image for Python on
Debian 13 "trixie" instead of compiling CPython from source on top of
debian:bookworm-slim. The previousway of building stays available for the PROD image as the deprecated legacy flavor.
Discussion: https://lists.apache.org/thread/y0mlxsx36kzc84tn31q1kyl4rdvgv1xx
Lazy consensus: https://lists.apache.org/thread/h0sxvd5g4drxfrph35dvlrk46zomsk0y
Image flavors
BASE_IMAGEghcr.io/apache/airflow/base/python:<X.Y.Z>-debian13-devdebian:bookworm-slimAIRFLOW_IMAGE_FLAVOR(hardenedby default, orlegacy) selects the flavor in the PRODDockerfile. Thelegacy build stage compiles Python into
/usr/pythonand hands it to the final image through/python-dist, so the hardened image gets no extra layer content. The flavor is recorded in theorg.apache.airflow.image.flavorlabel.breeze prod-image build --image-flavor hardened|legacy --debian-version trixie|bookworm. Thelegacy flavor builds on
debian:<version>-slim.breeze ci-image buildno longer takes--debian-version- the CI image is always hardened and trixie-based.prod-image-build.ymlgets animage-flavorinput and defaultsdebian-versiontotrixie.scheduled-legacy-prod-image-canary.ymlruns weekly (and on dispatch) and tests legacy images likeregular ones. It runs the same selective checks as a scheduled CI build, publishes the constraints from
the constraints branch as the usual
constraints-<python>artifacts (so no CI image is needed), and buildslegacy bookworm PROD images for every supported Python version through the reusable
prod-image-build.yml(includingprod-image verify). It then checks each image's flavor label andDebian codename, and runs the regular PROD image tests:
additional-prod-image-tests.yml(docker-composequick start, Task SDK integration, e2e suites, image extra checks) and the Kubernetes tests. Legacy
images are not built in PRs or canary builds.
Debian trixie
time_ttransition are picked per codename(
libssl3t64,libldap2on trixie;libssl3,libldap-2.5-0on bookworm).mariadb-client-compat, which provides themysqlnamed commands from 11.0 on. Bookworm keeps 10.11.http://dhi.io/deb/debian/main,signed with
/usr/share/keyrings/dhi-deb-main.gpg), whose+dhiNrebuilds take precedence over Debian'spackages. It is accessible anonymously -
apt-get updateneeds no credentials, unlike pulling thedhi.ioimages. The Debian 12 images have no such repository.
python-3.13,libpython-3.13, …)under
/usr, while the Debian 12 ones used/opt/python. The image now takes the base Python's prefixfrom
sys.base_prefix, so/usr/pythonlinks to/usron trixie. The system-Python guard only matchesDebian's own
libpython3.Xpackages, so it no longer fails on the base image'slibpython-3.13. Thatguard failure is what broke the first trixie CI image build.
Base image distribution
Pulling
dhi.iorequires a Docker Hub login. Rather than force credentials on every contributor and CIjob, the tags are mirrored to
ghcr.io/apache/airflow/base/pythonandBASE_IMAGEdefaults there, sobuilding needs no registry credentials at all. The images are Apache-2.0 licensed, so redistribution is
fine.
breeze release-management mirror-base-imagesplus a weekly workflow keep the mirror current; themirror publishes the pinned (
3.13.16-debian13-dev) and floating (3.13-debian13-dev) tags for bothdebian13anddebian12. Thedebian13tags are already mirrored, and their digests are identical todhi.io's.One patch-level pin serves both Debian releases. The upgrade check reads Docker's catalog for both and
only moves the pin to a patch level published for each of them, so a bump never points one of them at a
tag that does not exist. Pins: 3.11.17 / 3.12.15 / 3.13.16 / 3.14.8.
Gaps the hardened base has versus
debian:*-slimThe hardened images ship a deliberately minimal
/etcand toolset. What had to be restored - only for thehardened flavor - lives in
restore_debian_base_filesandlink_python:base-passwdaccountssasl2-bin:install: invalid group 'sasl'base-passwd, runupdate-passwd/etc/shellstmux:add-shellabortslibpam-runtimeadduser --gecos→chfn: PAM: Critical error/etc/pam.d/common-*files the shipped PAM configs@includedashlibgcrypt20postinst (#!/bin/dash) exits 127/usr/local,gzipln: /usr/local/bin/pip3: No such file;tar: gzip: Cannot execmkdir -pinlink_python;gzipin dev depslsb-releaseis dropped entirely -install_mysql/mssql/postgres.shnow read/etc/os-releasethroughcommon::debian_codename/common::debian_release. That package pulled inusrmerge, whose postinstremoved
/lib64and broke the build; it was the blocker in #60123.Differences between the hardened base image and the previous images
Investigating this PR's CI failures turned up behaviour differences in Docker's hardened Python
(definitions: https://github.com/docker-hardened-images/catalog,
package/pythonandimage/python):020-CVE-2026-3479-pkgutil-get-data.patch, sopkgutil.get_data()rejects
..resource paths (upstream CPython only documents this). moto < 5.2.2 loads its EC2 data via../resources/…, which broke the lowest-dependencyProviders[amazon]tests on every Python version.→ moto floor raised to 5.2.2.
-DTHREAD_STACK_SIZE=0x100000(previousimages: the 8 MiB glibc default). On 3.12/3.13 the fixed C-recursion depth then overflows, crashing an
xdist worker on
test_login.py::…[deeply_nested]. → hardened images installairflow-thread-stack-size.pthrestoring 8 MiB thread stacks.--with-ltoonly (previous:--enable-optimizations --with-lto), so CPU-boundPython is slower.
/etc/debian_version. Abase-filesupgrade stopped at dpkg's conffile prompt. → hardenedimages ship
/etc/dpkg/dpkg.cfg.d/airflow-keep-conffiles(force-confdef,force-confold).which. →common.shusescommand -v..pycfor the standard library. → the hardened build compiles it, keeping the Do not remove .pyc and .pyo files after building Python #58944 fix.These are documented in
docker-stack-docs/build.rst("Properties of the hardened base image", plus a new"Debian trixie and the legacy image flavor" section).
PYTHON_LTOis removed: FIPS builds pointBASE_IMAGEat a FIPS variant of the hardened image, and thelegacy flavor always compiles with LTO.
Drive-by fixes
libxmlsec1-openssladded to the runtime deps.libxmlsec1ships no crypto engine of its own, soimport xmlsec(viapython3-saml) failed withlibxmlsec1-openssl.so.1: cannot open shared object file. Pre-existing - reproduced ondebian:bookworm-slimwith the same package set.scripts/ci/prek/upgrade_important_versions.pyreads the supported Python versions fromglobal_constants.pyinstead of a hard-coded list that had left the 3.14 pin stale since March.Verification
test_local_executor.py::…test_actual_worker_death_after_start_releases_slot, was a timing assertionalready loosened on main by Increase worker startup timeout in LocalExecutor process death test #74292.
debian:bookworm-slimanddebian:trixie-slim; the MariaDB 11.8trixie repository resolves the dev and prod client package sets.
scriptsunit tests pass, including new tests for the flavor/Debian base image selection,the per-distro mirror and the patch-level pin across both catalogs.
Follow-ups deliberately not in this PR
-legacytag suffix with an entrypoint deprecation warning - partof the release tooling, to land before 3.4.0.
legacy flavor keeps the sigstore verification of the Python sources.
runtimevariant for the PRODmainstage - it would mean rewriting the entrypoint awayfrom bash and dropping the
ADDITIONAL_RUNTIME_APT_DEPScustomization contract, and it is coupled toHelm Chart: Directly Start Airflow Containers #65384.
closes: #59625
Was generative AI tooling used to co-author this PR?
Generated-by: Claude Code (Opus 5) following the guidelines
🤖 Generated with Claude Code