From 2f5b7b76ef694514c121bad377656a1b4d3ac2f3 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Wed, 3 Dec 2025 20:40:56 -0800 Subject: [PATCH 01/53] feat: add chroot-exec, the missing function --- Dockerfile.alpine | 1 + chroot-exec.sh | 63 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100755 chroot-exec.sh diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 1b86131..af9395f 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -24,6 +24,7 @@ ADD --chmod=0755 chroot-apk.sh /usr/local/bin/chroot-apk ADD --chmod=0755 chroot-pip.sh /usr/local/bin/chroot-pip ADD --chmod=0755 chroot-ln.sh /usr/local/bin/chroot-ln ADD --chmod=0755 remove-py-if-pyc-exists.sh /usr/local/bin/remove-py-if-pyc-exists +ADD --chmod=0755 chroot-exec.sh /usr/local/bin/chroot-exec RUN set -eu ; \ python -m pip install -U pip setuptools ; \ # Add to buildroot: diff --git a/chroot-exec.sh b/chroot-exec.sh new file mode 100755 index 0000000..084a2dd --- /dev/null +++ b/chroot-exec.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env sh + +if [ "x$CACHE_ROOT" = 'x' ] || [ "x$BUILD_ROOT" = 'x' ]; then + >&2 echo "CACHE_ROOT (${CACHE_ROOT:-not-set}) and/or BUILD_ROOT (${BUILD_ROOT:-not-set}) is not set!" + exit 1 +fi + +DEBUG="${CHROOT_EXEC_DEBUG:-0}" + +set -e +set -o pipefail + +setup () { + local extra= + if [ "$DEBUG" = '1' ]; then + >&2 echo "Grafting $CACHE_ROOT into $BUILD_ROOT..." + extra='-v' + fi + tar -C "$CACHE_ROOT" -cpf - . | eval tar -C "$BUILD_ROOT" $extra -xpf - + return $? +} + +fini () { + local rc=$? + local extra= + if [ "$DEBUG" = '1' ]; then + >&2 echo "Removing APK data from $BUILD_ROOT, storing in $CACHE_ROOT" + extra=-v + fi + tar -C "$BUILD_ROOT" -cpf - etc/apk bin/ln bin/busybox var/cache/apk usr/share/apk | eval tar -C "$CACHE_ROOT" $extra -xpf - + $_chroot /bin/ln -sf /usr/bin/dash /bin/sh.bak + rm -rf $BUILD_ROOT/bin/ln $BUILD_ROOT/bin/busybox $BUILD_ROOT/etc/apk $BUILD_ROOT/var/cache/apk $BUILD_ROOT/usr/share/apk + if $_chroot /usr/bin/dash -c '[ ! -x /bin/sh ]'; then + >&2 echo '/bin/sh in chroot failed the vibe check, replacing with a symlink to /usr/bin/dash!' + mv $BUILD_ROOT/bin/sh.bak $BUILD_ROOT/bin/sh + else + if [ "$DEBUG" = '1' ]; then + >&2 echo '/bin/sh passed the vibe check' + fi + rm $BUILD_ROOT/bin/sh.bak + fi + exit $rc +} + +trap fini EXIT + +while [ "x$1" = x/usr/bin/env -o "x$1" = xenv ]; do + shift +done + +if [ "x$@" = x -o x"$@" = 'x-h' -o x"$@" = 'x--help' -o x"$@" = 'x-?' ]; then + >&2 echo 'chroot-exec [NAME=VALUE]... [COMMAND [ARG]...] + +let NAME=VALUE denote environment variables + +All file accesses in this command are relative to the '"$BUILD_ROOT"' +' + exit 1 +fi + +setup + +chroot $BUILD_ROOT /usr/bin/env $@ From f7baf50c892cc260a75806e18e9d21a4c1138b5f Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Wed, 3 Dec 2025 20:41:13 -0800 Subject: [PATCH 02/53] feat: silence a lot of debugging info --- chroot-apk.sh | 28 ++++++++++++++++++++-------- chroot-ln.sh | 28 ++++++++++++++++++++-------- chroot-pip.sh | 29 +++++++++++++++++++---------- remove-py-if-pyc-exists.sh | 13 +++++++++++-- 4 files changed, 70 insertions(+), 28 deletions(-) diff --git a/chroot-apk.sh b/chroot-apk.sh index 056177f..abbc1e8 100755 --- a/chroot-apk.sh +++ b/chroot-apk.sh @@ -5,31 +5,43 @@ if [ "x$CACHE_ROOT" = 'x' ] || [ "x$BUILD_ROOT" = 'x' ]; then exit 1 fi +DEBUG="${CHROOT_APK_DEBUG:-0}" + set -e set -o pipefail setup () { - >&2 echo "Grafting $CACHE_ROOT into $BUILD_ROOT..." - tar -C "$CACHE_ROOT" -cpf - . | tar -C "$BUILD_ROOT" -xpf - + local extra='' + if [ "$DEBUG" = '1' ]; then + extra='-v' + >&2 echo "Grafting $CACHE_ROOT into $BUILD_ROOT..." + fi + tar -C "$CACHE_ROOT" -cpf - . | eval tar -C "$BUILD_ROOT" -xpf $extra - return $? } fini () { - >&2 echo "Removing APK data from $BUILD_ROOT, storing in $CACHE_ROOT" - tar -C "$BUILD_ROOT" -cpf - etc/apk bin/ln bin/busybox var/cache/apk usr/share/apk | tar -C "$CACHE_ROOT" -xpf - + local rc=$? + local extra='' + if [ "$DEBUG" = '1' ]; then + >&2 echo "Removing APK data from $BUILD_ROOT, storing in $CACHE_ROOT" + extra='-v' + fi + tar -C "$BUILD_ROOT" -cpf - etc/apk bin/ln bin/busybox var/cache/apk usr/share/apk | eval tar -C "$CACHE_ROOT" -xpf $extra - $_chroot /bin/ln -sf /usr/bin/dash /bin/sh.bak rm -rf $BUILD_ROOT/bin/ln $BUILD_ROOT/bin/busybox $BUILD_ROOT/etc/apk $BUILD_ROOT/var/cache/apk $BUILD_ROOT/usr/share/apk if $_chroot /usr/bin/dash -c '[ ! -x /bin/sh ]'; then >&2 echo '/bin/sh in chroot failed the vibe check, replacing with a symlink to /usr/bin/dash!' mv $BUILD_ROOT/bin/sh.bak $BUILD_ROOT/bin/sh else - >&2 echo '/bin/sh passed the vibe check' + if [ "$DEBUG" = '1' ]; then + >&2 echo '/bin/sh passed the vibe check' + fi rm $BUILD_ROOT/bin/sh.bak fi - return $? + exit $rc } trap fini EXIT setup -set -x -apk --root "$BUILD_ROOT" $@ \ No newline at end of file +apk --root "$BUILD_ROOT" $@ diff --git a/chroot-ln.sh b/chroot-ln.sh index 8ab8fa8..bc5ca17 100644 --- a/chroot-ln.sh +++ b/chroot-ln.sh @@ -5,31 +5,43 @@ if [ "x$CACHE_ROOT" = 'x' ] || [ "x$BUILD_ROOT" = 'x' ]; then exit 1 fi +DEBUG="${CHROOT_LN_DEBUG:-0}" + set -e set -o pipefail setup () { - >&2 echo "Grafting $CACHE_ROOT into $BUILD_ROOT..." - tar -C "$CACHE_ROOT" -cpf - . | tar -C "$BUILD_ROOT" -xpf - + local extra= + if [ "$DEBUG" = '1' ]; then + >&2 echo "Grafting $CACHE_ROOT into $BUILD_ROOT..." + extra='-v' + fi + tar -C "$CACHE_ROOT" -cpf - . | eval tar -C "$BUILD_ROOT" $extra -xpf - return $? } fini () { - >&2 echo "Removing APK data from $BUILD_ROOT, storing in $CACHE_ROOT" - tar -C "$BUILD_ROOT" -cpf - etc/apk bin/ln bin/busybox var/cache/apk usr/share/apk | tar -C "$CACHE_ROOT" -xpf - + local rc=$? + local extra= + if [ "$DEBUG" = '1' ]; then + >&2 echo "Removing APK data from $BUILD_ROOT, storing in $CACHE_ROOT" + extra=-v + fi + tar -C "$BUILD_ROOT" -cpf - etc/apk bin/ln bin/busybox var/cache/apk usr/share/apk | eval tar -C "$CACHE_ROOT" $extra -xpf - $_chroot /bin/ln -sf /usr/bin/dash /bin/sh.bak rm -rf $BUILD_ROOT/bin/ln $BUILD_ROOT/bin/busybox $BUILD_ROOT/etc/apk $BUILD_ROOT/var/cache/apk $BUILD_ROOT/usr/share/apk if $_chroot /usr/bin/dash -c '[ ! -x /bin/sh ]'; then >&2 echo '/bin/sh in chroot failed the vibe check, replacing with a symlink to /usr/bin/dash!' mv $BUILD_ROOT/bin/sh.bak $BUILD_ROOT/bin/sh else - >&2 echo '/bin/sh passed the vibe check' + if [ "$DEBUG" = '1' ]; then + >&2 echo '/bin/sh passed the vibe check' + fi rm $BUILD_ROOT/bin/sh.bak fi - return $? + exit $rc } trap fini EXIT setup -set -x -chroot $BUILD_ROOT /usr/bin/env ln $@ \ No newline at end of file +chroot $BUILD_ROOT /usr/bin/env ln $@ diff --git a/chroot-pip.sh b/chroot-pip.sh index f5c3d20..8499326 100644 --- a/chroot-pip.sh +++ b/chroot-pip.sh @@ -5,6 +5,8 @@ if [ "x$CACHE_ROOT" = 'x' ] || [ "x$BUILD_ROOT" = 'x' ]; then exit 1 fi +DEBUG="${CHROOT_PIP_DEBUG:-0}" + set -e set -o pipefail @@ -20,27 +22,39 @@ setup () { } fini () { + local rc=$? + local extra=-q + if [ "$DEBUG" = 1 ]; then + extra= + fi + if [ $PIP_OPTIMIZE = '1' ]; then python -m pip freeze >"$AFTER_PACKAGES" new_packages="$(diff -Naur "$BEFORE_PACKAGES" "$AFTER_PACKAGES" | grep -vE '^\+\+' | grep -E '^\+' | cut -f2 -d+ | cut -f1 -d= | xargs)" if [ "x$new_packages" != 'x' ]; then - >&2 echo "Optimizing packages (${new_packages})..." + if [ "$DEBUG" = '1' ]; then + >&2 echo "Optimizing packages (${new_packages})..." + fi for package in $new_packages do package_location=$(python -m pip show -f "$package" | grep -E '^Location' | cut -f2 -d: | xargs) for dir in $(python -m pip show -f "$package" | awk 'f;/Files:/{f=1}' | cut -f1 -d/ | sort | uniq | xargs) do - python -m compileall -b "$package_location/$dir" + eval python -m compileall \ + $extra \ + -b "$package_location/$dir" done done else - >&2 echo 'No new packages installed or changed to optimize with.' + if [ "$DEBUG" = '1' ]; then + >&2 echo 'No new packages installed or changed to optimize with.' + fi fi rm -f "$BEFORE_PACKAGES" "$AFTER_PACKAGES" fi export ALLOW_SITE_PACKAGES=1 find $BUILD_ROOT/usr/local/lib/python$PYTHON_VERSION/site-packages \ - -type f -name '*.py' -exec sh -c "remove-py-if-pyc-exists {}" \; ; + -type f -name '*.py' -exec sh -c "remove-py-if-pyc-exists $extra {}" \; ; return 0 } @@ -53,9 +67,4 @@ if [ $PIP_OPTIMIZE = '1' ]; then AFTER_PACKAGES=$(mktemp) python -m pip freeze >"$BEFORE_PACKAGES" fi -set -x -retval=0 -python -m pip $@ || retval=$? -set +ex - -exit $retval +python -m pip $@ diff --git a/remove-py-if-pyc-exists.sh b/remove-py-if-pyc-exists.sh index 7db94f6..ba0fc3a 100644 --- a/remove-py-if-pyc-exists.sh +++ b/remove-py-if-pyc-exists.sh @@ -1,12 +1,21 @@ #!/usr/bin/env sh ALLOW_SITE_PACKAGES=${ALLOW_SITE_PACKAGES:-0} +quiet=0 +if [ $1 = '-q' ]; then + quiet=1 + shift +fi if [ -f "${1}c" ]; then if [ "x$ALLOW_SITE_PACKAGES" = 'x0' ] && ! case "$1" in *site-packages/*) false ;; *) true ;; esac ; then - echo 'Skipping optimization of '"$1" + if [ $quiet -eq 0 ]; then + >&2 echo 'Skipping optimization of '"$1" + fi exit 0 fi - echo 'Removing '"${1}" + if [ $quiet -eq 0 ]; then + >&2 echo 'Removing '"${1}" + fi rm -f "${1}" fi \ No newline at end of file From dc7a26e49a7b7dfee28f5ba00d6ab097fc3766af Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Wed, 3 Dec 2025 20:47:17 -0800 Subject: [PATCH 03/53] build: reduce output --- Dockerfile.alpine | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.alpine b/Dockerfile.alpine index af9395f..6aab3c9 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -41,7 +41,7 @@ RUN set -eu ; \ find /usr/local/lib/python$PYTHON_VERSION -type d -name '__pycache__' -print0 | xargs -0 rm -rf ; \ # compile all py to an adjacent pyc and remove the original, leaving only the bytecode python -m compileall -b /usr/local/lib/python$PYTHON_VERSION ; \ - find -type f -name '*.py' -exec sh -c "remove-py-if-pyc-exists {}" \; ;\ + find -type f -name '*.py' -exec sh -c "remove-py-if-pyc-exists -q {}" \; ;\ # make the new root: mkdir -p \ $CACHE_ROOT/ \ From d526743ab4a0808baf424d018997db5b38caa8e1 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Wed, 3 Dec 2025 20:48:48 -0800 Subject: [PATCH 04/53] feat: update example --- examples/simple-flask/Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/simple-flask/Dockerfile b/examples/simple-flask/Dockerfile index 88a008b..9a7ce17 100644 --- a/examples/simple-flask/Dockerfile +++ b/examples/simple-flask/Dockerfile @@ -1,13 +1,12 @@ #syntax=docker/dockerfile:1 ARG SOURCE_IMAGE=autumnjolitz/distroless-python:3.12-alpine3.20 -FROM --platform=$TARGETPLATFORM ${SOURCE_IMAGE}-buildroot AS buildroot +FROM ${SOURCE_IMAGE}-buildroot AS buildroot ADD requirements.txt . -RUN python -m pip install \ +RUN chroot-pip install \ --no-cache \ - --prefix "$BUILD_ROOT/usr/local" \ -r requirements.txt ARG SOURCE_IMAGE=autumnjolitz/distroless-python:3.12-alpine3.20 From 42860f5c5ea23098d2cae0c5aacf0a0bcc62bce4 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Wed, 3 Dec 2025 20:54:28 -0800 Subject: [PATCH 05/53] build: quiet zip --- Dockerfile.alpine | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 6aab3c9..c355e4c 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -85,7 +85,7 @@ RUN set -eu ; \ cd /usr/local/lib && \ tar -C /usr/local/lib -cpf - python$PYTHON_VERSION/lib-dynload libpython* | tar -C $BUILD_ROOT/usr/local/lib -xpf - ; \ tar -C /usr/local/bin -cpf - python* | tar -C $BUILD_ROOT/usr/local/bin -xpf -; \ - (cd python$PYTHON_VERSION && zip -9 -X $BUILD_ROOT/usr/local/lib/python$(echo $PYTHON_VERSION | tr -d '.').zip $(\ + (cd python$PYTHON_VERSION && zip -q -9 -X $BUILD_ROOT/usr/local/lib/python$(echo $PYTHON_VERSION | tr -d '.').zip $(\ find . | grep -vE "(__pycache__|^\./(test|site-packages|lib-dynload|idlelib|lib2to3|tkinter|turtle|ensurepip))" \ )); \ cp -p python$PYTHON_VERSION/os.pyc $BUILD_ROOT/usr/local/lib/python$PYTHON_VERSION/os.pyc ; \ From 2266f0ab3df79f0f9d7924874474edaa9a0a142c Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Wed, 3 Dec 2025 20:55:11 -0800 Subject: [PATCH 06/53] build: remove debug line --- .github/workflows/main.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f75ea04..a65ebbb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -330,7 +330,6 @@ jobs: -H "Accept: application/vnd.github+json" \ -H 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \ $url)" - >&2 echo 'versions json from ('"$url"') is: '"${versions_json}" local version_id=$(echo "${versions_json}" | jq -r --arg tag "$tag" '.[] | select(.metadata.container.tags[] == $tag) | .id') local package_url="https://github.com/${repo_name}/pkgs/container/${package_name}/${version_id}?tag=${tag}" if ! [ x$output = x -o x$output = x- ]; then From d094eecde92b2daa850aefdd7b26c5257ab79ae0 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Wed, 3 Dec 2025 21:50:45 -0800 Subject: [PATCH 07/53] build: disable docker report output --- .github/workflows/development.yml | 8 ++++++++ .github/workflows/main.yml | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 925ed42..994c4f0 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -90,6 +90,9 @@ jobs: name: Create Buildroot uses: docker/build-push-action@v6 id: buildroot + env: + DOCKER_BUILD_SUMMARY: false + DOCKER_BUILD_RECORD_UPLOAD: false with: push: true platforms: ${{ env.TARGET_PLATFORMS }} @@ -119,6 +122,8 @@ jobs: uses: docker/build-push-action@v6 env: SOURCE_DATE_EPOCH: 0 + DOCKER_BUILD_SUMMARY: false + DOCKER_BUILD_RECORD_UPLOAD: false with: push: true context: "." @@ -156,6 +161,9 @@ jobs: - name: examples/simple-flask uses: docker/build-push-action@v6 + env: + DOCKER_BUILD_SUMMARY: false + DOCKER_BUILD_RECORD_UPLOAD: false with: context: "examples/simple-flask" platforms: ${{ env.TARGET_PLATFORMS }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a65ebbb..3c5b0ba 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -92,6 +92,9 @@ jobs: name: Create Buildroot uses: docker/build-push-action@v6 id: buildroot + env: + DOCKER_BUILD_SUMMARY: false + DOCKER_BUILD_RECORD_UPLOAD: false with: push: true platforms: ${{ env.TARGET_PLATFORMS }} @@ -121,6 +124,8 @@ jobs: uses: docker/build-push-action@v6 env: SOURCE_DATE_EPOCH: 0 + DOCKER_BUILD_SUMMARY: false + DOCKER_BUILD_RECORD_UPLOAD: false with: push: true context: "." @@ -158,6 +163,9 @@ jobs: - name: examples/simple-flask uses: docker/build-push-action@v6 + env: + DOCKER_BUILD_SUMMARY: false + DOCKER_BUILD_RECORD_UPLOAD: false with: context: "examples/simple-flask" platforms: ${{ env.TARGET_PLATFORMS }} From 913a02d581706a104dd839571e9832e42ff6d27f Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Wed, 3 Dec 2025 21:51:12 -0800 Subject: [PATCH 08/53] build: run pre-commit --- .github/workflows/development.yml | 8 ++++---- .github/workflows/main.yml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 994c4f0..d034b97 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -3,7 +3,7 @@ name: development on: pull_request: push: - branches: + branches: - '!main' # excludes main - '!master' # excludes master @@ -215,12 +215,12 @@ jobs: uses: actions/checkout@v4 - uses: actions/setup-python@v6 with: - python-version: '3.12' + python-version: '3.12' - name: install dependencies run: | python -m pip install jinplate - - + - name: fetch metadata uses: actions/download-artifact@v5 with: @@ -245,7 +245,7 @@ jobs: printf '```\n' } >>"$GITHUB_STEP_SUMMARY" - - + - name: fetch images uses: actions/download-artifact@v5 with: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3c5b0ba..6fe19eb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,7 +6,7 @@ on: # https://crontab.guru/once-a-week - cron: "0 0 * * 0" push: - branches: + branches: - 'main' @@ -222,7 +222,7 @@ jobs: uses: actions/checkout@v4 - uses: actions/setup-python@v6 with: - python-version: '3.12' + python-version: '3.12' - name: install dependencies run: | @@ -230,7 +230,7 @@ jobs: python -m pip install jinplate - - + - name: fetch images uses: actions/download-artifact@v5 with: @@ -430,7 +430,7 @@ jobs: README.rst README.md - - + - name: Update repo description uses: peter-evans/dockerhub-description@e98e4d1628a5f3be2be7c231e50981aee98723ae # v4.0.0 with: From c9031d804397323df9a6263a109a7ea86a2d855f Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Wed, 3 Dec 2025 21:51:42 -0800 Subject: [PATCH 09/53] build: update ``pre-commit`` --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 31cb6a5..1db0ded 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v6.0.0 hooks: - id: check-ast From 094119458b9a97146726a5dac78eb1bb8706232d Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 12:56:42 -0800 Subject: [PATCH 10/53] build: moved README template to docs, parallel image uploader --- .github/workflows/development.yml | 16 +- .github/workflows/main.yml | 200 ++++++++++++++++-------- README.rst.tmpl => docs/README.rst.tmpl | 0 3 files changed, 151 insertions(+), 65 deletions(-) rename README.rst.tmpl => docs/README.rst.tmpl (100%) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index d034b97..e652fe1 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -176,6 +176,10 @@ jobs: SOURCE_IMAGE=${{ steps.image_env.outputs.IMAGE_TAG }} tags: "${{ steps.image_env.outputs.IMAGE_TAG }}-example1-amd64" outputs: type=oci,dest=${{ steps.image_env.outputs.IMAGE_HOME }}/example1-${{ steps.image_env.outputs.IMAGE_TAG_SAFE }}.tar + - + id: test + run: | + - id: post_build run: | @@ -186,7 +190,9 @@ jobs: echo '${{ steps.image_env.outputs.IMAGE_TAG }}@'"$IMAGE_DIGEST" | tee -a ${{ steps.image_env.outputs.IMAGE_HOME }}/manifest.txt echo '${{ steps.image_env.outputs.IMAGE_BUILDROOT_TAG }}@'"$IMAGE_BUILDROOT_DIGEST" | tee -a ${{ steps.image_env.outputs.IMAGE_HOME }}/manifest.txt ls ${{ steps.image_env.outputs.IMAGE_HOME }} - - name: upload build + + - + name: upload build uses: actions/upload-artifact@v4 with: if-no-files-found: error @@ -196,7 +202,8 @@ jobs: ${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.IMAGE_TAG_SAFE }}.tar ${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.IMAGE_BUILDROOT_TAG_SAFE }}.tar - - name: upload build info + - + name: upload build info uses: actions/upload-artifact@v4 with: if-no-files-found: error @@ -205,7 +212,8 @@ jobs: path: | ${{ steps.image_env.outputs.IMAGE_HOME }}/*.txt - render-dockerhub-desc: + + post-build: name: "Render metadata" needs: [build_images] runs-on: "ubuntu-latest" @@ -277,7 +285,7 @@ jobs: cat ./values.json >./README.rst python \ -m jinplate.cli \ - README.rst.tmpl file://$PWD/values.json + docs/README.rst.tmpl file://$PWD/values.json - name: Convert README.rst to markdown uses: docker://pandoc/core:3.8 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6fe19eb..36a36a2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -189,7 +189,8 @@ jobs: echo '${{ steps.image_env.outputs.IMAGE_BUILDROOT_TAG }}@'"$IMAGE_BUILDROOT_DIGEST" | tee -a ${{ steps.image_env.outputs.IMAGE_HOME }}/manifest.txt ls ${{ steps.image_env.outputs.IMAGE_HOME }} - - name: upload build + - + name: upload build uses: actions/upload-artifact@v4 with: if-no-files-found: error @@ -199,7 +200,8 @@ jobs: ${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.IMAGE_TAG_SAFE }}.tar ${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.IMAGE_BUILDROOT_TAG_SAFE }}.tar - - name: upload build info + - + name: upload build info uses: actions/upload-artifact@v4 with: if-no-files-found: error @@ -208,61 +210,98 @@ jobs: path: | ${{ steps.image_env.outputs.IMAGE_HOME }}/*.txt - update-dockerhub-desc: + upload: name: "Upload and update metadata" needs: [build_images] - runs-on: "ubuntu-latest" + + strategy: + fail-fast: true + matrix: + registry: + - docker.io + - ghcr.io + python: + - '3.14' + - '3.13' + - '3.12' + - '3.11' + - '3.10' + - '3.9' + - '3.8' + alpine: + - '3.20' + - '3.21' + - '3.22' + os: + - 'ubuntu-latest' + exclude: + # No tag + - python: '3.8' + alpine: '3.21' + - python: '3.8' + alpine: '3.22' + - python: '3.14' + alpine: '3.20' permissions: packages: write + runs-on: ${{ matrix.os }} steps: - name: Checkout uses: actions/checkout@v4 - - uses: actions/setup-python@v6 + - + uses: actions/setup-python@v6 with: python-version: '3.12' - name: install dependencies run: | sudo apt-get -y install skopeo - python -m pip install jinplate - - - name: fetch images uses: actions/download-artifact@v5 with: - pattern: images-* - path: dist-images + name: images-alpine${{ matrix.alpine }}-python${{ matrix.python }} merge-multiple: true - + path: dist-images/ - - name: prep files + name: Rename files run: | - printf "Images:\n\n" >> "$GITHUB_STEP_SUMMARY" + printf 'Images:\n\n' >> "$GITHUB_STEP_SUMMARY" for filename in $(find dist-images -type f -name "*.tar" -print) do image_uri="$(basename -s .tar $filename | sed 's/image-//g' | base64 -d)" - for repository in docker.io ghcr.io - do - new_image_uri=$(echo $image_uri | sed 's|localhost:5000|'"$repository"'|g') - new_filename="dist-images/image-$(echo $new_image_uri | base64 -w 0).tar" - cp ${filename} ${new_filename} - echo '* `'${new_filename}'` (`'${new_image_uri}'`)' >> "$GITHUB_STEP_SUMMARY" - done + new_image_uri=$(echo $image_uri | sed 's|localhost:5000|${{ matrix.repository }}|g') + new_filename="dist-images/image-$(echo $new_image_uri | base64 -w 0).tar" + cp ${filename} ${new_filename} + printf '* `'${new_filename}'` (`'${new_image_uri}'`)\n' >> "$GITHUB_STEP_SUMMARY" rm $filename done + printf '\n' >> "GITHUB_STEP_SUMMARY" - - name: login to registry + - name: login to dockerhub + if: ${{ matrix.repository == 'docker.io' }} env: DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - printf "${GITHUB_TOKEN}\n" | skopeo login --password-stdin --username ${{ github.repository_owner }} ghcr.io - printf "${DOCKERHUB_TOKEN}\n" | skopeo login --password-stdin --username ${{ github.repository_owner }} docker.io + printf "${DOCKERHUB_TOKEN}\n" | skopeo \ + login \ + --password-stdin \ + --username ${{ github.repository_owner }} \ + '${{ matrix.registry }}' + - name: login to ghcr + if: ${{ matrix.repository == 'ghcr.io' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + printf "${GITHUB_TOKEN}\n" | skopeo \ + login \ + --password-stdin \ + --username ${{ github.repository_owner }} \ + '${{ matrix.registry }}' - name: upload to registry run: | @@ -340,14 +379,13 @@ jobs: $url)" local version_id=$(echo "${versions_json}" | jq -r --arg tag "$tag" '.[] | select(.metadata.container.tags[] == $tag) | .id') local package_url="https://github.com/${repo_name}/pkgs/container/${package_name}/${version_id}?tag=${tag}" - if ! [ x$output = x -o x$output = x- ]; then + if ! [ x$output = x -o x$output = x- -o x$output = x/dev/stdout ]; then >>"$output" echo "${image_tag}@${package_url}" else echo "${image_tag}@${package_url}" fi } - printf '#Upload to registry\n\n' >> "$GITHUB_STEP_SUMMARY" num_outstanding=0 concurrency_limit=8 for filename in $(find dist-images -type f -name "*.tar" -print) @@ -362,26 +400,12 @@ jobs: done wait echo 'Done uploading' - - - - - name: aggregate metadata - run: | { - printf '### manifest.txt\n\n' - printf '```\n' + printf '# Uploaded\n\n```' cat manifest.txt - printf '```\n' - } >>"$GITHUB_STEP_SUMMARY" - { - printf '### packages.txt\n\n' - printf '```\n' - cat packages.txt - printf '```\n' + print '```\n\n' } >>"$GITHUB_STEP_SUMMARY" - - - name: parse metadata for README - run: | + for filename in $(echo dist-images/image-*.tar) do IMAGE_URI="$(basename -s .tar $filename | sed 's/image-//g' | base64 -d)" @@ -401,37 +425,91 @@ jobs: "package_url": "'$PACKAGE_URL'" }' done | >./values.json jq -s '. | {"images": .}' - cat ./values.json + + - + name: Upload metadata + uses: actions/upload-artifact@v4 + with: + if-no-files-found: error + retention-days: 1 + name: upload-alpine${{ matrix.alpine }}-python${{ matrix.python }}-metadata + path: | + manifest.txt + packages.txt + values.json + + post-upload: + name: "Post Upload" + needs: [upload] + runs-on: "ubuntu-latest" + + steps: + - + name: Checkout + uses: actions/checkout@v4 + - + name: "Setup Python" + uses: actions/setup-python@v6 + with: + python-version: '3.12' + - + name: install dependencies + run: | + python -m pip install jinplate + - + name: Fetch build metadata + uses: actions/download-artifact@v5 + with: + pattern: upload-*-metadata + path: dist-images/ + merge-multiple: false + - + name: parse metadata for README + run: | + >./manifest.txt cat $(find ./dist-images -type f -name 'manifest.txt' -print) + >./packages.txt cat $(find ./dist-images -type f -name 'packages.txt' -print) + { + printf '# manifest_file.txt\n\n```' + cat ./manifest.txt + printf '```\n\n' + printf '# packages.txt\n\n```' + cat ./packages.txt + printf '```\n\n' + printf '# values.json\n\n```' + cat \ + $(find ./dist-images -type f -name 'values.json' -print) | \ + jq -r --slurp '[.[]] | flatten' | tee ./values.json + printf '```\n\n' + } | tee -a "$GITHUB_STEP_SUMMARY" + find ./dist-images \ + -type \( -name manifest.txt -o -name packages.txt -o -name values.json \) \ + -delete >./README.rst python \ -m jinplate.cli \ - README.rst.tmpl file://$PWD/values.json + docs/README.rst.tmpl file://$PWD/values.json + { + printf "# README.rst\n\n```" + cat README.rst + printf "```\n\n" + } | tee -a "$GITHUB_STEP_SUMMARY" - - name: Convert README.rst to markdown + name: "Convert README.rst to markdown" uses: docker://pandoc/core:3.8 with: args: >- - -s --wrap=none -t gfm -o README.md README.rst - - name: Print out markdown + name: "Print out markdown" run: | - cat README.md >>"$GITHUB_STEP_SUMMARY" - - - name: upload build info - uses: actions/upload-artifact@v4 - with: - if-no-files-found: error - retention-days: 1 - name: readmes - path: | - README.rst - README.md - + { + printf '# README.md\n\n' + cat README.md + } | tee -a "$GITHUB_STEP_SUMMARY" - - name: Update repo description + name: Update Dockerhub description uses: peter-evans/dockerhub-description@e98e4d1628a5f3be2be7c231e50981aee98723ae # v4.0.0 with: username: ${{ github.repository_owner }} diff --git a/README.rst.tmpl b/docs/README.rst.tmpl similarity index 100% rename from README.rst.tmpl rename to docs/README.rst.tmpl From e8cdc2931675281aadca2eeefa7a4e1c580b331b Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 13:50:22 -0800 Subject: [PATCH 11/53] build: add test for example --- .github/workflows/development.yml | 45 ++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index e652fe1..764e921 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -174,11 +174,54 @@ jobs: type=registry,ref=${{ steps.image_env.outputs.SOURCE_IMAGE }}@${{ steps.image_env.outputs.BASE_IMAGE_DIGEST }} build-args: | SOURCE_IMAGE=${{ steps.image_env.outputs.IMAGE_TAG }} - tags: "${{ steps.image_env.outputs.IMAGE_TAG }}-example1-amd64" + tags: "${{ steps.image_env.outputs.IMAGE_TAG }}-example1" outputs: type=oci,dest=${{ steps.image_env.outputs.IMAGE_HOME }}/example1-${{ steps.image_env.outputs.IMAGE_TAG_SAFE }}.tar - id: test + env: + IMAGE_URI: '${{ steps.image_env.outputs.IMAGE_TAG }}-example1' + IMAGE_ARCHIVE: '${{ steps.image_env.outputs.IMAGE_HOME }}/example1-${{ steps.image_env.outputs.IMAGE_TAG_SAFE }}.tar' run: | + set -o pipefail + docker image load -i "$IMAGE_ARCHIVE" + rc=0 + container_id="$(docker run --detach -p 8080:8080 --rm $IMAGE_URI)" || rc=$? + if [ $rc -ne 0 ]; then + printf 'docker run failed (return code '$rc')!\n' | >&2 tee \ + -a "$GITHUB_OUTPUT" + exit $rc + fi + trap 'docker stop $container_id' EXIT + start_ts=$(date +%s) + test_command="curl -sLv 'http://localhost:8080/'" + + run_test () { + local rc=0 + local T="$(mktemp)" + trap 'rm -f '"$T" RETURN + >"$T" 2>&1 eval $test_command || rc=$? + printf '`%s` (returned code: ' "$test_command" + printf '%s)\n```\n' "$rc" + cat "$T" + printf '\n```\n\n' + return "$rc" + } + + until [ x$(curl \ + --silent --output /dev/null \ + -w '%{http_code}\n' --fail 'http://localhost:8080/') = x200 ] + do + sleep 1 + if [ "$(expr "${start_ts}" \+ 30)" -lt "$(date +%s)" ]; then + >&2 echo 'failed to get a good response in 30 seconds!' + break + fi + done + rc=0 + (run_test | tee -a "$GITHUB_STEP_SUMMARY) || rc=$? + if [ $rc -ne 0 ]; then + exit $rc + fi - id: post_build From c74a341c40a6f0b66183c609c5b583035ff77a0c Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 13:54:57 -0800 Subject: [PATCH 12/53] build: add containerd --- .github/workflows/development.yml | 6 ++++++ .github/workflows/main.yml | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 764e921..9170728 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -62,6 +62,12 @@ jobs: uses: docker/setup-buildx-action@v3 with: driver-opts: network=host + daemon-config: | + { + "features": { + "containerd-snapshotter": true + } + } - id: image_env run: | diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 36a36a2..434288f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -64,6 +64,13 @@ jobs: uses: docker/setup-buildx-action@v3 with: driver-opts: network=host + daemon-config: | + { + "features": { + "containerd-snapshotter": true + } + } + - id: image_env run: | From 3d06444cfe22baff58a87ad00191f6a2c4a6083a Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 14:07:16 -0800 Subject: [PATCH 13/53] build: use local registry --- .github/workflows/development.yml | 34 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 9170728..fec2bad 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -80,17 +80,19 @@ jobs: docker pull "${SOURCE_IMAGE}" || true echo "IMAGE_HOME=$(mktemp -d)" >> "$GITHUB_OUTPUT" - echo ALPINE_VERSION="${ALPINE_VERSION}" >> "$GITHUB_OUTPUT" - echo PYTHON_VERSION="${PYTHON_VERSION}" >> "$GITHUB_OUTPUT" - echo SOURCE_IMAGE="${SOURCE_IMAGE}" >> "$GITHUB_OUTPUT" - echo IMAGE_TAG="${IMAGE_TAG}" >> "$GITHUB_OUTPUT" - echo IMAGE_BUILDROOT_TAG="${IMAGE_TAG}-buildroot" >> "$GITHUB_OUTPUT" - echo IMAGE_TAG_SAFE="$(echo "$IMAGE_TAG" | base64 -w 0 )" >> "$GITHUB_OUTPUT" - echo IMAGE_BUILDROOT_TAG_SAFE="$(echo "${IMAGE_TAG}-buildroot" | base64 -w 0 )" >> "$GITHUB_OUTPUT" - echo REPOSITORY="${REPOSITORY}" >> "$GITHUB_OUTPUT" - echo REPOSITORY_SAFE="$(echo "${REPOSITORY}" | base64 -w 0 )" >> "$GITHUB_OUTPUT" - echo BASE_IMAGE_DIGEST="$(digest_of "$SOURCE_IMAGE")" >> "$GITHUB_OUTPUT" - echo 'IMAGE_DESCRIPTION=${{ github.event.repository.description }}. See ${{ github.server_url }}/${{ github.repository }} for more info.' >> "$GITHUB_OUTPUT" + echo ALPINE_VERSION="${ALPINE_VERSION}"| tee -a "$GITHUB_OUTPUT" + echo PYTHON_VERSION="${PYTHON_VERSION}"| tee -a "$GITHUB_OUTPUT" + echo SOURCE_IMAGE="${SOURCE_IMAGE}"| tee -a "$GITHUB_OUTPUT" + echo IMAGE_TAG="${IMAGE_TAG}"| tee -a "$GITHUB_OUTPUT" + echo IMAGE_BUILDROOT_TAG="${IMAGE_TAG}-buildroot"| tee -a "$GITHUB_OUTPUT" + echo IMAGE_TAG_SAFE="$(echo "$IMAGE_TAG" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT" + echo IMAGE_BUILDROOT_TAG_SAFE="$(echo "${IMAGE_TAG}-buildroot" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT" + echo 'EXAMPLE_IMAGE_TAG='"${IMAGE_TAG}-example1" | tee -a "$GITHUB_OUTPUT" + + echo REPOSITORY="${REPOSITORY}"| tee -a "$GITHUB_OUTPUT" + echo REPOSITORY_SAFE="$(echo "${REPOSITORY}" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT" + echo BASE_IMAGE_DIGEST="$(digest_of "$SOURCE_IMAGE")"| tee -a "$GITHUB_OUTPUT" + echo 'IMAGE_DESCRIPTION=${{ github.event.repository.description }}. See ${{ github.server_url }}/${{ github.repository }} for more info.' | tee -a "$GITHUB_OUTPUT" - name: Create Buildroot @@ -171,6 +173,7 @@ jobs: DOCKER_BUILD_SUMMARY: false DOCKER_BUILD_RECORD_UPLOAD: false with: + push: true context: "examples/simple-flask" platforms: ${{ env.TARGET_PLATFORMS }} cache-from: | @@ -185,13 +188,12 @@ jobs: - id: test env: - IMAGE_URI: '${{ steps.image_env.outputs.IMAGE_TAG }}-example1' - IMAGE_ARCHIVE: '${{ steps.image_env.outputs.IMAGE_HOME }}/example1-${{ steps.image_env.outputs.IMAGE_TAG_SAFE }}.tar' + IMAGE_URI: '${{ steps.image_env.outputs.EXAMPLE_IMAGE_TAG }}' + test_command: curl -sLv 'http://localhost:8080/' run: | set -o pipefail - docker image load -i "$IMAGE_ARCHIVE" rc=0 - container_id="$(docker run --detach -p 8080:8080 --rm $IMAGE_URI)" || rc=$? + container_id="$(docker run --pull never --detach -p 8080:8080 --rm $IMAGE_URI)" || rc=$? if [ $rc -ne 0 ]; then printf 'docker run failed (return code '$rc')!\n' | >&2 tee \ -a "$GITHUB_OUTPUT" @@ -199,8 +201,6 @@ jobs: fi trap 'docker stop $container_id' EXIT start_ts=$(date +%s) - test_command="curl -sLv 'http://localhost:8080/'" - run_test () { local rc=0 local T="$(mktemp)" From 4cbe901594051273b7fa478e417e8a6ad395edd8 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 14:09:46 -0800 Subject: [PATCH 14/53] build: refine test --- .github/workflows/development.yml | 33 ++++++++++++++++--------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index fec2bad..b62b3d6 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -77,7 +77,7 @@ jobs: '${{ github.repository_owner }}' \ 'localhost:5000' - docker pull "${SOURCE_IMAGE}" || true + docker pull -q "${SOURCE_IMAGE}" || true echo "IMAGE_HOME=$(mktemp -d)" >> "$GITHUB_OUTPUT" echo ALPINE_VERSION="${ALPINE_VERSION}"| tee -a "$GITHUB_OUTPUT" @@ -88,7 +88,6 @@ jobs: echo IMAGE_TAG_SAFE="$(echo "$IMAGE_TAG" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT" echo IMAGE_BUILDROOT_TAG_SAFE="$(echo "${IMAGE_TAG}-buildroot" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT" echo 'EXAMPLE_IMAGE_TAG='"${IMAGE_TAG}-example1" | tee -a "$GITHUB_OUTPUT" - echo REPOSITORY="${REPOSITORY}"| tee -a "$GITHUB_OUTPUT" echo REPOSITORY_SAFE="$(echo "${REPOSITORY}" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT" echo BASE_IMAGE_DIGEST="$(digest_of "$SOURCE_IMAGE")"| tee -a "$GITHUB_OUTPUT" @@ -192,27 +191,29 @@ jobs: test_command: curl -sLv 'http://localhost:8080/' run: | set -o pipefail + + run_test () { + local rv=0 + local T="$(mktemp)" + trap 'rm -f '"$T" RETURN + >"$T" 2>&1 eval "$@" || rv=$? + printf '`%s` (returned code: ' "$@" + printf '%s)\n```\n' "$rv" + cat "$T" + printf '\n```\n\n' + return "$rv" + } + rc=0 - container_id="$(docker run --pull never --detach -p 8080:8080 --rm $IMAGE_URI)" || rc=$? + container_id="$(docker run --detach -p 8080:8080 --rm $IMAGE_URI)" || rc=$? if [ $rc -ne 0 ]; then printf 'docker run failed (return code '$rc')!\n' | >&2 tee \ -a "$GITHUB_OUTPUT" exit $rc fi trap 'docker stop $container_id' EXIT - start_ts=$(date +%s) - run_test () { - local rc=0 - local T="$(mktemp)" - trap 'rm -f '"$T" RETURN - >"$T" 2>&1 eval $test_command || rc=$? - printf '`%s` (returned code: ' "$test_command" - printf '%s)\n```\n' "$rc" - cat "$T" - printf '\n```\n\n' - return "$rc" - } + start_ts=$(date +%s) until [ x$(curl \ --silent --output /dev/null \ -w '%{http_code}\n' --fail 'http://localhost:8080/') = x200 ] @@ -224,7 +225,7 @@ jobs: fi done rc=0 - (run_test | tee -a "$GITHUB_STEP_SUMMARY) || rc=$? + (run_test "$test_command" | tee -a "$GITHUB_STEP_SUMMARY") || rc=$? if [ $rc -ne 0 ]; then exit $rc fi From 542c04959f582fb61b551002d966aa7e3abd68ee Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 14:22:03 -0800 Subject: [PATCH 15/53] build: add metadata name --- .github/workflows/development.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index b62b3d6..1d58403 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -186,6 +186,7 @@ jobs: outputs: type=oci,dest=${{ steps.image_env.outputs.IMAGE_HOME }}/example1-${{ steps.image_env.outputs.IMAGE_TAG_SAFE }}.tar - id: test + name: Test Example env: IMAGE_URI: '${{ steps.image_env.outputs.EXAMPLE_IMAGE_TAG }}' test_command: curl -sLv 'http://localhost:8080/' @@ -193,6 +194,7 @@ jobs: set -o pipefail run_test () { + printf '## Test\n\n' local rv=0 local T="$(mktemp)" trap 'rm -f '"$T" RETURN @@ -200,11 +202,14 @@ jobs: printf '`%s` (returned code: ' "$@" printf '%s)\n```\n' "$rv" cat "$T" - printf '\n```\n\n' + printf '\n```\n\n### images\n\n```' + docker images --tree $IMAGE_URI + printf '\n```\n' return "$rv" } rc=0 + docker pull -q "$IMAGE_URI" container_id="$(docker run --detach -p 8080:8080 --rm $IMAGE_URI)" || rc=$? if [ $rc -ne 0 ]; then printf 'docker run failed (return code '$rc')!\n' | >&2 tee \ From 1f40c582f44d7bc3b6f202932b2fef86651a4fa1 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 14:25:16 -0800 Subject: [PATCH 16/53] build: fallible test --- .github/workflows/development.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 1d58403..477b1f4 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -199,6 +199,10 @@ jobs: local T="$(mktemp)" trap 'rm -f '"$T" RETURN >"$T" 2>&1 eval "$@" || rv=$? + if [ $rv -eq 0 ] && case "$(tail -1 $T)" in '

