Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
2f5b7b7
feat: add chroot-exec, the missing function
autumnjolitz Dec 4, 2025
f7baf50
feat: silence a lot of debugging info
autumnjolitz Dec 4, 2025
dc7a26e
build: reduce output
autumnjolitz Dec 4, 2025
d526743
feat: update example
autumnjolitz Dec 4, 2025
42860f5
build: quiet zip
autumnjolitz Dec 4, 2025
2266f0a
build: remove debug line
autumnjolitz Dec 4, 2025
d094eec
build: disable docker report output
autumnjolitz Dec 4, 2025
913a02d
build: run pre-commit
autumnjolitz Dec 4, 2025
c9031d8
build: update ``pre-commit``
autumnjolitz Dec 4, 2025
0941194
build: moved README template to docs, parallel image uploader
autumnjolitz Dec 4, 2025
e8cdc29
build: add test for example
autumnjolitz Dec 4, 2025
c74a341
build: add containerd
autumnjolitz Dec 4, 2025
3d06444
build: use local registry
autumnjolitz Dec 4, 2025
4cbe901
build: refine test
autumnjolitz Dec 4, 2025
542c049
build: add metadata name
autumnjolitz Dec 4, 2025
1f40c58
build: fallible test
autumnjolitz Dec 4, 2025
47665b7
build: pull N platforms
autumnjolitz Dec 4, 2025
1dc3234
build: remove color codes
autumnjolitz Dec 4, 2025
d21b790
build: verify digest
autumnjolitz Dec 4, 2025
32c8272
build: try a multi-image store setting
autumnjolitz Dec 4, 2025
a01b6ad
build: restarting docker trashes the registry, so start in init
autumnjolitz Dec 4, 2025
4d0abac
build: stop registry when done
autumnjolitz Dec 4, 2025
64daf70
build: test both platforms
autumnjolitz Dec 4, 2025
a7fc765
build: expose the architecture to verify
autumnjolitz Dec 4, 2025
507d7e3
build: remove from output
autumnjolitz Dec 5, 2025
a9daea1
fix: add ``-q`` for bytecode compilation
autumnjolitz Dec 5, 2025
25bc892
feat: optimize setuptools
autumnjolitz Dec 5, 2025
3b78ce9
build: expose the rst body
autumnjolitz Dec 5, 2025
f85da5a
docs: switch to using ``chroot-pip`` for example
autumnjolitz Dec 5, 2025
2170356
build: debug optimize
autumnjolitz Dec 5, 2025
5f6f55a
build: chroot-pip has an optimize command, handles case of no-changes
autumnjolitz Dec 5, 2025
5f7f5df
fix: use ``chroot-exec`` helper
autumnjolitz Dec 5, 2025
fb0035b
build: add pip cache
autumnjolitz Dec 5, 2025
c690340
build: add missing field back
autumnjolitz Dec 5, 2025
df3dac4
fix: add missing ``ARG`` declarations
autumnjolitz Dec 5, 2025
b6fd48b
build: add test to main
autumnjolitz Dec 5, 2025
c4f62a9
build: add pip cache
autumnjolitz Dec 5, 2025
0b5d2db
feat: add alpine3.23
autumnjolitz Dec 5, 2025
f6c3a13
fix: broken build
autumnjolitz Dec 5, 2025
99ace78
build: make /dev/fd in chroot
autumnjolitz Dec 5, 2025
07fa2f3
build: reduce output
autumnjolitz Dec 5, 2025
2136a95
feat: chroot-pip autosets SOURCE_DATE_EPOCH for deterministic pyc cre…
autumnjolitz Dec 6, 2025
9015f0d
feat: split out docker files to reduce privileged docker runs
autumnjolitz Dec 6, 2025
a53ff7f
feat: add missing mounts to privileged layer, remove useless copy
autumnjolitz Dec 6, 2025
5334566
feat: skip first non-option verb of wrapped pip command for optimize …
autumnjolitz Dec 6, 2025
00765dc
feat: add SOURCE_DATE_EPOCH=0 byte compilation layer
autumnjolitz Dec 6, 2025
63b98d1
feat: add helpful message for ensurepip runtime callers
autumnjolitz Dec 6, 2025
173def6
fix: add missing ``&``
autumnjolitz Dec 6, 2025
45b6cf6
fix: move chmod to correct Dockerfile location
autumnjolitz Dec 6, 2025
aeaeb63
fix: adjust for versions specifiers in package names in chroot-pip
autumnjolitz Dec 6, 2025
b4df978
ci: strip tag name for a digest copy instead
autumnjolitz Dec 6, 2025
dedefc5
fix: add checks for empty vars in chroot-pip
autumnjolitz Dec 6, 2025
5a31a9b
fix: silence a few more variables in chroot-pip
autumnjolitz Dec 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
241 changes: 209 additions & 32 deletions .github/workflows/development.yml

