Skip to content

Commit

Permalink
feat: introducing distroless-python
Browse files Browse the repository at this point in the history
  • Loading branch information
autumnjolitz committed Aug 3, 2024
0 parents commit 5f01eb1
Show file tree
Hide file tree
Showing 12 changed files with 737 additions and 0 deletions.
222 changes: 222 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
name: main

on:
push:
branches: main
pull_request:
branches: main

jobs:
docker:
strategy:
fail-fast: false
matrix:
repository:
- 'ghcr.io'
- 'docker.io'
python:
- '3.12'
- '3.11'
- '3.10'
- '3.9'
- '3.8'
alpine:
- '3.20'
os:
- 'ubuntu-latest'

runs-on: ${{ matrix.os }}
permissions:
packages: write

steps:
-
name: Checkout
uses: actions/checkout@v4

-
name: Convert README.rst to markdown
uses: docker://pandoc/core:2.9
with:
args: >-
-s
--wrap=none
-t gfm
-o README.md
README.rst
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
id: image_env
run: |
. ./env.sh \
'${{ matrix.alpine }}' \
'${{ matrix.python }}' \
'${{ github.repository_owner }}' \
'${{ matrix.repository }}'
docker pull "${SOURCE_IMAGE}"
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 REPOSITORY="${REPOSITORY}" >> "$GITHUB_OUTPUT"
echo BASE_IMAGE_DIGEST="$(digest_of "$SOURCE_IMAGE")" >> "$GITHUB_OUTPUT"
echo 'LONG_DESCRIPTION<<EOF' >> "$GITHUB_OUTPUT"
<README.md cat >> "$GITHUB_OUTPUT"
echo 'EOF' >> "$GITHUB_OUTPUT"
-
name: Buildroot
uses: docker/build-push-action@v6
with:
platforms: |
linux/amd64
linux/arm64
context: "."
file: Dockerfile.alpine
target: buildroot
cache-from: |
type=registry,ref=${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot
type=registry,ref=docker.io/python@${{ steps.image_env.outputs.BASE_IMAGE_DIGEST }}
build-args: |
ALPINE_VERSION=${{ steps.image_env.outputs.ALPINE_VERSION }}
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 }}
BUILD_ROOT=/d
tags: "${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot"
-
name: distroless
uses: docker/build-push-action@v6
with:
platforms: |
linux/amd64
linux/arm64
context: "."
file: Dockerfile.alpine
# target: distroless-python
cache-from: |
type=registry,ref=${{ steps.image_env.outputs.IMAGE_TAG }}
type=registry,ref=${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot
type=registry,ref=docker.io/python@${{ steps.image_env.outputs.BASE_IMAGE_DIGEST }}
build-args: |
ALPINE_VERSION=${{ steps.image_env.outputs.ALPINE_VERSION }}
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 }}
BUILD_ROOT=/d
tags: "${{ steps.image_env.outputs.IMAGE_TAG }}"
# -
# name: distroless-tests
# uses: docker/build-push-action@v6
# with:
# context: "."
# platforms: |
# linux/amd64
# linux/arm64
# file: Dockerfile.alpine
# target: tests
# cache-from: |
# type=registry,ref=${{ steps.image_env.outputs.IMAGE_TAG }}
# type=registry,ref=${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot
# type=registry,ref=docker.io/python@${{ steps.image_env.outputs.BASE_IMAGE_DIGEST }}
# build-args: |
# ALPINE_VERSION=${{ steps.image_env.outputs.ALPINE_VERSION }}
# 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 }}
# BUILD_ROOT=/d
# tags: "${{ steps.image_env.outputs.IMAGE_TAG }}-test"
# -
# name: export annotations
# id: inspect
# run: |
# echo 'annotations<<EOF' >> "$GITHUB_OUTPUT"
# docker inspect '${{ steps.image_env.outputs.IMAGE_TAG }}' | jq -r '.[].Config.Labels| keys[] as $k | "\($k)=\(.[$k])"' >> "$GITHUB_OUTPUT"
# echo 'EOF' >> "$GITHUB_OUTPUT"