Hello, World!

') false ;; *) true ;; esac ; then + >&2 echo 'failed! got "'"$(tail -1 $T)"'" !' + rv=1 + fi printf '`%s` (returned code: ' "$@" printf '%s)\n```\n' "$rv" cat "$T" From 47665b7d5e16fe3356bfb9506fa51807c610f026 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 14:36:22 -0800 Subject: [PATCH 17/53] build: pull N platforms --- .github/workflows/development.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 477b1f4..f250371 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -167,6 +167,7 @@ jobs: - name: examples/simple-flask + id: build_test uses: docker/build-push-action@v6 env: DOCKER_BUILD_SUMMARY: false @@ -189,6 +190,7 @@ jobs: name: Test Example env: IMAGE_URI: '${{ steps.image_env.outputs.EXAMPLE_IMAGE_TAG }}' + IMAGE_DIGEST: '${{ steps.build_test.outputs.digest }}' test_command: curl -sLv 'http://localhost:8080/' run: | set -o pipefail @@ -207,12 +209,16 @@ jobs: printf '%s)\n```\n' "$rv" cat "$T" printf '\n```\n\n### images\n\n```' - docker images --tree $IMAGE_URI + docker images --tree $IMAGE_URI | cat printf '\n```\n' return "$rv" } rc=0 + for platform in $(echo $TARGET_PLATFORMS | tr , '\n') + do + docker pull --platform $platform -q "$IMAGE_URI" + done docker pull -q "$IMAGE_URI" container_id="$(docker run --detach -p 8080:8080 --rm $IMAGE_URI)" || rc=$? if [ $rc -ne 0 ]; then From 1dc3234c3f481b8b93a40e6d11104134fa4d58a0 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 14:45:48 -0800 Subject: [PATCH 18/53] build: remove color codes --- .github/workflows/development.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index f250371..8600288 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -209,7 +209,7 @@ jobs: printf '%s)\n```\n' "$rv" cat "$T" printf '\n```\n\n### images\n\n```' - docker images --tree $IMAGE_URI | cat + docker images --tree $IMAGE_URI | sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2};?)?)?[mGK]//g" printf '\n```\n' return "$rv" } From d21b7900a62d2f59d73df94485ce4ebd5ff94204 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 14:56:32 -0800 Subject: [PATCH 19/53] build: verify digest --- .github/workflows/development.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 8600288..ce5129e 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -10,6 +10,8 @@ on: env: TARGET_PLATFORMS: linux/amd64,linux/arm64 + COMPOSE_DOCKER_CLI_BUILD: '1' + DOCKER_BUILDKIT: '1' jobs: build_images: @@ -215,11 +217,17 @@ jobs: } rc=0 + REMOTE_DIGEST="$(skopeo inspect --raw "$IMAGE_URI" | skopeo manifest-digest /dev/stdin)" + if [ "$REMOTE_DIGEST" != "${IMAGE_DIGEST}" ]; then + echo 'generated a diferent manifest than '"${IMAGE_DIGEST}"': '"$REMOTE_DIGEST" + exit 2 + fi + docker pull -q "$(echo "$IMAGE_URI" | rev | cut -d: -f2- | rev )@${IMAGE_DIGEST}" for platform in $(echo $TARGET_PLATFORMS | tr , '\n') do docker pull --platform $platform -q "$IMAGE_URI" + >&2 printf 'pulled %s (%s)\n' "$IMAGE_TAG" "$platform" done - docker pull -q "$IMAGE_URI" container_id="$(docker run --detach -p 8080:8080 --rm $IMAGE_URI)" || rc=$? if [ $rc -ne 0 ]; then printf 'docker run failed (return code '$rc')!\n' | >&2 tee \ From 32c82729fa91d430eef94f622f73a4a606d32d19 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 15:08:50 -0800 Subject: [PATCH 20/53] build: try a multi-image store setting --- .github/workflows/development.yml | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index ce5129e..ba01d15 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -55,7 +55,29 @@ jobs: - name: Setup run: | + + setup_containerd_snapshotter () { + local _sudo=sudo + if [ $(id -u) -eq 0 ]; then + _sudo= + fi + if ! [ -f /etc/docker/daemon.json ]; then + eval $_sudo mkdir -p /etc/docker + echo '{}' | $_sudo tee /etc/docker/daemon.json + fi + cat /etc/docker/daemon.json | jq '. | .+{"features": {"containerd-snapshotter": true}}' | eval $_sudo tee /etc/docker/daemon.json + printf '# Docker config\n\n```\n' + cat /etc/docker/daemon.json + printf '\n```\n\n' + eval $_sudo systemctl restart docker + sleep 1 + printf '# Docker Info\n\n```' + docker info + printf '\n```\n' + } + sudo apt-get -y install skopeo + setup_containerd_snapshotter | tee -a "$GITHUB_STEP_SUMMARY" - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -63,13 +85,8 @@ jobs: name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: + platforms: ${{ env.TARGET_PLATFORMS }} driver-opts: network=host - daemon-config: | - { - "features": { - "containerd-snapshotter": true - } - } - id: image_env run: | From a01b6ad7143a674e7fdc1ea0b70fd6566c2af4ce Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 15:25:06 -0800 Subject: [PATCH 21/53] build: restarting docker trashes the registry, so start in init --- .github/workflows/development.yml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index ba01d15..03818c1 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -16,11 +16,6 @@ env: jobs: build_images: name: "Create runtime and buildroot OCI Images" - services: - registry: - image: registry:3 - ports: - - 5000:5000 strategy: fail-fast: true matrix: @@ -55,11 +50,17 @@ jobs: - name: Setup run: | - + setup_registry () { + registry_id=$(docker run \ + -p5000:5000 \ + --name registry \ + --rm -d registry:3) + echo 'Started registry at '$registry_id + } setup_containerd_snapshotter () { local _sudo=sudo if [ $(id -u) -eq 0 ]; then - _sudo= + _sudo=sudo fi if ! [ -f /etc/docker/daemon.json ]; then eval $_sudo mkdir -p /etc/docker @@ -78,6 +79,8 @@ jobs: sudo apt-get -y install skopeo setup_containerd_snapshotter | tee -a "$GITHUB_STEP_SUMMARY" + printf '## Registry info:\n\n' | tee -a "$GITHUB_STEP_SUMMARY" + setup_registry | tee -a "$GITHUB_STEP_SUMMARY" - name: Set up QEMU uses: docker/setup-qemu-action@v3 From 4d0abac7ccf3688916a96f4fc6325dbc2fa982ac Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 15:28:34 -0800 Subject: [PATCH 22/53] build: stop registry when done --- .github/workflows/development.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 03818c1..2bf53ff 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -48,6 +48,7 @@ jobs: name: Checkout uses: actions/checkout@v4 - + id: setup name: Setup run: | setup_registry () { @@ -55,7 +56,7 @@ jobs: -p5000:5000 \ --name registry \ --rm -d registry:3) - echo 'Started registry at '$registry_id + echo 'REGISTRY_CONTAINER_ID='$registry_id } setup_containerd_snapshotter () { local _sudo=sudo @@ -80,7 +81,7 @@ jobs: sudo apt-get -y install skopeo setup_containerd_snapshotter | tee -a "$GITHUB_STEP_SUMMARY" printf '## Registry info:\n\n' | tee -a "$GITHUB_STEP_SUMMARY" - setup_registry | tee -a "$GITHUB_STEP_SUMMARY" + setup_registry | tee -a "$GITHUB_OUTPUT" - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -237,7 +238,7 @@ jobs: } rc=0 - REMOTE_DIGEST="$(skopeo inspect --raw "$IMAGE_URI" | skopeo manifest-digest /dev/stdin)" + REMOTE_DIGEST="$(skopeo inspect --tls-verify=false --raw docker://"$IMAGE_URI" | skopeo manifest-digest /dev/stdin)" if [ "$REMOTE_DIGEST" != "${IMAGE_DIGEST}" ]; then echo 'generated a diferent manifest than '"${IMAGE_DIGEST}"': '"$REMOTE_DIGEST" exit 2 @@ -276,6 +277,8 @@ jobs: - id: post_build run: | + docker stop '${{ steps.setup.outputs.REGISTRY_CONTAINER_ID }}' + IMAGE_DIGEST='${{steps.runtime.outputs.digest}}' IMAGE_BUILDROOT_DIGEST='${{steps.buildroot.outputs.digest}}' echo 'IMAGE_DIGEST='$IMAGE_DIGEST >> "GITHUB_OUTPUT" From 64daf70c8a0d14786d052603f4ff21f7c2a02e33 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 15:45:40 -0800 Subject: [PATCH 23/53] build: test both platforms --- .github/workflows/development.yml | 62 +++++++++++++++++++------------ 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 2bf53ff..715c568 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -219,7 +219,6 @@ jobs: set -o pipefail run_test () { - printf '## Test\n\n' local rv=0 local T="$(mktemp)" trap 'rm -f '"$T" RETURN @@ -231,9 +230,7 @@ jobs: printf '`%s` (returned code: ' "$@" printf '%s)\n```\n' "$rv" cat "$T" - printf '\n```\n\n### images\n\n```' - docker images --tree $IMAGE_URI | sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2};?)?)?[mGK]//g" - printf '\n```\n' + printf '\n```\n\n' return "$rv" } @@ -249,30 +246,47 @@ jobs: docker pull --platform $platform -q "$IMAGE_URI" >&2 printf 'pulled %s (%s)\n' "$IMAGE_TAG" "$platform" done - container_id="$(docker run --detach -p 8080:8080 --rm $IMAGE_URI)" || rc=$? - if [ $rc -ne 0 ]; then - printf 'docker run failed (return code '$rc')!\n' | >&2 tee \ - -a "$GITHUB_OUTPUT" - exit $rc - fi - trap 'docker stop $container_id' EXIT + { + printf '## images\n\n```' + docker images --tree $IMAGE_URI | sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2};?)?)?[mGK]//g" + printf '\n```\n' + } | tee -a "$GITHUB_STEP_SUMMARY" - start_ts=$(date +%s) - until [ x$(curl \ - --silent --output /dev/null \ - -w '%{http_code}\n' --fail 'http://localhost:8080/') = x200 ] + for platform in $(echo $TARGET_PLATFORMS | tr , '\n') do - sleep 1 - if [ "$(expr "${start_ts}" \+ 30)" -lt "$(date +%s)" ]; then - >&2 echo 'failed to get a good response in 30 seconds!' - break + rc=0 + container_id="$(docker run \ + --platform $platform \ + --detach -p 8080:8080 \ + --rm $IMAGE_URI)" || rc=$? + trap 'docker stop $container_id' EXIT + if [ $rc -ne 0 ]; then + printf 'docker run failed (return code '$rc')!\n' | >&2 tee \ + -a "$GITHUB_OUTPUT" + exit $rc + fi + start_ts=$(date +%s) + until [ x$(curl \ + --silent --output /dev/null \ + -w '%{http_code}\n' --fail 'http://localhost:8080/') = x200 ] + do + sleep 1 + if [ "$(expr "${start_ts}" \+ 30)" -lt "$(date +%s)" ]; then + >&2 echo 'failed to get a good response in 30 seconds!' + break + fi + done + + rc=0 + printf '## Test (%s)\n\n' $platform | tee -a "$GITHUB_STEP_SUMMARY" + (run_test "$test_command" | tee -a "$GITHUB_STEP_SUMMARY") || rc=$? + if [ $rc -ne 0 ]; then + exit $rc fi + trap -- EXIT + docker stop $container_id done - rc=0 - (run_test "$test_command" | tee -a "$GITHUB_STEP_SUMMARY") || rc=$? - if [ $rc -ne 0 ]; then - exit $rc - fi + - id: post_build From a7fc76575a0dc92adea5b075641a43120df36a64 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 15:57:15 -0800 Subject: [PATCH 24/53] build: expose the architecture to verify --- .github/workflows/development.yml | 4 ++-- examples/simple-flask/hello.py | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 715c568..b13f90c 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -268,7 +268,7 @@ jobs: start_ts=$(date +%s) until [ x$(curl \ --silent --output /dev/null \ - -w '%{http_code}\n' --fail 'http://localhost:8080/') = x200 ] + -w '%{http_code}\n' --fail 'http://localhost:8080/_health') = x200 ] do sleep 1 if [ "$(expr "${start_ts}" \+ 30)" -lt "$(date +%s)" ]; then @@ -278,7 +278,7 @@ jobs: done rc=0 - printf '## Test (%s)\n\n' $platform | tee -a "$GITHUB_STEP_SUMMARY" + printf '## Test (%s)\n\n' "$(curl 'http://localhost:8080/_arch')" | tee -a "$GITHUB_STEP_SUMMARY" (run_test "$test_command" | tee -a "$GITHUB_STEP_SUMMARY") || rc=$? if [ $rc -ne 0 ]; then exit $rc diff --git a/examples/simple-flask/hello.py b/examples/simple-flask/hello.py index 0d2c13a..6f50a38 100644 --- a/examples/simple-flask/hello.py +++ b/examples/simple-flask/hello.py @@ -1,3 +1,5 @@ +import platform + from flask import Flask app = Flask(__name__) @@ -6,3 +8,13 @@ @app.route("/") def hello_world() -> str: return "