Large diffs are not rendered by default.

413 changes: 332 additions & 81 deletions .github/workflows/main.yml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
17 changes: 15 additions & 2 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
122 changes: 65 additions & 57 deletions Dockerfile.alpine → buildroot/Dockerfile.alpine
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
#syntax=docker/dockerfile:1
# syntax=docker/dockerfile:1.20-labs


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

FROM ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} AS buildroot
FROM ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} AS source

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.14, 3.22, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.13, 3.21, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.14, 3.21, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.13, 3.22, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.13, 3.20, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.12, 3.20, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.14, 3.23, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.12, 3.23, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.11, 3.23, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.11, 3.22, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.12, 3.21, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.11, 3.20, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.10, 3.21, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.13, 3.23, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.10, 3.22, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.10, 3.23, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.12, 3.22, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.10, 3.20, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.9, 3.20, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.11, 3.21, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.9, 3.21, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.9, 3.22, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 9 in buildroot/Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / Create runtime and buildroot OCI Images (3.8, 3.20, ubuntu-latest)

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${SOURCE_IMAGE}@${BASE_IMAGE_DIGEST} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/
FROM source AS buildroot
ARG PYTHON_VERSION=3.12
ARG TARGETARCH
ARG TARGETVARIANT

ARG BUILD_ROOT='/dest'
ARG CACHE_ROOT='/cache'
Expand All @@ -17,16 +21,17 @@
_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"

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
ADD --chmod=0755 remove-py-if-pyc-exists.sh /usr/local/bin/remove-py-if-pyc-exists
RUN set -eu ; \
python -m pip install -U pip setuptools ; \
# Add to buildroot:
ADD --chmod=0755 chroot-exec.sh /usr/local/bin/chroot-exec
RUN \
set -eu ; \
$_sys_apk_add \
dash \
# TLS certs
Expand All @@ -36,18 +41,16 @@
# 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 -b /usr/local/lib/python$PYTHON_VERSION ; \
find -type f -name '*.py' -exec sh -c "remove-py-if-pyc-exists {}" \; ;\
# make the new root:
mkdir -p \
$CACHE_ROOT/ \
$BUILD_ROOT/etc \
$BUILD_ROOT/bin \
$BUILD_ROOT/usr/local/lib/python$PYTHON_VERSION/site-packages \
$BUILD_ROOT/usr/local/bin \
$BUILD_ROOT/proc \
$BUILD_ROOT/sys \
$BUILD_ROOT/dev \
; \
# copy the apk related confs
cp -R /etc/apk $BUILD_ROOT/etc/apk ; \
Expand All @@ -57,68 +60,73 @@
alpine-release \
musl \
libffi \
dash \
dash-binsh \
coreutils-env \
; \
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 -t proc none $BUILD_ROOT/proc ; \
mount -t sysfs none $BUILD_ROOT/sys ; \
mount --rbind /dev /$BUILD_ROOT/dev ; \
$_apk_add \
busybox \
dash \
dash-binsh \
; \
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 ; \
export SOURCE_DATE_EPOCH=0 ; \
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 ; \
# 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 ; \
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 {}" \; ;\
(\
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 ; \
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 $BUILD_ROOT update-ca-certificates && \
chroot-pip install --force-reinstall setuptools
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 $BUILD_ROOT /
LABEL \
org.opencontainers.image.authors="distroless-python image developers <[email protected]>" \
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}"
COPY --chmod=755 <<EOF /$BUILD_ROOT/usr/local/lib/python$PYTHON_VERSION/ensurepip.py
import sys

SHELL ["/usr/bin/dash", "-c"]
ENTRYPOINT [ "/usr/local/bin/python" ]
if __name__ == "__main__":
print("""
ensurepip is removed to save space
please install the package in the buildroot image via chroot-pip
and then COPY it over (using --from=) into your image root.
""", sys.stderr)
EOF
41 changes: 33 additions & 8 deletions chroot-apk.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,56 @@ 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
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
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" $@
apk --root "$BUILD_ROOT" $@
64 changes: 64 additions & 0 deletions chroot-exec.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/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
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
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 $@
Loading