-
name: Login to GitHub Container Registry
if: ${{ matrix.repository == 'ghcr.io' }}
uses: docker/login-action@v3
with:
registry: 'ghcr.io'
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
-
name: Login to DockerHub
if: ${{ matrix.repository == 'docker.io' }}
uses: docker/login-action@v3
with:
registry: 'docker.io'
username: ${{ github.repository_owner }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

-
name: Upload Buildroot
uses: docker/build-push-action@v6
with:
push: true
platforms: |
linux/amd64
linux/arm64
context: "."
file: Dockerfile.alpine
target: buildroot
cache-from: |
type=registry,ref=${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot
type=registry,ref=docker.io/python@${{ steps.image_env.outputs.BASE_IMAGE_DIGEST }}
build-args: |
ALPINE_VERSION=${{ steps.image_env.outputs.ALPINE_VERSION }}
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 }}
BUILD_ROOT=/d
tags: "${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot"
-
name: Upload
uses: docker/build-push-action@v6
with:
push: true
context: "."
platforms: |
linux/amd64
linux/arm64
file: Dockerfile.alpine
cache-from: |
type=registry,ref=${{ steps.image_env.outputs.IMAGE_TAG }}
type=registry,ref=${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot
type=registry,ref=docker.io/python@${{ steps.image_env.outputs.BASE_IMAGE_DIGEST }}
build-args: |
ALPINE_VERSION=${{ steps.image_env.outputs.ALPINE_VERSION }}
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 }}
BUILD_ROOT=/d
tags: "${{ steps.image_env.outputs.IMAGE_TAG }}"
labels: ${{steps.image_env.outputs.IMAGE_LABELS}}
annotations: |
index,manifest:org.opencontainers.image.authors=distroless-python image developers <autumn.jolitz+distroless-python@gmail.com>
index,manifest:org.opencontainers.image.source=https://github.com/autumnjolitz/distroless-python
index,manifest:org.opencontainers.image.title=Distroless Python ${{ steps.image_env.outputs.PYTHON_VERSION }} on alpine${{ steps.image_env.outputs.ALPINE_VERSION }}
index,manifest:org.opencontainers.image.description=${{ steps.image_env.LONG_DESCRIPTION }}
index,manifest:org.opencontainers.image.base.digest=${{ steps.image_env.outputs.BASE_IMAGE_DIGEST }}
index,manifest:org.opencontainers.image.base.name=${{ steps.image_env.outputs.SOURCE_IMAGE }}
index,manifest:distroless.python-version=${{ steps.image_env.outputs.PYTHON_VERSION }}
index,manifest:distroless.alpine-version=${{ steps.image_env.outputs.ALPINE_VERSION }}
index,manifest:distroless.base-image=alpine${{ steps.image_env.outputs.ALPINE_VERSION }}
- name: Update repo description
if: ${{ matrix.repository == 'docker.io' }}
uses: peter-evans/dockerhub-description@e98e4d1628a5f3be2be7c231e50981aee98723ae # v4.0.0
with:
username: ${{ github.repository_owner }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
short-description: ${{ github.event.repository.description }}
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
*.egg-info/
.idea/*
*.idx
*.sublime-*
*.pyc
dist/
.cache/
*.dat
.DS_Store
python/
.pytest_cache/
build/*
.pytype/*
instruct/about.py
python*/
.coverage
32 changes: 32 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
repos:
-
repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
-
id: check-ast
-
id: check-case-conflict
-
id: check-executables-have-shebangs
-
id: check-merge-conflict
-
id: check-yaml
-
id: end-of-file-fixer
-
id: check-shebang-scripts-are-executable
-
id: detect-private-key
-
id: trailing-whitespace
args:
- '--markdown-linebreak-ext=rst'
-
repo: https://github.com/python/black
rev: '23.11.0'
hooks:
-
id: black
language_version: python3
111 changes: 111 additions & 0 deletions Dockerfile.alpine
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
ARG ALPINE_VERSION=3.20
ARG PYTHON_VERSION=3.12
ARG SOURCE_IMAGE=docker.io/python:${PYTHON_VERSION}-alpine${ALPINE_VERSION}

FROM --platform=$BUILDPLATFORM $SOURCE_IMAGE AS buildroot
ARG PYTHON_VERSION=3.12

ARG BUILD_ROOT='/dest'
ARG CACHE_ROOT='/cache'
ENV BUILD_ROOT=$BUILD_ROOT \
CACHE_ROOT=$CACHE_ROOT \
PYTHON_VERSION=$PYTHON_VERSION \
_sys_apk_add="/usr/bin/env apk add --no-cache" \
_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" \
_chroot="chroot $BUILD_ROOT"

RUN set -eu ; \
python -m pip install -U pip setuptools ; \
# Add to buildroot:
$_sys_apk_add \
# dash is used as a /bin/sh replacement
dash \
# TLS certs
ca-certificates \
# zip is used to take all the bytecode compiled standard
# library and create a pythonXY.zip file that will
# 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 "[ -f \"{}c\" ] && echo 'Removing \"{}\"' && rm -f \"{}\"" \; ;\
# 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 \
; \
# use a symlink to hold the apk related confs
ln -s /etc/apk $BUILD_ROOT/etc/apk ; \
$_apk_add --initdb ; \
$_apk_add \
alpine-baselayout-data \
alpine-release \
musl \
libffi \
# needed for update-ca-certificates to work:
run-parts \
# install the runtime dependencies for python
$(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 \
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 - ; \
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 $(\
find . | grep -vE "(__pycache__|^\./(test|site-packages|lib-dynload|idlelib|lib2to3|tkinter|turtle|ensurepip|pydoc))" \
)); \
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 /usr/share/apk && \
# regenerate the ca-certs!
chroot $BUILD_ROOT update-ca-certificates


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 <autumn.jolitz+distroless-python@gmail.com>" \
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}"

ENTRYPOINT [ "/usr/local/bin/python" ]
Loading

0 comments on commit 5f01eb1

Please sign in to comment.