Hello, World!

" + + +@app.route("/_health") +def check_health() -> str: + return "ok" + + +@app.route("/_arch") +def show_config() -> str: + return platform.uname().machine From 507d7e3851debd245a4fb68218561b8ef60c7f2d Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 16:02:14 -0800 Subject: [PATCH 25/53] build: remove from output --- .github/workflows/development.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index b13f90c..05a81ba 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -65,9 +65,9 @@ jobs: fi if ! [ -f /etc/docker/daemon.json ]; then eval $_sudo mkdir -p /etc/docker - echo '{}' | $_sudo tee /etc/docker/daemon.json + echo '{}' | >/dev/null $_sudo tee /etc/docker/daemon.json fi - cat /etc/docker/daemon.json | jq '. | .+{"features": {"containerd-snapshotter": true}}' | eval $_sudo tee /etc/docker/daemon.json + cat /etc/docker/daemon.json | jq '. | .+{"features": {"containerd-snapshotter": true}}' | >/dev/null eval $_sudo tee /etc/docker/daemon.json printf '# Docker config\n\n```\n' cat /etc/docker/daemon.json printf '\n```\n\n' From a9daea15e94dc0520042b8ffbb2dad22d1137ed8 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 16:07:53 -0800 Subject: [PATCH 26/53] fix: add ``-q`` for bytecode compilation --- Dockerfile.alpine | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.alpine b/Dockerfile.alpine index c355e4c..7f4196a 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -40,7 +40,7 @@ RUN set -eu ; \ # remove all ``__pycache__`` directories find /usr/local/lib/python$PYTHON_VERSION -type d -name '__pycache__' -print0 | xargs -0 rm -rf ; \ # compile all py to an adjacent pyc and remove the original, leaving only the bytecode - python -m compileall -b /usr/local/lib/python$PYTHON_VERSION ; \ + python -m compileall -q -b /usr/local/lib/python$PYTHON_VERSION ; \ find -type f -name '*.py' -exec sh -c "remove-py-if-pyc-exists -q {}" \; ;\ # make the new root: mkdir -p \ From 25bc892bb0ee6ea98ccfe6ebad52093158c88f7c Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 16:20:49 -0800 Subject: [PATCH 27/53] feat: optimize setuptools --- Dockerfile.alpine | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 7f4196a..22eb096 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -98,7 +98,7 @@ RUN set -eu ; \ rm -rf $BUILD_ROOT/bin/ln $BUILD_ROOT/bin/busybox $BUILD_ROOT/etc/apk $BUILD_ROOT/var/cache/apk $BUILD_ROOT/usr/share/apk && \ # regenerate the ca-certs! chroot $BUILD_ROOT update-ca-certificates && \ - chroot-pip install --force-reinstall setuptools + chroot-pip --optimize install --force-reinstall setuptools FROM scratch AS distroless-python ARG ALPINE_VERSION=3.20 From 3b78ce971c50a4a6e409b36a6c0b1195d6c43f2c Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 16:21:01 -0800 Subject: [PATCH 28/53] build: expose the rst body --- .github/workflows/development.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 05a81ba..e83aa0d 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -80,8 +80,11 @@ jobs: sudo apt-get -y install skopeo setup_containerd_snapshotter | tee -a "$GITHUB_STEP_SUMMARY" - printf '## Registry info:\n\n' | tee -a "$GITHUB_STEP_SUMMARY" - setup_registry | tee -a "$GITHUB_OUTPUT" + { + printf '## Registry info:\n\n' + setup_registry + } | tee -a "$GITHUB_STEP_SUMMARY" + - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -401,7 +404,6 @@ jobs: uses: docker://pandoc/core:3.8 with: args: >- - -s --wrap=none -t gfm -o README.md @@ -410,3 +412,6 @@ jobs: name: Print out markdown run: | cat README.md >>"$GITHUB_STEP_SUMMARY" + printf '\n\n# README.rst\n\n```\n' >> "$GITHUB_STEP_SUMMARY" + cat README.rst >> "$GITHUB_STEP_SUMMARY" + printf '\n```\n' >> "$GITHUB_STEP_SUMMARY" From f85da5af1e5695fb135790c049fad632aa134236 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 16:21:57 -0800 Subject: [PATCH 29/53] docs: switch to using ``chroot-pip`` for example --- docs/README.rst.tmpl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/README.rst.tmpl b/docs/README.rst.tmpl index 990ba17..27fd798 100644 --- a/docs/README.rst.tmpl +++ b/docs/README.rst.tmpl @@ -92,10 +92,7 @@ Given the following ``Dockerfile``, we will add ``httpie`` to the image and refe #syntax=docker/dockerfile:1 FROM autumnjolitz/distroless-python:3.12-alpine3.20-buildroot AS buildroot - RUN python -m pip install \ - --no-cache \ - --prefix "$BUILD_ROOT/usr/local" \ - httpie + RUN chroot-pip install httpie FROM autumnjolitz/distroless-python:3.12-alpine3.20 COPY --from=buildroot \ From 21703566e3264f53351293b094047a680eded2f8 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 16:23:55 -0800 Subject: [PATCH 30/53] build: debug optimize --- Dockerfile.alpine | 2 +- chroot-pip.sh | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 22eb096..061d509 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -98,7 +98,7 @@ RUN set -eu ; \ rm -rf $BUILD_ROOT/bin/ln $BUILD_ROOT/bin/busybox $BUILD_ROOT/etc/apk $BUILD_ROOT/var/cache/apk $BUILD_ROOT/usr/share/apk && \ # regenerate the ca-certs! chroot $BUILD_ROOT update-ca-certificates && \ - chroot-pip --optimize install --force-reinstall setuptools + CHROOT_PIP_DEBUG=1 chroot-pip --optimize install --force-reinstall setuptools FROM scratch AS distroless-python ARG ALPINE_VERSION=3.20 diff --git a/chroot-pip.sh b/chroot-pip.sh index 8499326..c75f27a 100644 --- a/chroot-pip.sh +++ b/chroot-pip.sh @@ -27,6 +27,7 @@ fini () { if [ "$DEBUG" = 1 ]; then extra= fi + set -x if [ $PIP_OPTIMIZE = '1' ]; then python -m pip freeze >"$AFTER_PACKAGES" From 5f6f55a0859c274db304d24fd03afd785ccfc3e6 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 16:51:16 -0800 Subject: [PATCH 31/53] build: chroot-pip has an optimize command, handles case of no-changes --- Dockerfile.alpine | 2 +- chroot-pip.sh | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 061d509..22eb096 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -98,7 +98,7 @@ RUN set -eu ; \ rm -rf $BUILD_ROOT/bin/ln $BUILD_ROOT/bin/busybox $BUILD_ROOT/etc/apk $BUILD_ROOT/var/cache/apk $BUILD_ROOT/usr/share/apk && \ # regenerate the ca-certs! chroot $BUILD_ROOT update-ca-certificates && \ - CHROOT_PIP_DEBUG=1 chroot-pip --optimize install --force-reinstall setuptools + chroot-pip --optimize install --force-reinstall setuptools FROM scratch AS distroless-python ARG ALPINE_VERSION=3.20 diff --git a/chroot-pip.sh b/chroot-pip.sh index c75f27a..7122d84 100644 --- a/chroot-pip.sh +++ b/chroot-pip.sh @@ -22,16 +22,18 @@ setup () { } fini () { + local rv= local rc=$? local extra=-q + local new_packages= if [ "$DEBUG" = 1 ]; then extra= fi - set -x if [ $PIP_OPTIMIZE = '1' ]; then python -m pip freeze >"$AFTER_PACKAGES" - new_packages="$(diff -Naur "$BEFORE_PACKAGES" "$AFTER_PACKAGES" | grep -vE '^\+\+' | grep -E '^\+' | cut -f2 -d+ | cut -f1 -d= | xargs)" + rv=0 + new_packages="$(diff -Naur "$BEFORE_PACKAGES" "$AFTER_PACKAGES" | grep -vE '^\+\+' | grep -E '^\+' | cut -f2 -d+ | cut -f1 -d= | xargs)" || rv=$? if [ "x$new_packages" != 'x' ]; then if [ "$DEBUG" = '1' ]; then >&2 echo "Optimizing packages (${new_packages})..." @@ -63,9 +65,41 @@ trap fini EXIT setup export PYTHONPATH="${BUILD_ROOT}/usr/local/lib/python${PYTHON_VERSION}/site-packages" export PIP_PREFIX="${BUILD_ROOT}/usr/local" + +case "$1" in + optimize) + PIP_OPTIMIZE='1' + ;; +esac + if [ $PIP_OPTIMIZE = '1' ]; then BEFORE_PACKAGES=$(mktemp) AFTER_PACKAGES=$(mktemp) python -m pip freeze >"$BEFORE_PACKAGES" fi + +case "$@" in + *'--force-reinstall'*|optimize*) + for package_name in $@ + do + if case "$package_name" in '-'*) false ;; *) true ;; esac ; then + if >/dev/null pip show "${package_name}"; then + sed -i'' '/^'"${package_name}"'==/d' $BEFORE_PACKAGES + fi + fi + done + ;; +esac + +if [ $1 = optimize ]; then + shift + if [ x"$@" = x ]; then + >&2 echo 'optimize [PACKAGE] [PACKAGE2] ... [PACKAGEN] +pass in package names to run optimize on +' + exit 1 + fi + exit +fi + python -m pip $@ From 5f7f5df74d4f918b8d97782689819b9f5374e959 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 17:00:14 -0800 Subject: [PATCH 32/53] fix: use ``chroot-exec`` helper --- Dockerfile.alpine | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 22eb096..0dd4d9c 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -97,7 +97,7 @@ RUN set -eu ; \ tar -C "$BUILD_ROOT" -cpf - etc/apk bin/ln bin/busybox var/cache/apk usr/share/apk | tar -C "$CACHE_ROOT" -xpf - ; \ rm -rf $BUILD_ROOT/bin/ln $BUILD_ROOT/bin/busybox $BUILD_ROOT/etc/apk $BUILD_ROOT/var/cache/apk $BUILD_ROOT/usr/share/apk && \ # regenerate the ca-certs! - chroot $BUILD_ROOT update-ca-certificates && \ + chroot-exec update-ca-certificates && \ chroot-pip --optimize install --force-reinstall setuptools FROM scratch AS distroless-python From fb0035b95165db7764526f5233c3c1849fcbe73a Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 17:07:04 -0800 Subject: [PATCH 33/53] build: add pip cache --- Dockerfile.alpine | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 0dd4d9c..975d4eb 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -25,7 +25,9 @@ ADD --chmod=0755 chroot-pip.sh /usr/local/bin/chroot-pip ADD --chmod=0755 chroot-ln.sh /usr/local/bin/chroot-ln ADD --chmod=0755 remove-py-if-pyc-exists.sh /usr/local/bin/remove-py-if-pyc-exists ADD --chmod=0755 chroot-exec.sh /usr/local/bin/chroot-exec -RUN set -eu ; \ +RUN \ + --mount=type=cache,id=pip-cache-${TARGETARCH}${TARGETVARIANT},sharing=shared,target=/root/.cache/pip \ + set -eu ; \ python -m pip install -U pip setuptools ; \ # Add to buildroot: $_sys_apk_add \ From c6903408a3f4df7766943c3af40bd7efd79184bf Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 17:08:13 -0800 Subject: [PATCH 34/53] build: add missing field back --- .github/workflows/development.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index e83aa0d..6c1011f 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -82,7 +82,7 @@ jobs: setup_containerd_snapshotter | tee -a "$GITHUB_STEP_SUMMARY" { printf '## Registry info:\n\n' - setup_registry + setup_registry | tee -a "$GITHUB_OUTPUT" } | tee -a "$GITHUB_STEP_SUMMARY" - From df3dac42c82acbbd13e6949c7080fecb3248e518 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 17:27:19 -0800 Subject: [PATCH 35/53] fix: add missing ``ARG`` declarations --- Dockerfile.alpine | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 975d4eb..8cb26c7 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -7,6 +7,8 @@ ARG BASE_IMAGE_DIGEST FROM ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} AS buildroot ARG PYTHON_VERSION=3.12 +ARG TARGETARCH +ARG TARGETVARIANT ARG BUILD_ROOT='/dest' ARG CACHE_ROOT='/cache' From b6fd48bd59e124b55625e56508dbd187c5878373 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 17:33:07 -0800 Subject: [PATCH 36/53] build: add test to main --- .github/workflows/main.yml | 162 ++++++++++++++++++++++++++++++------- 1 file changed, 132 insertions(+), 30 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 434288f..88f772f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,11 +16,6 @@ env: jobs: build_images: name: "Create runtime and buildroot OCI Images" - services: - registry: - image: registry:3 - ports: - - 5000:5000 strategy: fail-fast: true matrix: @@ -53,9 +48,42 @@ jobs: name: Checkout uses: actions/checkout@v4 - + id: setup name: Setup run: | + setup_registry () { + registry_id=$(docker run \ + -p5000:5000 \ + --name registry \ + --rm -d registry:3) + echo 'REGISTRY_CONTAINER_ID='$registry_id + } + setup_containerd_snapshotter () { + local _sudo=sudo + if [ $(id -u) -eq 0 ]; then + _sudo=sudo + fi + if ! [ -f /etc/docker/daemon.json ]; then + eval $_sudo mkdir -p /etc/docker + echo '{}' | >/dev/null $_sudo tee /etc/docker/daemon.json + fi + cat /etc/docker/daemon.json | jq '. | .+{"features": {"containerd-snapshotter": true}}' | >/dev/null eval $_sudo tee /etc/docker/daemon.json + printf '# Docker config\n\n```\n' + cat /etc/docker/daemon.json + printf '\n```\n\n' + eval $_sudo systemctl restart docker + sleep 1 + printf '# Docker Info\n\n```' + docker info + printf '\n```\n' + } + sudo apt-get -y install skopeo + setup_containerd_snapshotter | tee -a "$GITHUB_STEP_SUMMARY" + { + printf '## Registry info:\n\n' + setup_registry | tee -a "$GITHUB_OUTPUT" + } | tee -a "$GITHUB_STEP_SUMMARY" - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -64,12 +92,6 @@ jobs: uses: docker/setup-buildx-action@v3 with: driver-opts: network=host - daemon-config: | - { - "features": { - "containerd-snapshotter": true - } - } - id: image_env @@ -80,20 +102,19 @@ jobs: '${{ github.repository_owner }}' \ 'localhost:5000' - docker pull "${SOURCE_IMAGE}" || true - echo "IMAGE_HOME=$(mktemp -d)" >> "$GITHUB_OUTPUT" - - echo ALPINE_VERSION="${ALPINE_VERSION}" >> "$GITHUB_OUTPUT" - echo PYTHON_VERSION="${PYTHON_VERSION}" >> "$GITHUB_OUTPUT" - echo SOURCE_IMAGE="${SOURCE_IMAGE}" >> "$GITHUB_OUTPUT" - echo IMAGE_TAG="${IMAGE_TAG}" >> "$GITHUB_OUTPUT" - echo IMAGE_BUILDROOT_TAG="${IMAGE_TAG}-buildroot" >> "$GITHUB_OUTPUT" - echo IMAGE_TAG_SAFE="$(echo "$IMAGE_TAG" | base64 -w 0 )" >> "$GITHUB_OUTPUT" - echo IMAGE_BUILDROOT_TAG_SAFE="$(echo "${IMAGE_TAG}-buildroot" | base64 -w 0 )" >> "$GITHUB_OUTPUT" - echo REPOSITORY="${REPOSITORY}" >> "$GITHUB_OUTPUT" - echo REPOSITORY_SAFE="$(echo "${REPOSITORY}" | base64 -w 0 )" >> "$GITHUB_OUTPUT" - echo BASE_IMAGE_DIGEST="$(digest_of "$SOURCE_IMAGE")" >> "$GITHUB_OUTPUT" - echo 'IMAGE_DESCRIPTION=${{ github.event.repository.description }}. See ${{ github.server_url }}/${{ github.repository }} for more info.' >> "$GITHUB_OUTPUT" + docker pull -q "${SOURCE_IMAGE}" || true + echo "IMAGE_HOME=$(mktemp -d)" | tee -a "$GITHUB_OUTPUT" + echo ALPINE_VERSION="${ALPINE_VERSION}" | tee -a "$GITHUB_OUTPUT" + echo PYTHON_VERSION="${PYTHON_VERSION}" | tee -a "$GITHUB_OUTPUT" + echo SOURCE_IMAGE="${SOURCE_IMAGE}" | tee -a "$GITHUB_OUTPUT" + echo IMAGE_TAG="${IMAGE_TAG}" | tee -a "$GITHUB_OUTPUT" + echo IMAGE_BUILDROOT_TAG="${IMAGE_TAG}-buildroot" | tee -a "$GITHUB_OUTPUT" + echo IMAGE_TAG_SAFE="$(echo "$IMAGE_TAG" | base64 -w 0 )" | tee -a "$GITHUB_OUTPUT" + echo IMAGE_BUILDROOT_TAG_SAFE="$(echo "${IMAGE_TAG}-buildroot" | base64 -w 0 )" | tee -a "$GITHUB_OUTPUT" + echo REPOSITORY="${REPOSITORY}" | tee -a "$GITHUB_OUTPUT" + echo REPOSITORY_SAFE="$(echo "${REPOSITORY}" | base64 -w 0 )" | tee -a "$GITHUB_OUTPUT" + echo BASE_IMAGE_DIGEST="$(digest_of "$SOURCE_IMAGE")" | tee -a "$GITHUB_OUTPUT" + echo 'IMAGE_DESCRIPTION=${{ github.event.repository.description }}. See ${{ github.server_url }}/${{ github.repository }} for more info.' | tee -a "$GITHUB_OUTPUT" - name: Create Buildroot @@ -185,9 +206,90 @@ jobs: SOURCE_IMAGE=${{ steps.image_env.outputs.IMAGE_TAG }} tags: "${{ steps.image_env.outputs.IMAGE_TAG }}-example1-amd64" outputs: type=oci,dest=${{ steps.image_env.outputs.IMAGE_HOME }}/example1-${{ steps.image_env.outputs.IMAGE_TAG_SAFE }}.tar + - + id: test + name: Test Example + env: + IMAGE_URI: '${{ steps.image_env.outputs.EXAMPLE_IMAGE_TAG }}' + IMAGE_DIGEST: '${{ steps.build_test.outputs.digest }}' + test_command: curl -sLv 'http://localhost:8080/' + run: | + set -o pipefail + + run_test () { + local rv=0 + local T="$(mktemp)" + trap 'rm -f '"$T" RETURN + >"$T" 2>&1 eval "$@" || rv=$? + if [ $rv -eq 0 ] && case "$(tail -1 $T)" in '

