Skip to content

feat: Python standalone images from python-build-standalone - #2154

Open
reneleonhardt wants to merge 5 commits into
GoogleContainerTools:mainfrom
reneleonhardt:feat/python-build-standalone
Open

feat: Python standalone images from python-build-standalone#2154
reneleonhardt wants to merge 5 commits into
GoogleContainerTools:mainfrom
reneleonhardt:feat/python-build-standalone

Conversation

@reneleonhardt

@reneleonhardt reneleonhardt commented Aug 10, 2026

Copy link
Copy Markdown

Summary

New python/ package (mirrors nodejs/) building distroless Python images from python-build-standalone (PBS) prebuilt tarballs instead of Debian-packaged python. The image base stays Debian (//cc provides glibc); only the python distribution method changes.

Matrix: 3.13.15 / 3.14.7 x debian13 x amd64 / arm64 / s390x / riscv64 x root/nonroot x normal/debug.

Tags: python3.13-debian13, python3.14-debian13 (+ per-arch and default-distro variants, nodejs publish pattern).

Why PBS

  • One tarball serves all archs; runtime deps are glibc only (full stdlib import walk passes with zero deb packages) — PBS statically links zlib/bz2/sqlite3/OpenSSL/ffi/lzma/ncurses into libpython, so there are no shared objects to ship and no PYTHON_PACKAGES list to maintain (only libc-bin for ldconfig).
  • Decouples Python versions from the Debian release (3.13 and 3.14 on debian13; 3.15 added by the updater when it reaches a stable release).
  • Pinned to PBS release 20260807; shas from the release SHA256SUMS, updated via knife update-python-archives.

Platform exclusions (verified, not oversights)

  • armv7: PBS publishes soft-float gnueabi builds (interpreter /lib/ld-linux.so.3); the distroless armhf base ships /usr/lib/ld-linux-armhf.so.3 - the binary cannot exec (confirmed in Docker). Removed.
  • ppc64le: not published by PBS.
  • s390x / riscv64 loaders verified against the cc bases (/usr/lib/ld64.so.1, /usr/lib/ld-linux-riscv64-lp64d.so.1).

Notes

  • debian12 intentionally absent: new debian12 builds are being deprecated upstream.
  • find_library() works for system libs (c/ssl/z verified): image ships /sbin/ldconfig (libc-bin) + a per-arch ld.so.cache (python3/ parity, maintainer request); regenerated via bazel run //python:update_ldconfig, checked by //python:check_ldconfig_tests (4/4) + a CST test. Statically-linked PBS libs have nothing to find; python -m venv works (ensurepip wheels bundled); crypt moot (removed in 3.13).
  • testdata schema 2.0.0 (nodejs parity); python3/ (deb) untouched.

Verification

  • bazelisk build //python/... green (debian13, 3.13+3.14, 4 archs).
  • 8/8 structure tests via colima docker (arm64, debian13, 3.13+3.14, root+nonroot).
  • amd64 image executed in Docker: 3.14.7 x86_64, ssl certs OK.
  • s390x/riscv64 built; OCI config (arch/os/annotations) verified; runtime in CI.
  • All required cc base permutations verified (arch configs correct).
  • armv7/ppc64le exclusions verified (loader/ABI evidence, see Platform exclusions).
  • find_library('c'/'ssl'/'z') resolves in the image (ldconfig + ld.so.cache); //python:check_ldconfig_tests 4/4 (amd64/arm64/s390x/riscv64); CST includes a find_library test; CDLL(soname) works (libc/libssl/libgcc/libpython).
  • Updater (knife update-python-archives): no-op idempotent; --verbose/--dry-run; fills new stable minors, drops EOL tail, sorts+dedupes the matrix; hermetic smoke test //python:update_python_archives_test; pre-flight validation (RED mutates nothing; recovery via git, lockfile reset documented).
  • buildifier check + lint clean.
  • CI wiring: images build via kind(oci_image, deps(:sign_and_push)); CST tagged manual+arch run via attr(tags, "amd64", ...) — same rails as python3/; check-ldconfig.yaml extended to cover //python:check_ldconfig_tests.

Checklist

  • Google CLA signed (per CONTRIBUTING.md)
  • Signed-off-by on all commits (repo convention)
  • CI green on fork: bazelisk build //python/..., structure tests

reneleonhardt and others added 2 commits August 10, 2026 21:54
Mirrors nodejs/ build; swaps Debian's python package for a
python-build-standalone tarball (image base stays Debian).
Versions 3.13.15, 3.14.7, 3.15.0rc1 x debian12+13 x amd64/arm64/s390x/riscv64(debian13).

Signed-off-by: Rene Leonhardt <65483435+reneleonhardt@users.noreply.github.com>
Co-authored-by: DeepSeek <noreply@deepseek.com>
README covers find_library/venv/compat; test harness renamed to
stdlib_check.py.

Signed-off-by: Rene Leonhardt <65483435+reneleonhardt@users.noreply.github.com>
Co-authored-by: DeepSeek <noreply@deepseek.com>
@google-cla

google-cla Bot commented Aug 10, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@loosebazooka

loosebazooka commented Aug 11, 2026

Copy link
Copy Markdown
Member

At a highlevel:

  1. don't add any new debian12 builds (deprecating those in a month)
  2. there's something in here I don't understand (but maybe it just means we don't need any new debs here)

    runtime deps are glibc only (full stdlib import walk passes with zero deb packages)

  3. images should just be 3.13 and 3.14, the code should be general purpose enough to add 3.15 when it is released
  4. an updater script should be included to find and update new releases of python

- debian12 removed: new debian12 builds are being deprecated upstream;
  python/ is debian13-only now
- 3.15rc1 removed: images are 3.13 and 3.14; matrix is general enough
  that 3.15 is added via config.bzl + python.bzl when stable
- README/provenance/testdata updated; publish map picks up the new
  matrix automatically (root BUILD iterates config)
…on-archives)

Reads the python matrix (minors + archs) from python/config.bzl and the
current versions from //private/extensions:python.bzl, queries the latest
PBS release (latest-release.json, API fallback), rewrites the extension's
archive/version data, and bumps/creates testdata version strings.
Idempotent (no-op when current).

- knife.d/update_python_archives.sh (bash 3.2 compatible, gnu-sed style
  per repo convention) + knife cmd + README provenance + extension
  versions-comment + lockfile synced
- hermetic smoke test //python:update_python_archives_test (no network,
  no fixtures): asserts the committed matrix, extension, and MODULE.bazel
  use_repo agree; the updater keeps its expected-minors line in sync