Hello, World!

') false ;; *) true ;; esac ; then + >&2 echo 'failed! got "'"$(tail -1 $T)"'" !' + rv=1 + fi + printf '`%s` (returned code: ' "$@" + printf '%s)\n```\n' "$rv" + cat "$T" + printf '\n```\n\n' + return "$rv" + } + + rc=0 + REMOTE_DIGEST="$(skopeo inspect --tls-verify=false --raw docker://"$IMAGE_URI" | skopeo manifest-digest /dev/stdin)" + if [ "$REMOTE_DIGEST" != "${IMAGE_DIGEST}" ]; then + echo 'generated a diferent manifest than '"${IMAGE_DIGEST}"': '"$REMOTE_DIGEST" + exit 2 + fi + docker pull -q "$(echo "$IMAGE_URI" | rev | cut -d: -f2- | rev )@${IMAGE_DIGEST}" + for platform in $(echo $TARGET_PLATFORMS | tr , '\n') + do + docker pull --platform $platform -q "$IMAGE_URI" + >&2 printf 'pulled %s (%s)\n' "$IMAGE_TAG" "$platform" + done + { + printf '## images\n\n```' + docker images --tree $IMAGE_URI | sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2};?)?)?[mGK]//g" + printf '\n```\n' + } | tee -a "$GITHUB_STEP_SUMMARY" + + for platform in $(echo $TARGET_PLATFORMS | tr , '\n') + do + rc=0 + container_id="$(docker run \ + --platform $platform \ + --detach -p 8080:8080 \ + --rm $IMAGE_URI)" || rc=$? + trap 'docker stop $container_id' EXIT + if [ $rc -ne 0 ]; then + printf 'docker run failed (return code '$rc')!\n' | >&2 tee \ + -a "$GITHUB_OUTPUT" + exit $rc + fi + start_ts=$(date +%s) + until [ x$(curl \ + --silent --output /dev/null \ + -w '%{http_code}\n' --fail 'http://localhost:8080/_health') = x200 ] + do + sleep 1 + if [ "$(expr "${start_ts}" \+ 30)" -lt "$(date +%s)" ]; then + >&2 echo 'failed to get a good response in 30 seconds!' + break + fi + done + + rc=0 + printf '## Test (%s)\n\n' "$(curl 'http://localhost:8080/_arch')" | tee -a "$GITHUB_STEP_SUMMARY" + (run_test "$test_command" | tee -a "$GITHUB_STEP_SUMMARY") || rc=$? + if [ $rc -ne 0 ]; then + exit $rc + fi + trap -- EXIT + docker stop $container_id + done + - id: post_build run: | + docker stop '${{ steps.setup.outputs.REGISTRY_CONTAINER_ID }}' + IMAGE_DIGEST='${{steps.runtime.outputs.digest}}' IMAGE_BUILDROOT_DIGEST='${{steps.buildroot.outputs.digest}}' echo 'IMAGE_DIGEST='$IMAGE_DIGEST >> "GITHUB_OUTPUT" @@ -509,12 +611,12 @@ jobs: -o README.md README.rst - - name: "Print out markdown" + name: Print out markdown run: | - { - printf '# README.md\n\n' - cat README.md - } | tee -a "$GITHUB_STEP_SUMMARY" + cat README.md >>"$GITHUB_STEP_SUMMARY" + printf '\n\n# README.rst\n\n```\n' >> "$GITHUB_STEP_SUMMARY" + cat README.rst >> "$GITHUB_STEP_SUMMARY" + printf '\n```\n' >> "$GITHUB_STEP_SUMMARY" - name: Update Dockerhub description uses: peter-evans/dockerhub-description@e98e4d1628a5f3be2be7c231e50981aee98723ae # v4.0.0 From c4f62a980345f54e16d13a81f9584e7325ecd0b2 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 17:35:54 -0800 Subject: [PATCH 37/53] build: add pip cache --- .github/workflows/development.yml | 1 + .github/workflows/main.yml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 6c1011f..31955f1 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -337,6 +337,7 @@ jobs: - uses: actions/setup-python@v6 with: python-version: '3.12' + cache: 'pip' - name: install dependencies run: | diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 88f772f..0b718bb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -364,6 +364,7 @@ jobs: uses: actions/setup-python@v6 with: python-version: '3.12' + cache: 'pip' - name: install dependencies run: | @@ -561,6 +562,7 @@ jobs: uses: actions/setup-python@v6 with: python-version: '3.12' + cache: 'pip' - name: install dependencies run: | From 0b5d2dbd4a36e777155ec5b25ba311f98e9ae5bd Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 17:41:56 -0800 Subject: [PATCH 38/53] feat: add alpine3.23 --- .github/workflows/development.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 31955f1..45a52e8 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -31,6 +31,7 @@ jobs: - '3.20' - '3.21' - '3.22' + - '3.23' os: - 'ubuntu-latest' exclude: @@ -39,8 +40,16 @@ jobs: alpine: '3.21' - python: '3.8' alpine: '3.22' + - python: '3.8' + alpine: '3.23' + + - python: '3.9' + alpine: '3.23' + - python: '3.14' alpine: '3.20' + - python: '3.15' + alpine: '3.20' runs-on: ${{ matrix.os }} steps: From f6c3a134c9a2a828aacdf1ebb1c6aeec269e7cc9 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 17:51:18 -0800 Subject: [PATCH 39/53] fix: broken build --- .github/workflows/development.yml | 1 + Dockerfile.alpine | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 45a52e8..11a83c2 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -61,6 +61,7 @@ jobs: name: Setup run: | setup_registry () { + >/dev/null docker pull -q docker.io/library/registry:3 registry_id=$(docker run \ -p5000:5000 \ --name registry \ diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 8cb26c7..e3c2f04 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -62,9 +62,11 @@ RUN \ alpine-release \ musl \ libffi \ + coreutils-env \ + ; \ + $_apk_add --no-scripts \ dash \ dash-binsh \ - coreutils-env \ ; \ $_apk_add \ ca-certificates \ From 99ace781b2912cd261abac66e521e46f367995c4 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Thu, 4 Dec 2025 18:11:30 -0800 Subject: [PATCH 40/53] build: make /dev/fd in chroot --- .github/workflows/development.yml | 4 ++ .github/workflows/main.yml | 19 +++++++++ Dockerfile.alpine | 64 ++++++++++++++++++++----------- chroot-apk.sh | 13 +++++++ chroot-exec.sh | 1 + chroot-ln.sh | 1 + 6 files changed, 79 insertions(+), 23 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 11a83c2..0445b2c 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -104,6 +104,8 @@ jobs: with: platforms: ${{ env.TARGET_PLATFORMS }} driver-opts: network=host + buildkitd-flags: '--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host' + - id: image_env run: | @@ -137,6 +139,7 @@ jobs: DOCKER_BUILD_SUMMARY: false DOCKER_BUILD_RECORD_UPLOAD: false with: + allow: network.host,security.insecure # this is for the bind mount push: true platforms: ${{ env.TARGET_PLATFORMS }} context: "." @@ -168,6 +171,7 @@ jobs: DOCKER_BUILD_SUMMARY: false DOCKER_BUILD_RECORD_UPLOAD: false with: + allow: network.host,security.insecure # due to using a file that has a sec contxt push: true context: "." platforms: ${{ env.TARGET_PLATFORMS }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0b718bb..7fb44e9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,6 +31,7 @@ jobs: - '3.20' - '3.21' - '3.22' + - '3.23' os: - 'ubuntu-latest' exclude: @@ -39,8 +40,16 @@ jobs: alpine: '3.21' - python: '3.8' alpine: '3.22' + - python: '3.8' + alpine: '3.23' + + - python: '3.9' + alpine: '3.23' + - python: '3.14' alpine: '3.20' + - python: '3.15' + alpine: '3.20' runs-on: ${{ matrix.os }} steps: @@ -341,6 +350,7 @@ jobs: - '3.20' - '3.21' - '3.22' + - '3.23' os: - 'ubuntu-latest' exclude: @@ -349,8 +359,17 @@ jobs: alpine: '3.21' - python: '3.8' alpine: '3.22' + - python: '3.8' + alpine: '3.23' + + - python: '3.9' + alpine: '3.23' + - python: '3.14' alpine: '3.20' + - python: '3.15' + alpine: '3.20' + permissions: packages: write diff --git a/Dockerfile.alpine b/Dockerfile.alpine index e3c2f04..1c1c1ab 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -1,4 +1,5 @@ -#syntax=docker/dockerfile:1 +# syntax=docker/dockerfile:1.20-labs + ARG ALPINE_VERSION=3.20 ARG PYTHON_VERSION=3.12 @@ -19,7 +20,7 @@ ENV BUILD_ROOT=$BUILD_ROOT \ _apk_add="/usr/bin/env apk add --root $BUILD_ROOT --no-cache" \ _apk_del="/usr/bin/env apk del --root $BUILD_ROOT --purge" \ _sh="chroot $BUILD_ROOT sh" \ - _ln="chroot $BUILD_ROOT ln" \ + _ln="chroot $BUILD_ROOT /bin/ln" \ _chroot="chroot $BUILD_ROOT" ADD --chmod=0755 chroot-apk.sh /usr/local/bin/chroot-apk @@ -28,10 +29,7 @@ ADD --chmod=0755 chroot-ln.sh /usr/local/bin/chroot-ln ADD --chmod=0755 remove-py-if-pyc-exists.sh /usr/local/bin/remove-py-if-pyc-exists ADD --chmod=0755 chroot-exec.sh /usr/local/bin/chroot-exec RUN \ - --mount=type=cache,id=pip-cache-${TARGETARCH}${TARGETVARIANT},sharing=shared,target=/root/.cache/pip \ set -eu ; \ - python -m pip install -U pip setuptools ; \ - # Add to buildroot: $_sys_apk_add \ dash \ # TLS certs @@ -41,11 +39,6 @@ RUN \ # be imported from. This makes the stdlib immutable. zip \ ; \ - # remove all ``__pycache__`` directories - find /usr/local/lib/python$PYTHON_VERSION -type d -name '__pycache__' -print0 | xargs -0 rm -rf ; \ - # compile all py to an adjacent pyc and remove the original, leaving only the bytecode - python -m compileall -q -b /usr/local/lib/python$PYTHON_VERSION ; \ - find -type f -name '*.py' -exec sh -c "remove-py-if-pyc-exists -q {}" \; ;\ # make the new root: mkdir -p \ $CACHE_ROOT/ \ @@ -53,7 +46,9 @@ RUN \ $BUILD_ROOT/bin \ $BUILD_ROOT/usr/local/lib/python$PYTHON_VERSION/site-packages \ $BUILD_ROOT/usr/local/bin \ + $BUILD_ROOT/proc \ ; \ + cp -R /dev $BUILD_ROOT/dev ; \ # copy the apk related confs cp -R /etc/apk $BUILD_ROOT/etc/apk ; \ $_apk_add --initdb ; \ @@ -62,22 +57,46 @@ RUN \ alpine-release \ musl \ libffi \ - coreutils-env \ ; \ - $_apk_add --no-scripts \ + cp -p /bin/busybox $BUILD_ROOT/bin/busybox ; \ + chroot $BUILD_ROOT /bin/busybox busybox ln -sf /bin/busybox /bin/ln + +RUN --security=insecure \ + set -eu ; \ + mount --bind /proc /$BUILD_ROOT/proc ; \ + $_apk_add \ + busybox \ dash \ dash-binsh \ - ; \ - $_apk_add \ + ; \ + T=$(mktemp -d) ; \ + if [ -f $BUILD_ROOT/lib/apk/db/scripts.tar.gz ]; then \ + tar -C "$T" -xzpf $BUILD_ROOT/lib/apk/db/scripts.tar.gz ; \ + rm -f $BUILD_ROOT/lib/apk/db/scripts.tar.gz ; \ + find "$T" -name 'busybox-*' -delete ; \ + tar -C "$T" -cpvzf $BUILD_ROOT/lib/apk/db/scripts.tar.gz . ; \ + rm -rf "$T" ; \ + fi ; \ + tar -C "$BUILD_ROOT" -cpf - etc/apk bin/ln bin/busybox var/cache/apk usr/share/apk | tar -C "$CACHE_ROOT" -xpf - ; \ + rm -rf $BUILD_ROOT/bin/ln $BUILD_ROOT/bin/busybox $BUILD_ROOT/etc/apk $BUILD_ROOT/var/cache/apk $BUILD_ROOT/usr/share/apk && \ + chroot-apk add \ ca-certificates \ # needed for update-ca-certificates to work: - run-parts \ - # install the runtime dependencies for python + run-parts + +RUN \ + --mount=type=cache,id=pip-cache-${TARGETARCH}${TARGETVARIANT},sharing=shared,target=/root/.cache/pip \ + set -eu ; \ + chroot-apk add \ + coreutils-env \ $(apk info -R .python-rundeps | grep -vE ':$') \ ; \ - cp -p /bin/busybox $BUILD_ROOT/bin/busybox ; \ - ls -lt $BUILD_ROOT/bin/busybox ; \ - chroot $BUILD_ROOT /bin/busybox ln -sf /bin/busybox /bin/ln ; \ + python -m pip install -U pip setuptools ; \ + # remove all ``__pycache__`` directories + find /usr/local/lib/python$PYTHON_VERSION -type d -name '__pycache__' -print0 | xargs -0 rm -rf ; \ + # compile all py to an adjacent pyc and remove the original, leaving only the bytecode + python -m compileall -q -b /usr/local/lib/python$PYTHON_VERSION ; \ + find -type f -name '*.py' -exec sh -c "remove-py-if-pyc-exists -q {}" \; ;\ # copy dash into the container so we can use it as the default bin/sh # tar -C / -cpf - $(\ # apk info -L \ @@ -98,10 +117,8 @@ RUN \ touch $BUILD_ROOT/usr/local/lib/python$PYTHON_VERSION/ensurepip.py ; \ rm $BUILD_ROOT/usr/local/lib/python$PYTHON_VERSION/lib-dynload/_tkinter* ; \ ) && \ - $_ln -sf /usr/local/bin/python$PYTHON_VERSION /usr/local/bin/python3 && \ - $_ln -sf /usr/local/bin/python$PYTHON_VERSION /usr/local/bin/python && \ - tar -C "$BUILD_ROOT" -cpf - etc/apk bin/ln bin/busybox var/cache/apk usr/share/apk | tar -C "$CACHE_ROOT" -xpf - ; \ - rm -rf $BUILD_ROOT/bin/ln $BUILD_ROOT/bin/busybox $BUILD_ROOT/etc/apk $BUILD_ROOT/var/cache/apk $BUILD_ROOT/usr/share/apk && \ + chroot-ln -sf /usr/local/bin/python$PYTHON_VERSION /usr/local/bin/python3 && \ + chroot-ln -sf /usr/local/bin/python$PYTHON_VERSION /usr/local/bin/python && \ # regenerate the ca-certs! chroot-exec update-ca-certificates && \ chroot-pip --optimize install --force-reinstall setuptools @@ -115,6 +132,7 @@ ARG BUILD_ROOT='/dest' ENV BUILD_ROOT=$BUILD_ROOT \ PYTHON_VERSION=$PYTHON_VERSION \ ALPINE_VERSION=$ALPINE_VERSION +COPY --from=buildroot /dev /dev COPY --from=buildroot $BUILD_ROOT / LABEL \ org.opencontainers.image.authors="distroless-python image developers " \ diff --git a/chroot-apk.sh b/chroot-apk.sh index abbc1e8..c34ebe5 100755 --- a/chroot-apk.sh +++ b/chroot-apk.sh @@ -27,6 +27,19 @@ fini () { >&2 echo "Removing APK data from $BUILD_ROOT, storing in $CACHE_ROOT" extra='-v' fi + local T="$(mktemp -d)" + if [ -f $BUILD_ROOT/lib/apk/db/scripts.tar.gz ]; then + tar -C "$T" -xzpf $BUILD_ROOT/lib/apk/db/scripts.tar.gz + rm -f $BUILD_ROOT/lib/apk/db/scripts.tar.gz + sed -i'' 's|^#!busybox sh|#!/usr/bin/dash|g' $(find "$T" -type f -print) + sed -i'' 's|^#!/bin/sh|#!/usr/bin/dash|g' $(find "$T" -type f -print) + sed -i'' 's|^#!/bin/busybox sh|#!/usr/bin/dash|g' $(find "$T" -type f -print) + cat $(find "$T" -type f -print) + tar -C "$T" -cpvzf $BUILD_ROOT/lib/apk/db/scripts.tar.gz . + rm -rf "$T" + fi + + mkdir -p $BUILD_ROOT/var/cache/apk tar -C "$BUILD_ROOT" -cpf - etc/apk bin/ln bin/busybox var/cache/apk usr/share/apk | eval tar -C "$CACHE_ROOT" -xpf $extra - $_chroot /bin/ln -sf /usr/bin/dash /bin/sh.bak rm -rf $BUILD_ROOT/bin/ln $BUILD_ROOT/bin/busybox $BUILD_ROOT/etc/apk $BUILD_ROOT/var/cache/apk $BUILD_ROOT/usr/share/apk diff --git a/chroot-exec.sh b/chroot-exec.sh index 084a2dd..84095d3 100755 --- a/chroot-exec.sh +++ b/chroot-exec.sh @@ -27,6 +27,7 @@ fini () { >&2 echo "Removing APK data from $BUILD_ROOT, storing in $CACHE_ROOT" extra=-v fi + mkdir -p $BUILD_ROOT/var/cache/apk tar -C "$BUILD_ROOT" -cpf - etc/apk bin/ln bin/busybox var/cache/apk usr/share/apk | eval tar -C "$CACHE_ROOT" $extra -xpf - $_chroot /bin/ln -sf /usr/bin/dash /bin/sh.bak rm -rf $BUILD_ROOT/bin/ln $BUILD_ROOT/bin/busybox $BUILD_ROOT/etc/apk $BUILD_ROOT/var/cache/apk $BUILD_ROOT/usr/share/apk diff --git a/chroot-ln.sh b/chroot-ln.sh index bc5ca17..4d00e86 100644 --- a/chroot-ln.sh +++ b/chroot-ln.sh @@ -27,6 +27,7 @@ fini () { >&2 echo "Removing APK data from $BUILD_ROOT, storing in $CACHE_ROOT" extra=-v fi + mkdir -p $BUILD_ROOT/var/cache/apk tar -C "$BUILD_ROOT" -cpf - etc/apk bin/ln bin/busybox var/cache/apk usr/share/apk | eval tar -C "$CACHE_ROOT" $extra -xpf - $_chroot /bin/ln -sf /usr/bin/dash /bin/sh.bak rm -rf $BUILD_ROOT/bin/ln $BUILD_ROOT/bin/busybox $BUILD_ROOT/etc/apk $BUILD_ROOT/var/cache/apk $BUILD_ROOT/usr/share/apk From 07fa2f365a2273834ad18672c6485edd4c316ccd Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Fri, 5 Dec 2025 14:55:07 -0800 Subject: [PATCH 41/53] build: reduce output --- .github/workflows/development.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 0445b2c..d3abc0f 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -60,6 +60,11 @@ jobs: id: setup name: Setup run: | + DEBUG_OUTPUT=/dev/null + if [ x"${DEBUG_DOCKERD:-}" = x1 ]; then + DEBUG_OUTPUT="$GITHUB_STEP_SUMMARY" + fi + setup_registry () { >/dev/null docker pull -q docker.io/library/registry:3 registry_id=$(docker run \ @@ -89,11 +94,11 @@ jobs: } sudo apt-get -y install skopeo - setup_containerd_snapshotter | tee -a "$GITHUB_STEP_SUMMARY" + setup_containerd_snapshotter | tee -a "$DEBUG_OUTPUT" { printf '## Registry info:\n\n' setup_registry | tee -a "$GITHUB_OUTPUT" - } | tee -a "$GITHUB_STEP_SUMMARY" + } | tee -a "$DEBUG_OUTPUT" - name: Set up QEMU @@ -264,7 +269,8 @@ jobs: >&2 printf 'pulled %s (%s)\n' "$IMAGE_TAG" "$platform" done { - printf '## images\n\n```' + printf '# Test\n\n' + printf '## Source Images\n\n```' docker images --tree $IMAGE_URI | sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2};?)?)?[mGK]//g" printf '\n```\n' } | tee -a "$GITHUB_STEP_SUMMARY" From 2136a9542291da3a9dc657ccce28893621e780fe Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Fri, 5 Dec 2025 16:00:13 -0800 Subject: [PATCH 42/53] feat: chroot-pip autosets SOURCE_DATE_EPOCH for deterministic pyc creation --- chroot-pip.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/chroot-pip.sh b/chroot-pip.sh index 7122d84..a73a5f4 100644 --- a/chroot-pip.sh +++ b/chroot-pip.sh @@ -7,6 +7,14 @@ fi DEBUG="${CHROOT_PIP_DEBUG:-0}" +if [ "x${SOURCE_DATE_EPOCH:-}" = x ]; then + SOURCE_DATE_EPOCH=0 +elif [ "x${SOURCE_DATE_EPOCH:-}" = x- ]; then + # ARJ: if the SOURCE_DATE_EPOCH is '-' then it + # will have the effect of clearing the default SOURCE_DATE_EPOCH + SOURCE_DATE_EPOCH= +fi + set -e set -o pipefail From 9015f0d1e203417b6ad7d871329e45c3fe629891 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Fri, 5 Dec 2025 16:15:47 -0800 Subject: [PATCH 43/53] feat: split out docker files to reduce privileged docker runs --- .github/workflows/development.yml | 36 ++++++++-- .github/workflows/main.yml | 65 ++++++++++++++----- build.sh | 17 ++++- .../Dockerfile.alpine | 25 ------- runtime/Dockerfile.alpine | 32 +++++++++ 5 files changed, 127 insertions(+), 48 deletions(-) rename Dockerfile.alpine => buildroot/Dockerfile.alpine (80%) create mode 100644 runtime/Dockerfile.alpine diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index d3abc0f..be24450 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -131,6 +131,7 @@ jobs: echo IMAGE_TAG_SAFE="$(echo "$IMAGE_TAG" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT" echo IMAGE_BUILDROOT_TAG_SAFE="$(echo "${IMAGE_TAG}-buildroot" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT" echo 'EXAMPLE_IMAGE_TAG='"${IMAGE_TAG}-example1" | tee -a "$GITHUB_OUTPUT" + echo EXAMPLE_IMAGE_TAG_SAFE="$(echo "${EXAMPLE_IMAGE_TAG}" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT" echo REPOSITORY="${REPOSITORY}"| tee -a "$GITHUB_OUTPUT" echo REPOSITORY_SAFE="$(echo "${REPOSITORY}" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT" echo BASE_IMAGE_DIGEST="$(digest_of "$SOURCE_IMAGE")"| tee -a "$GITHUB_OUTPUT" @@ -148,7 +149,7 @@ jobs: push: true platforms: ${{ env.TARGET_PLATFORMS }} context: "." - file: Dockerfile.alpine + file: buildroot/Dockerfile.alpine sbom: true provenance: mode=max target: buildroot @@ -165,7 +166,6 @@ jobs: SOURCE_IMAGE=${{ steps.image_env.outputs.SOURCE_IMAGE }} BUILD_ROOT=/d tags: "${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot" - outputs: type=oci,dest=${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.IMAGE_BUILDROOT_TAG_SAFE }}.tar - name: Create Image @@ -176,11 +176,10 @@ jobs: DOCKER_BUILD_SUMMARY: false DOCKER_BUILD_RECORD_UPLOAD: false with: - allow: network.host,security.insecure # due to using a file that has a sec contxt push: true context: "." platforms: ${{ env.TARGET_PLATFORMS }} - file: Dockerfile.alpine + file: runtime/Dockerfile.alpine cache-to: | type=gha,mode=max cache-from: | @@ -193,9 +192,9 @@ jobs: BASE_IMAGE_DIGEST=${{ steps.image_env.outputs.BASE_IMAGE_DIGEST }} PYTHON_VERSION=${{ steps.image_env.outputs.PYTHON_VERSION }} SOURCE_IMAGE=${{ steps.image_env.outputs.SOURCE_IMAGE }} + BUILDROOT_IMAGE=${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot@${{ steps.buildroot.outputs.digest }} BUILD_ROOT=/d tags: "${{ steps.image_env.outputs.IMAGE_TAG }}" - outputs: type=oci,dest=${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.IMAGE_TAG_SAFE }}.tar labels: ${{steps.image_env.outputs.IMAGE_LABELS}} sbom: true provenance: mode=max @@ -229,7 +228,6 @@ jobs: build-args: | SOURCE_IMAGE=${{ steps.image_env.outputs.IMAGE_TAG }} tags: "${{ steps.image_env.outputs.IMAGE_TAG }}-example1" - outputs: type=oci,dest=${{ steps.image_env.outputs.IMAGE_HOME }}/example1-${{ steps.image_env.outputs.IMAGE_TAG_SAFE }}.tar - id: test name: Test Example @@ -313,7 +311,33 @@ jobs: - id: post_build + name: 'download multiplatform images' run: | + skopeo copy \ + --src-no-creds \ + --src-tls-verify=false \ + --quiet \ + --multi-arch all \ + docker://${{ steps.image_env.outputs.IMAGE_TAG }} \ + oci-archive://${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.IMAGE_TAG_SAFE }}.tar \ + & + skopeo copy \ + --src-no-creds \ + --src-tls-verify=false \ + --quiet \ + --multi-arch all \ + docker://${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot \ + oci-archive://${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.IMAGE_BUILDROOT_TAG_SAFE }}.tar \ + & + skopeo copy \ + --src-no-creds \ + --src-tls-verify=false \ + --quiet \ + --multi-arch all \ + docker://${{ steps.image_env.outputs.EXAMPLE_IMAGE_TAG }} \ + oci-archive://${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.EXAMPLE_IMAGE_TAG_SAFE }}.tar \ + & + wait docker stop '${{ steps.setup.outputs.REGISTRY_CONTAINER_ID }}' IMAGE_DIGEST='${{steps.runtime.outputs.digest}}' diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7fb44e9..e9ccb99 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -60,6 +60,11 @@ jobs: id: setup name: Setup run: | + DEBUG_OUTPUT=/dev/null + if [ x"${DEBUG_DOCKERD:-}" = x1 ]; then + DEBUG_OUTPUT="$GITHUB_STEP_SUMMARY" + fi + setup_registry () { registry_id=$(docker run \ -p5000:5000 \ @@ -88,11 +93,11 @@ jobs: } sudo apt-get -y install skopeo - setup_containerd_snapshotter | tee -a "$GITHUB_STEP_SUMMARY" + setup_containerd_snapshotter | tee -a "$DEBUG_OUTPUT" { printf '## Registry info:\n\n' setup_registry | tee -a "$GITHUB_OUTPUT" - } | tee -a "$GITHUB_STEP_SUMMARY" + } | tee -a "$DEBUG_OUTPUT " - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -112,18 +117,21 @@ jobs: 'localhost:5000' docker pull -q "${SOURCE_IMAGE}" || true - echo "IMAGE_HOME=$(mktemp -d)" | tee -a "$GITHUB_OUTPUT" - echo ALPINE_VERSION="${ALPINE_VERSION}" | tee -a "$GITHUB_OUTPUT" - echo PYTHON_VERSION="${PYTHON_VERSION}" | tee -a "$GITHUB_OUTPUT" - echo SOURCE_IMAGE="${SOURCE_IMAGE}" | tee -a "$GITHUB_OUTPUT" - echo IMAGE_TAG="${IMAGE_TAG}" | tee -a "$GITHUB_OUTPUT" - echo IMAGE_BUILDROOT_TAG="${IMAGE_TAG}-buildroot" | tee -a "$GITHUB_OUTPUT" - echo IMAGE_TAG_SAFE="$(echo "$IMAGE_TAG" | base64 -w 0 )" | tee -a "$GITHUB_OUTPUT" - echo IMAGE_BUILDROOT_TAG_SAFE="$(echo "${IMAGE_TAG}-buildroot" | base64 -w 0 )" | tee -a "$GITHUB_OUTPUT" - echo REPOSITORY="${REPOSITORY}" | tee -a "$GITHUB_OUTPUT" - echo REPOSITORY_SAFE="$(echo "${REPOSITORY}" | base64 -w 0 )" | tee -a "$GITHUB_OUTPUT" - echo BASE_IMAGE_DIGEST="$(digest_of "$SOURCE_IMAGE")" | tee -a "$GITHUB_OUTPUT" - echo 'IMAGE_DESCRIPTION=${{ github.event.repository.description }}. See ${{ github.server_url }}/${{ github.repository }} for more info.' | tee -a "$GITHUB_OUTPUT" + echo "IMAGE_HOME=$(mktemp -d)" >> "$GITHUB_OUTPUT" + + echo ALPINE_VERSION="${ALPINE_VERSION}"| tee -a "$GITHUB_OUTPUT" + echo PYTHON_VERSION="${PYTHON_VERSION}"| tee -a "$GITHUB_OUTPUT" + echo SOURCE_IMAGE="${SOURCE_IMAGE}"| tee -a "$GITHUB_OUTPUT" + echo IMAGE_TAG="${IMAGE_TAG}"| tee -a "$GITHUB_OUTPUT" + echo IMAGE_BUILDROOT_TAG="${IMAGE_TAG}-buildroot"| tee -a "$GITHUB_OUTPUT" + echo IMAGE_TAG_SAFE="$(echo "$IMAGE_TAG" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT" + echo IMAGE_BUILDROOT_TAG_SAFE="$(echo "${IMAGE_TAG}-buildroot" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT" + echo 'EXAMPLE_IMAGE_TAG='"${IMAGE_TAG}-example1" | tee -a "$GITHUB_OUTPUT" + echo EXAMPLE_IMAGE_TAG_SAFE="$(echo "${EXAMPLE_IMAGE_TAG}" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT" + echo REPOSITORY="${REPOSITORY}"| tee -a "$GITHUB_OUTPUT" + echo REPOSITORY_SAFE="$(echo "${REPOSITORY}" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT" + echo BASE_IMAGE_DIGEST="$(digest_of "$SOURCE_IMAGE")"| tee -a "$GITHUB_OUTPUT" + echo 'IMAGE_DESCRIPTION=${{ github.event.repository.description }}. See ${{ github.server_url }}/${{ github.repository }} for more info.' | tee -a "$GITHUB_OUTPUT" - name: Create Buildroot @@ -254,7 +262,8 @@ jobs: >&2 printf 'pulled %s (%s)\n' "$IMAGE_TAG" "$platform" done { - printf '## images\n\n```' + printf '# Test\n\n' + printf '## Source Images\n\n```' docker images --tree $IMAGE_URI | sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2};?)?)?[mGK]//g" printf '\n```\n' } | tee -a "$GITHUB_STEP_SUMMARY" @@ -296,7 +305,33 @@ jobs: - id: post_build + name: 'download multiplatform images' run: | + skopeo copy \ + --src-no-creds \ + --src-tls-verify=false \ + --quiet \ + --multi-arch all \ + docker://${{ steps.image_env.outputs.IMAGE_TAG }} \ + oci-archive://${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.IMAGE_TAG_SAFE }}.tar \ + & + skopeo copy \ + --src-no-creds \ + --src-tls-verify=false \ + --quiet \ + --multi-arch all \ + docker://${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot \ + oci-archive://${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.IMAGE_BUILDROOT_TAG_SAFE }}.tar \ + & + skopeo copy \ + --src-no-creds \ + --src-tls-verify=false \ + --quiet \ + --multi-arch all \ + docker://${{ steps.image_env.outputs.EXAMPLE_IMAGE_TAG }} \ + oci-archive://${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.EXAMPLE_IMAGE_TAG_SAFE }}.tar \ + & + wait docker stop '${{ steps.setup.outputs.REGISTRY_CONTAINER_ID }}' IMAGE_DIGEST='${{steps.runtime.outputs.digest}}' diff --git a/build.sh b/build.sh index d4de6eb..7892230 100755 --- a/build.sh +++ b/build.sh @@ -24,8 +24,21 @@ if ! >"$log" 2>&1 docker build \ --build-arg "BASE_IMAGE_DIGEST=${BASE_IMAGE_DIGEST}" \ --build-arg "PYTHON_VERSION=${PYTHON_VERSION}" \ --build-arg "BUILD_ROOT=/d" \ - -f Dockerfile.alpine \ - -t "$IMAGE_TAG" \ + -f buildroot/Dockerfile.alpine \ + -t "${IMAGE_TAG}-buildroot" \ + . ; then + >&2 echo 'Unable to build '"${IMAGE_TAG}-buildroot"'!' + >&2 cat "$log" + exit 8; +fi +if ! >"$log" 2>&1 docker build \ + --build-arg "ALPINE_VERSION=${ALPINE_VERSION}" \ + --build-arg "BASE_IMAGE_DIGEST=${BASE_IMAGE_DIGEST}" \ + --build-arg "PYTHON_VERSION=${PYTHON_VERSION}" \ + --build-arg "BUILD_ROOT=/d" \ + --build-arg "SOURCE_IMAGE=${IMAGE_TAG}-buildroot" \ + -f buildroot/Dockerfile.alpine \ + -t "${IMAGE_TAG}" \ . ; then >&2 echo 'Unable to build '"$IMAGE_TAG"'!' >&2 cat "$log" diff --git a/Dockerfile.alpine b/buildroot/Dockerfile.alpine similarity index 80% rename from Dockerfile.alpine rename to buildroot/Dockerfile.alpine index 1c1c1ab..2e6e036 100644 --- a/Dockerfile.alpine +++ b/buildroot/Dockerfile.alpine @@ -122,28 +122,3 @@ RUN \ # regenerate the ca-certs! chroot-exec update-ca-certificates && \ chroot-pip --optimize install --force-reinstall setuptools - -FROM scratch AS distroless-python -ARG ALPINE_VERSION=3.20 -ARG PYTHON_VERSION=3.12 -ARG SOURCE_IMAGE=docker.io/python:${PYTHON_VERSION}-alpine${ALPINE_VERSION} -ARG BASE_IMAGE_DIGEST -ARG BUILD_ROOT='/dest' -ENV BUILD_ROOT=$BUILD_ROOT \ - PYTHON_VERSION=$PYTHON_VERSION \ - ALPINE_VERSION=$ALPINE_VERSION -COPY --from=buildroot /dev /dev -COPY --from=buildroot $BUILD_ROOT / -LABEL \ - org.opencontainers.image.authors="distroless-python image developers " \ - org.opencontainers.image.source="https://github.com/autumnjolitz/distroless-python" \ - org.opencontainers.image.title="Distroless Python ${PYTHON_VERSION} on alpine${ALPINE_VERSION}" \ - org.opencontainers.image.description="Distroless, optimized Python images distilled from the DockerHub official Python images. These images only have a python interpreter and the dash shell." \ - org.opencontainers.image.base.digest="${BASE_IMAGE_DIGEST}" \ - org.opencontainers.image.base.name="$SOURCE_IMAGE" \ - distroless.python-version="${PYTHON_VERSION}" \ - distroless.alpine-version="${ALPINE_VERSION}" \ - distroless.base-image="alpine${ALPINE_VERSION}" - -SHELL ["/usr/bin/dash", "-c"] -ENTRYPOINT [ "/usr/local/bin/python" ] diff --git a/runtime/Dockerfile.alpine b/runtime/Dockerfile.alpine new file mode 100644 index 0000000..ef3cf6e --- /dev/null +++ b/runtime/Dockerfile.alpine @@ -0,0 +1,32 @@ +# syntax=docker/dockerfile:1.20-labs + + +ARG ALPINE_VERSION=3.20 +ARG PYTHON_VERSION=3.12 +ARG BUILDROOT_IMAGE=docker.io/autumnjolitz/python:${PYTHON_VERSION}-alpine${ALPINE_VERSION}-buildroot +FROM ${BUILDROOT_IMAGE} AS buildroot + +FROM scratch AS distroless-python +ARG ALPINE_VERSION=3.20 +ARG PYTHON_VERSION=3.12 +ARG SOURCE_IMAGE=docker.io/python:${PYTHON_VERSION}-alpine${ALPINE_VERSION} +ARG BASE_IMAGE_DIGEST +ARG BUILD_ROOT='/dest' +ENV BUILD_ROOT=$BUILD_ROOT \ + PYTHON_VERSION=$PYTHON_VERSION \ + ALPINE_VERSION=$ALPINE_VERSION +COPY --from=buildroot /dev /dev +COPY --from=buildroot $BUILD_ROOT / +LABEL \ + org.opencontainers.image.authors="distroless-python image developers " \ + org.opencontainers.image.source="https://github.com/autumnjolitz/distroless-python" \ + org.opencontainers.image.title="Distroless Python ${PYTHON_VERSION} on alpine${ALPINE_VERSION}" \ + org.opencontainers.image.description="Distroless, optimized Python images distilled from the DockerHub official Python images. These images only have a python interpreter and the dash shell." \ + org.opencontainers.image.base.digest="${BASE_IMAGE_DIGEST}" \ + org.opencontainers.image.base.name="$SOURCE_IMAGE" \ + distroless.python-version="${PYTHON_VERSION}" \ + distroless.alpine-version="${ALPINE_VERSION}" \ + distroless.base-image="alpine${ALPINE_VERSION}" + +SHELL ["/usr/bin/dash", "-c"] +ENTRYPOINT [ "/usr/local/bin/python" ] From a53ff7f6f60ab3dbc16a5a9e97525adc03208eaa Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Fri, 5 Dec 2025 16:32:22 -0800 Subject: [PATCH 44/53] feat: add missing mounts to privileged layer, remove useless copy --- buildroot/Dockerfile.alpine | 20 ++++++++------------ runtime/Dockerfile.alpine | 4 +--- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/buildroot/Dockerfile.alpine b/buildroot/Dockerfile.alpine index 2e6e036..ee6b59b 100644 --- a/buildroot/Dockerfile.alpine +++ b/buildroot/Dockerfile.alpine @@ -6,7 +6,8 @@ ARG PYTHON_VERSION=3.12 ARG SOURCE_IMAGE=docker.io/python:${PYTHON_VERSION}-alpine${ALPINE_VERSION} ARG BASE_IMAGE_DIGEST -FROM ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} AS buildroot +FROM ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} AS source +FROM source AS buildroot ARG PYTHON_VERSION=3.12 ARG TARGETARCH ARG TARGETVARIANT @@ -23,6 +24,7 @@ ENV BUILD_ROOT=$BUILD_ROOT \ _ln="chroot $BUILD_ROOT /bin/ln" \ _chroot="chroot $BUILD_ROOT" +COPY --from=source /dev /$BUILD_ROOT/dev ADD --chmod=0755 chroot-apk.sh /usr/local/bin/chroot-apk ADD --chmod=0755 chroot-pip.sh /usr/local/bin/chroot-pip ADD --chmod=0755 chroot-ln.sh /usr/local/bin/chroot-ln @@ -47,8 +49,9 @@ RUN \ $BUILD_ROOT/usr/local/lib/python$PYTHON_VERSION/site-packages \ $BUILD_ROOT/usr/local/bin \ $BUILD_ROOT/proc \ + $BUILD_ROOT/sys \ + $BUILD_ROOT/dev \ ; \ - cp -R /dev $BUILD_ROOT/dev ; \ # copy the apk related confs cp -R /etc/apk $BUILD_ROOT/etc/apk ; \ $_apk_add --initdb ; \ @@ -63,7 +66,9 @@ RUN \ RUN --security=insecure \ set -eu ; \ - mount --bind /proc /$BUILD_ROOT/proc ; \ + mount -t proc none $BUILD_ROOT/proc ; \ + mount -t sysfs none $BUILD_ROOT/sys ; \ + mount --rbind /dev /$BUILD_ROOT/dev ; \ $_apk_add \ busybox \ dash \ @@ -97,15 +102,6 @@ RUN \ # compile all py to an adjacent pyc and remove the original, leaving only the bytecode python -m compileall -q -b /usr/local/lib/python$PYTHON_VERSION ; \ find -type f -name '*.py' -exec sh -c "remove-py-if-pyc-exists -q {}" \; ;\ - # copy dash into the container so we can use it as the default bin/sh - # tar -C / -cpf - $(\ - # apk info -L \ - # dash \ - # dash-binsh \ - # ca-certificates \ - # | grep -vE ':$' \ - # ) | tar -C $BUILD_ROOT -xpf - ; \ - # $_ln -sf /usr/bin/dash /bin/sh ; \ (\ cd /usr/local/lib && \ tar -C /usr/local/lib -cpf - python$PYTHON_VERSION/lib-dynload libpython* | tar -C $BUILD_ROOT/usr/local/lib -xpf - ; \ diff --git a/runtime/Dockerfile.alpine b/runtime/Dockerfile.alpine index ef3cf6e..c1a449b 100644 --- a/runtime/Dockerfile.alpine +++ b/runtime/Dockerfile.alpine @@ -1,5 +1,4 @@ -# syntax=docker/dockerfile:1.20-labs - +# syntax=docker/dockerfile:1 ARG ALPINE_VERSION=3.20 ARG PYTHON_VERSION=3.12 @@ -15,7 +14,6 @@ ARG BUILD_ROOT='/dest' ENV BUILD_ROOT=$BUILD_ROOT \ PYTHON_VERSION=$PYTHON_VERSION \ ALPINE_VERSION=$ALPINE_VERSION -COPY --from=buildroot /dev /dev COPY --from=buildroot $BUILD_ROOT / LABEL \ org.opencontainers.image.authors="distroless-python image developers " \ From 5334566ea044eab3750f16cb3a6a221b38d739bd Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Fri, 5 Dec 2025 16:52:40 -0800 Subject: [PATCH 45/53] feat: skip first non-option verb of wrapped pip command for optimize flagging --- chroot-pip.sh | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/chroot-pip.sh b/chroot-pip.sh index a73a5f4..07a43dd 100644 --- a/chroot-pip.sh +++ b/chroot-pip.sh @@ -66,7 +66,7 @@ fini () { export ALLOW_SITE_PACKAGES=1 find $BUILD_ROOT/usr/local/lib/python$PYTHON_VERSION/site-packages \ -type f -name '*.py' -exec sh -c "remove-py-if-pyc-exists $extra {}" \; ; - return 0 + return $rc } trap fini EXIT @@ -88,18 +88,32 @@ fi case "$@" in *'--force-reinstall'*|optimize*) + maybe_packages= + index=-1 for package_name in $@ do - if case "$package_name" in '-'*) false ;; *) true ;; esac ; then - if >/dev/null pip show "${package_name}"; then - sed -i'' '/^'"${package_name}"'==/d' $BEFORE_PACKAGES + if ! case "$package_name" in '-'*) true ;; *) false ;; esac ; then + index="$(expr $index \+ 1)" || true # ARJ: expr 0 returns 1 lol + if [ $index -ne 0 ]; then + maybe_packages="${maybe_packages} $package_name" fi fi done + package_name= + if [ x$maybe_packages != x ]; then + for package_name in $maybe_packages + do + # if the package is already installed, flag it + # as if it wasn't installed so we can optimize it + if >/dev/null pip show "${package_name}"; then + sed -i'' '/^'"${package_name}"'==/d' $BEFORE_PACKAGES + fi + done + fi ;; esac -if [ $1 = optimize ]; then +if [ x$1 = xoptimize ]; then shift if [ x"$@" = x ]; then >&2 echo 'optimize [PACKAGE] [PACKAGE2] ... [PACKAGEN] From 00765dca05fd4baea60f643f8dc1bab9287f207f Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Fri, 5 Dec 2025 17:21:55 -0800 Subject: [PATCH 46/53] feat: add SOURCE_DATE_EPOCH=0 byte compilation layer --- .github/workflows/development.yml | 9 ++++----- buildroot/Dockerfile.alpine | 1 + 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index be24450..0ab0e49 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -131,7 +131,7 @@ jobs: echo IMAGE_TAG_SAFE="$(echo "$IMAGE_TAG" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT" echo IMAGE_BUILDROOT_TAG_SAFE="$(echo "${IMAGE_TAG}-buildroot" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT" echo 'EXAMPLE_IMAGE_TAG='"${IMAGE_TAG}-example1" | tee -a "$GITHUB_OUTPUT" - echo EXAMPLE_IMAGE_TAG_SAFE="$(echo "${EXAMPLE_IMAGE_TAG}" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT" + echo EXAMPLE_IMAGE_TAG_SAFE="$(echo "${IMAGE_TAG}-example1" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT" echo REPOSITORY="${REPOSITORY}"| tee -a "$GITHUB_OUTPUT" echo REPOSITORY_SAFE="$(echo "${REPOSITORY}" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT" echo BASE_IMAGE_DIGEST="$(digest_of "$SOURCE_IMAGE")"| tee -a "$GITHUB_OUTPUT" @@ -318,7 +318,7 @@ jobs: --src-tls-verify=false \ --quiet \ --multi-arch all \ - docker://${{ steps.image_env.outputs.IMAGE_TAG }} \ + docker://${{ steps.image_env.outputs.IMAGE_TAG }}@${{ steps.runtime.outputs.digest }} \ oci-archive://${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.IMAGE_TAG_SAFE }}.tar \ & skopeo copy \ @@ -326,7 +326,7 @@ jobs: --src-tls-verify=false \ --quiet \ --multi-arch all \ - docker://${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot \ + docker://${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot@${{steps.buildroot.outputs.digest}} \ oci-archive://${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.IMAGE_BUILDROOT_TAG_SAFE }}.tar \ & skopeo copy \ @@ -334,9 +334,8 @@ jobs: --src-tls-verify=false \ --quiet \ --multi-arch all \ - docker://${{ steps.image_env.outputs.EXAMPLE_IMAGE_TAG }} \ + docker://${{ steps.image_env.outputs.EXAMPLE_IMAGE_TAG }}@${{steps.build_test.outputs.digest}} \ oci-archive://${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.EXAMPLE_IMAGE_TAG_SAFE }}.tar \ - & wait docker stop '${{ steps.setup.outputs.REGISTRY_CONTAINER_ID }}' diff --git a/buildroot/Dockerfile.alpine b/buildroot/Dockerfile.alpine index ee6b59b..591aeda 100644 --- a/buildroot/Dockerfile.alpine +++ b/buildroot/Dockerfile.alpine @@ -92,6 +92,7 @@ RUN --security=insecure \ RUN \ --mount=type=cache,id=pip-cache-${TARGETARCH}${TARGETVARIANT},sharing=shared,target=/root/.cache/pip \ set -eu ; \ + export SOURCE_DATE_EPOCH=0 ; \ chroot-apk add \ coreutils-env \ $(apk info -R .python-rundeps | grep -vE ':$') \ From 63b98d14ac8e949bdc02e95b5bd42511b42821d7 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Fri, 5 Dec 2025 17:45:38 -0800 Subject: [PATCH 47/53] feat: add helpful message for ensurepip runtime callers --- buildroot/Dockerfile.alpine | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/buildroot/Dockerfile.alpine b/buildroot/Dockerfile.alpine index 591aeda..a677f29 100644 --- a/buildroot/Dockerfile.alpine +++ b/buildroot/Dockerfile.alpine @@ -119,3 +119,13 @@ RUN \ # regenerate the ca-certs! chroot-exec update-ca-certificates && \ chroot-pip --optimize install --force-reinstall setuptools +COPY < Date: Fri, 5 Dec 2025 17:47:26 -0800 Subject: [PATCH 48/53] fix: add missing ``&`` --- .github/workflows/development.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 0ab0e49..8187338 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -336,6 +336,7 @@ jobs: --multi-arch all \ docker://${{ steps.image_env.outputs.EXAMPLE_IMAGE_TAG }}@${{steps.build_test.outputs.digest}} \ oci-archive://${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.EXAMPLE_IMAGE_TAG_SAFE }}.tar \ + & wait docker stop '${{ steps.setup.outputs.REGISTRY_CONTAINER_ID }}' From 45b6cf67d7974dbfbc00b08a55ed3c5b867b8bcd Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Fri, 5 Dec 2025 17:49:04 -0800 Subject: [PATCH 49/53] fix: move chmod to correct Dockerfile location --- buildroot/Dockerfile.alpine | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/buildroot/Dockerfile.alpine b/buildroot/Dockerfile.alpine index a677f29..5c0b123 100644 --- a/buildroot/Dockerfile.alpine +++ b/buildroot/Dockerfile.alpine @@ -119,7 +119,8 @@ RUN \ # regenerate the ca-certs! chroot-exec update-ca-certificates && \ chroot-pip --optimize install --force-reinstall setuptools -COPY < Date: Fri, 5 Dec 2025 17:59:14 -0800 Subject: [PATCH 50/53] fix: adjust for versions specifiers in package names in chroot-pip --- chroot-pip.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/chroot-pip.sh b/chroot-pip.sh index 07a43dd..fcb9d20 100644 --- a/chroot-pip.sh +++ b/chroot-pip.sh @@ -103,9 +103,12 @@ case "$@" in if [ x$maybe_packages != x ]; then for package_name in $maybe_packages do + if case "$package_name" in *'=='*) true ;; *) false ;; esac ; then + package_name="$(echo "${package_name}" | cut -d= -f1 | xargs)" + fi # if the package is already installed, flag it # as if it wasn't installed so we can optimize it - if >/dev/null pip show "${package_name}"; then + if [ x$package_name != x ] && >/dev/null pip show "${package_name}"; then sed -i'' '/^'"${package_name}"'==/d' $BEFORE_PACKAGES fi done @@ -113,7 +116,7 @@ case "$@" in ;; esac -if [ x$1 = xoptimize ]; then +if [ x"${1:-}" = xoptimize ]; then shift if [ x"$@" = x ]; then >&2 echo 'optimize [PACKAGE] [PACKAGE2] ... [PACKAGEN] From b4df978de3d5fbf88c274fadecb2fede4f8f30af Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Fri, 5 Dec 2025 18:02:51 -0800 Subject: [PATCH 51/53] ci: strip tag name for a digest copy instead --- .github/workflows/development.yml | 7 ++++--- .github/workflows/main.yml | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 8187338..7e8cc3d 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -266,6 +266,7 @@ jobs: docker pull --platform $platform -q "$IMAGE_URI" >&2 printf 'pulled %s (%s)\n' "$IMAGE_TAG" "$platform" done + docker pull -q "$IMAGE_URI" { printf '# Test\n\n' printf '## Source Images\n\n```' @@ -318,7 +319,7 @@ jobs: --src-tls-verify=false \ --quiet \ --multi-arch all \ - docker://${{ steps.image_env.outputs.IMAGE_TAG }}@${{ steps.runtime.outputs.digest }} \ + docker://$(echo '${{ steps.image_env.outputs.IMAGE_TAG }}' | rev | cut -d: -f2- | rev)@${{ steps.runtime.outputs.digest }} \ oci-archive://${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.IMAGE_TAG_SAFE }}.tar \ & skopeo copy \ @@ -326,7 +327,7 @@ jobs: --src-tls-verify=false \ --quiet \ --multi-arch all \ - docker://${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot@${{steps.buildroot.outputs.digest}} \ + docker://$(echo '${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot' | rev | cut -d: -f2- | rev)@${{steps.buildroot.outputs.digest}} \ oci-archive://${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.IMAGE_BUILDROOT_TAG_SAFE }}.tar \ & skopeo copy \ @@ -334,7 +335,7 @@ jobs: --src-tls-verify=false \ --quiet \ --multi-arch all \ - docker://${{ steps.image_env.outputs.EXAMPLE_IMAGE_TAG }}@${{steps.build_test.outputs.digest}} \ + docker://$(echo '${{ steps.image_env.outputs.EXAMPLE_IMAGE_TAG }}' | rev | cut -d: -f2- | rev)@${{steps.build_test.outputs.digest}} \ oci-archive://${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.EXAMPLE_IMAGE_TAG_SAFE }}.tar \ & wait diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e9ccb99..fce4ad6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -261,6 +261,7 @@ jobs: docker pull --platform $platform -q "$IMAGE_URI" >&2 printf 'pulled %s (%s)\n' "$IMAGE_TAG" "$platform" done + docker pull -q "$IMAGE_URI" { printf '# Test\n\n' printf '## Source Images\n\n```' @@ -312,7 +313,7 @@ jobs: --src-tls-verify=false \ --quiet \ --multi-arch all \ - docker://${{ steps.image_env.outputs.IMAGE_TAG }} \ + docker://$(echo '${{ steps.image_env.outputs.IMAGE_TAG }}' | rev | cut -d: -f2- | rev)@${{ steps.runtime.outputs.digest }} \ oci-archive://${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.IMAGE_TAG_SAFE }}.tar \ & skopeo copy \ @@ -320,7 +321,7 @@ jobs: --src-tls-verify=false \ --quiet \ --multi-arch all \ - docker://${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot \ + docker://$(echo '${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot' | rev | cut -d: -f2- | rev)@${{steps.buildroot.outputs.digest}} \ oci-archive://${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.IMAGE_BUILDROOT_TAG_SAFE }}.tar \ & skopeo copy \ @@ -328,7 +329,7 @@ jobs: --src-tls-verify=false \ --quiet \ --multi-arch all \ - docker://${{ steps.image_env.outputs.EXAMPLE_IMAGE_TAG }} \ + docker://$(echo '${{ steps.image_env.outputs.EXAMPLE_IMAGE_TAG }}' | rev | cut -d: -f2- | rev)@${{steps.build_test.outputs.digest}} \ oci-archive://${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.EXAMPLE_IMAGE_TAG_SAFE }}.tar \ & wait From dedefc5279e55ffe163bfdb39ce22a787b4a2a6d Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Fri, 5 Dec 2025 18:22:38 -0800 Subject: [PATCH 52/53] fix: add checks for empty vars in chroot-pip --- chroot-pip.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/chroot-pip.sh b/chroot-pip.sh index fcb9d20..54889ab 100644 --- a/chroot-pip.sh +++ b/chroot-pip.sh @@ -18,7 +18,7 @@ fi set -e set -o pipefail -if [ "$1" = '-O' ] || [ "$1" = '--optimize' ]; then +if [ "x${1:-}" = 'x-O' ] || [ "x${1:-}" = 'x--optimize' ]; then PIP_OPTIMIZE='1' shift fi @@ -34,16 +34,16 @@ fini () { local rc=$? local extra=-q local new_packages= - if [ "$DEBUG" = 1 ]; then + if [ "x${DEBUG:-}" = x1 ]; then extra= fi - if [ $PIP_OPTIMIZE = '1' ]; then + if [ x${PIP_OPTIMIZE:-} = 'x1' ]; then python -m pip freeze >"$AFTER_PACKAGES" rv=0 new_packages="$(diff -Naur "$BEFORE_PACKAGES" "$AFTER_PACKAGES" | grep -vE '^\+\+' | grep -E '^\+' | cut -f2 -d+ | cut -f1 -d= | xargs)" || rv=$? if [ "x$new_packages" != 'x' ]; then - if [ "$DEBUG" = '1' ]; then + if [ "x${DEBUG:-}" = 'x1' ]; then >&2 echo "Optimizing packages (${new_packages})..." fi for package in $new_packages @@ -57,7 +57,7 @@ fini () { done done else - if [ "$DEBUG" = '1' ]; then + if [ "x${DEBUG:-}" = 'x1' ]; then >&2 echo 'No new packages installed or changed to optimize with.' fi fi @@ -80,7 +80,7 @@ case "$1" in ;; esac -if [ $PIP_OPTIMIZE = '1' ]; then +if [ x$PIP_OPTIMIZE = 'x1' ]; then BEFORE_PACKAGES=$(mktemp) AFTER_PACKAGES=$(mktemp) python -m pip freeze >"$BEFORE_PACKAGES" @@ -104,11 +104,11 @@ case "$@" in for package_name in $maybe_packages do if case "$package_name" in *'=='*) true ;; *) false ;; esac ; then - package_name="$(echo "${package_name}" | cut -d= -f1 | xargs)" + package_name="$(echo "${package_name}" | cut -d'=' -f1 | xargs)" fi # if the package is already installed, flag it # as if it wasn't installed so we can optimize it - if [ x$package_name != x ] && >/dev/null pip show "${package_name}"; then + if [ x$package_name != x ] && pip show "${package_name}" >/dev/null ; then sed -i'' '/^'"${package_name}"'==/d' $BEFORE_PACKAGES fi done From 5a31a9bdaf46d864223b7edb1a62f546383e00a0 Mon Sep 17 00:00:00 2001 From: Autumn Jolitz Date: Fri, 5 Dec 2025 18:33:12 -0800 Subject: [PATCH 53/53] fix: silence a few more variables in chroot-pip --- chroot-pip.sh | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/chroot-pip.sh b/chroot-pip.sh index 54889ab..775a820 100644 --- a/chroot-pip.sh +++ b/chroot-pip.sh @@ -89,23 +89,22 @@ fi case "$@" in *'--force-reinstall'*|optimize*) maybe_packages= - index=-1 + index='-1' for package_name in $@ do - if ! case "$package_name" in '-'*) true ;; *) false ;; esac ; then - index="$(expr $index \+ 1)" || true # ARJ: expr 0 returns 1 lol + if [ x"$package_name" != x ] && ! case "$package_name" in '-'*) true ;; *) false ;; esac ; then + index="$(expr $index \+ 1)" || index='0' + if case "$package_name" in *'=='*) true ;; *) false ;; esac ; then + package_name="$(echo "${package_name}" | cut -d'=' -f1 | xargs)" + fi if [ $index -ne 0 ]; then - maybe_packages="${maybe_packages} $package_name" + maybe_packages="${maybe_packages} ${package_name}" fi fi done - package_name= if [ x$maybe_packages != x ]; then for package_name in $maybe_packages do - if case "$package_name" in *'=='*) true ;; *) false ;; esac ; then - package_name="$(echo "${package_name}" | cut -d'=' -f1 | xargs)" - fi # if the package is already installed, flag it # as if it wasn't installed so we can optimize it if [ x$package_name != x ] && pip show "${package_name}" >/dev/null ; then