Policy: the matrix is a contiguous support window anchored at its oldest
minor - the updater fills every published stable minor above it (several
at once), the maintainer drops the tail (EOL), minors are sorted+deduped
internally, rc/a/b and freethreaded tarballs are excluded.

Flags: --verbose (list found latest versions even when unchanged),
--dry-run (print what would change, mutate nothing).

Safety: a broken script must not clean up after itself - all verifiable
failures happen BEFORE any mutation (config anchors checked, generated
extension buildifier-fixed on a temp file, config edits and the
MODULE.bazel use_repo line applied to temp copies and grep-verified, all
only mv'd after verification). RED reports pre-flight (nothing mutated)
vs mid-mutation (partial, git recovery hint incl. MODULE.bazel.lock) via
a MUTATED_PARTIAL marker; GREEN reminds to sync the lockfile (bazel mod
deps --lockfile_mode=update).

A new minor now also updates MODULE.bazel's use_repo(py, ...) line - the
new repos must be visible to the root module (previously the 3.15 image
failed to analyze with 'No repository visible as @python315_arm64').

Verified: real no-op/verbose/dry-run; mocked PBS 3.15.0 (fill-top,
tail-drop, unsorted/dup normalization, patch replace, smoke-test sync,
use_repo update + target analysis, GREEN/RED paths incl. forced
post-mutation failure); smoke test catches matrix/extension/use_repo
drift.
@reneleonhardt

Copy link
Copy Markdown
Author

don't add any new debian12 builds (deprecating those in a month)

Removed. python/ is debian13-only (config.bzl, README, publish map).

"runtime deps are glibc only (full stdlib import walk passes with zero deb packages)" — unclear

Explained above. PBS linux builds statically bundle zlib/bz2/sqlite3/OpenSSL/libffi/lzma/ncurses; the tarball binaries reference only glibc core libs, so the image adds zero python-specific deb packages. The 502-module stdlib import walk (ssl, sqlite3, zlib, ctypes, https) passes in-image with none; deliberate skips (GUI, _pyrepl._minimal_curses, dbm.gnu, windows-only, test) are documented so "zero debs" is exact, and any future missing lib fails the walk.

images should just be 3.13 and 3.14; code general enough to add 3.15 when it is released

Done. Matrix is 3.13 + 3.14; _bin_version + the matrix-driven config make 3.15 a one-entry addition when it reaches stable (no rc in the matrix anymore).

an updater script should be included to find and update new releases of python

Included: knife update-python-archives (knife.d/update_python_archives.sh). Reads the matrix from python/config.bzl, queries the latest PBS release, replaces patch versions + SHAs in place, fills new stable minors (3.15 once 3.15.0 ships), honors EOL tail drops, keeps testdata + MODULE.bazel use_repo in sync. --verbose/--dry-run flags, hermetic smoke test (//python:update_python_archives_test), pre-flight validation (a failed run mutates nothing).

@loosebazooka

Copy link
Copy Markdown
Member

I'm sorry I will get to this soon, lots of other things on my plate.

@reneleonhardt

Copy link
Copy Markdown
Author

No stress, my awesome contribution everyone's been waiting for isn't running away 😄

@loosebazooka

Copy link
Copy Markdown
Member

I'm no python expert, but the ldconfig seemed somewhat important to people. is CDLL sufficient in this case? are people not going to use "find_library()" ?

@loosebazooka

Copy link
Copy Markdown
Member

I had a teammate recommend we keep the ldconfig binary and continue to generate the ldconfig.so.cache to allow find_library to continue to work

Maintainer: 'Keep the ldconfig binary and continue to generate the
ldconfig.so.cache to allow find_library to continue to work'. python3/
parity, ported to python/:

- libc-bin added to the image (provides /sbin/ldconfig) via
  PYTHON_PACKAGES in config.bzl
- per-arch ld.so.cache generated by running ldconfig inside the image
  (python/ldconfig.bzl + ldconfig.sh, ported from python3; bazel run
  //python:update_ldconfig) and shipped at /etc/ld.so.cache
- python/ldconfig.bzl: oci_load + do_load genrules + update_ldconfig +
  check_ldconfig_{arch}_test + test suite (caches for amd64, arm64,
  s390x, riscv64 generated and committed)
- find_library CST test added to testdata/python3.yaml
- check-ldconfig.yaml workflow extended to cover //python too
- README find_library section rewritten (works now; regenerate via
  bazel run //python:update_ldconfig)

Verified: find_library('c'/'ssl'/'z') resolve in the image;
check_ldconfig tests 4/4 pass; CST (incl. find_library) + smoke test
pass.
@reneleonhardt
reneleonhardt force-pushed the feat/python-build-standalone branch from be7c93f to 3c51b57 Compare August 13, 2026 20:37
@reneleonhardt

Copy link
Copy Markdown
Author

Good catch! Now python3/ parity.

  • Image ships /sbin/ldconfig (libc-bin) + a per-arch ld.so.cache (amd64/arm64/s390x/riscv64), generated in-image via bazel run //python:update_ldconfig.
  • //python:check_ldconfig_tests 4/4 (also wired into check-ldconfig.yaml); CST find_library test added.

Verified in the image: find_library('c') -> libc.so.6, find_library('ssl') -> libssl.so.3, find_library('z') -> libz.so.1.

Scope note (deep-audited against the python3 image): PBS statically links zlib/bz2/sqlite3/OpenSSL/ffi/lzma/ncurses INTO libpython, so those modules work with no shared object to find - find_library() covers system libs (c/ssl/z), which is what the cache lists. python -m venv works (ensurepip wheels are bundled); crypt is moot (module removed in 3.13, PEP 594); dbm.gnu/tkinter are absent in both images (parity).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants