From 10d0b70f5750e0be6db00d2f420357cc7a99e43c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 15 Feb 2026 09:57:39 +0000 Subject: [PATCH 01/15] feat: switch base image to GitHub runner and add DevOps tools Replace ubuntu:24.04 base with ghcr.io/actions/runner for native GitHub Actions runner support. Add skopeo, Argo Workflows CLI, HashiCorp Packer, and Cloud Native Buildpacks (pack) CLI as configurable build args. https://claude.ai/code/session_01RofXXAMZxK4irobNYjYn3W --- Containerfile | 54 +++++++++++++++++++++++++++++++++++++-------------- manifest.yaml | 6 ++++-- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/Containerfile b/Containerfile index ed783d7..9705627 100644 --- a/Containerfile +++ b/Containerfile @@ -1,20 +1,10 @@ -ARG UBUNTU_VERSION=24.04 +ARG RUNNER_VERSION=latest -FROM docker.io/library/ubuntu:$UBUNTU_VERSION as base +FROM ghcr.io/actions/runner:${RUNNER_VERSION} as base -ARG APP_UID=1000 -ARG APP_HOME=/home/appuser +ARG APP_HOME=/home/runner -# Setup the non-root user -RUN userdel --remove ubuntu \ - && useradd \ - --no-log-init \ - --uid $APP_UID \ - --home-dir ${APP_HOME} \ - --create-home \ - --user-group \ - appuser && \ - chown -R appuser:appuser ${APP_HOME} +USER root # Update and upgrade the system RUN apt-get update \ @@ -35,6 +25,40 @@ RUN apt-get update \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* +# Install skopeo +# hadolint ignore=DL3008 +RUN apt-get update \ + && apt-get install --no-install-recommends -y skopeo \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Install Argo Workflows CLI +ARG ARGO_VERSION=3.6.4 +RUN curl -sSL -o /tmp/argo-linux-amd64.gz \ + "https://github.com/argoproj/argo-workflows/releases/download/v${ARGO_VERSION}/argo-linux-amd64.gz" \ + && gunzip /tmp/argo-linux-amd64.gz \ + && mv /tmp/argo-linux-amd64 /usr/local/bin/argo \ + && chmod +x /usr/local/bin/argo + +# Install HashiCorp Packer +ARG PACKER_VERSION=1.11.2 +# hadolint ignore=DL3008 +RUN apt-get update \ + && apt-get install --no-install-recommends -y unzip \ + && curl -sSL -o /tmp/packer.zip \ + "https://releases.hashicorp.com/packer/${PACKER_VERSION}/packer_${PACKER_VERSION}_linux_amd64.zip" \ + && unzip /tmp/packer.zip -d /usr/local/bin/ \ + && rm /tmp/packer.zip \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Install pack (Cloud Native Buildpacks CLI) +ARG PACK_VERSION=0.36.4 +RUN curl -sSL -o /tmp/pack.tgz \ + "https://github.com/buildpacks/pack/releases/download/v${PACK_VERSION}/pack-v${PACK_VERSION}-linux.tgz" \ + && tar -xzf /tmp/pack.tgz -C /usr/local/bin/ \ + && rm /tmp/pack.tgz + # Install Poetry latest version and add it to PATH # hadolint ignore=DL4006 RUN curl -sSL https://install.python-poetry.org | python3 - @@ -54,7 +78,7 @@ LABEL org.opencontainers.image.licenses="MIT" LABEL org.opencontainers.image.authors="Deerhide" LABEL org.opencontainers.image.vendor="Deerhide" -USER ${APP_UID} +USER runner WORKDIR ${APP_HOME} # Install Poetry latest version and add it to PATH diff --git a/manifest.yaml b/manifest.yaml index 2a3b6c9..1ae732d 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -5,8 +5,10 @@ registry: ghcr.io/deerhide/python-github-runner build: format: oci args: - - APP_UID=1000 - - UBUNTU_VERSION=24.04 + - RUNNER_VERSION=latest + - ARGO_VERSION=3.6.4 + - PACKER_VERSION=1.11.2 + - PACK_VERSION=0.36.4 labels: - org.opencontainers.image.source=https://github.com/deerhide/python-github-runner - org.opencontainers.image.description="Python GitHub Runner" From 51c2b520dfb567270096e8d8d2b10d945385c167 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 15 Feb 2026 09:59:01 +0000 Subject: [PATCH 02/15] feat: replace packer with kargo CLI Swap out HashiCorp Packer for Kargo CLI (v1.9.2) for application lifecycle orchestration support. https://claude.ai/code/session_01RofXXAMZxK4irobNYjYn3W --- Containerfile | 16 +++++----------- manifest.yaml | 2 +- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/Containerfile b/Containerfile index 9705627..a045ac8 100644 --- a/Containerfile +++ b/Containerfile @@ -40,17 +40,11 @@ RUN curl -sSL -o /tmp/argo-linux-amd64.gz \ && mv /tmp/argo-linux-amd64 /usr/local/bin/argo \ && chmod +x /usr/local/bin/argo -# Install HashiCorp Packer -ARG PACKER_VERSION=1.11.2 -# hadolint ignore=DL3008 -RUN apt-get update \ - && apt-get install --no-install-recommends -y unzip \ - && curl -sSL -o /tmp/packer.zip \ - "https://releases.hashicorp.com/packer/${PACKER_VERSION}/packer_${PACKER_VERSION}_linux_amd64.zip" \ - && unzip /tmp/packer.zip -d /usr/local/bin/ \ - && rm /tmp/packer.zip \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* +# Install Kargo CLI +ARG KARGO_VERSION=1.9.2 +RUN curl -sSL -o /usr/local/bin/kargo \ + "https://github.com/akuity/kargo/releases/download/v${KARGO_VERSION}/kargo-linux-amd64" \ + && chmod +x /usr/local/bin/kargo # Install pack (Cloud Native Buildpacks CLI) ARG PACK_VERSION=0.36.4 diff --git a/manifest.yaml b/manifest.yaml index 1ae732d..2d8063b 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -7,7 +7,7 @@ build: args: - RUNNER_VERSION=latest - ARGO_VERSION=3.6.4 - - PACKER_VERSION=1.11.2 + - KARGO_VERSION=1.9.2 - PACK_VERSION=0.36.4 labels: - org.opencontainers.image.source=https://github.com/deerhide/python-github-runner From 02d7b2e01b94745c653db92910cb62cf2c9029ca Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 15 Feb 2026 10:00:57 +0000 Subject: [PATCH 03/15] ci: add scheduled workflow to update tool versions Runs weekly (Monday 08:00 UTC) and on manual dispatch. Checks latest releases for Argo, Kargo, and pack CLIs and opens a PR when updates are available. https://claude.ai/code/session_01RofXXAMZxK4irobNYjYn3W --- .github/workflows/update-tools.yaml | 101 ++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 .github/workflows/update-tools.yaml diff --git a/.github/workflows/update-tools.yaml b/.github/workflows/update-tools.yaml new file mode 100644 index 0000000..7ae0ac6 --- /dev/null +++ b/.github/workflows/update-tools.yaml @@ -0,0 +1,101 @@ +name: Update tool versions + +on: + schedule: + - cron: "0 8 * * 1" # Every Monday at 08:00 UTC + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + update-tools: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Fetch latest tool versions + id: versions + env: + GH_TOKEN: ${{ github.token }} + run: | + get_latest_version() { + local repo="$1" + gh api "repos/${repo}/releases/latest" --jq '.tag_name' | sed 's/^v//' + } + + ARGO_LATEST=$(get_latest_version "argoproj/argo-workflows") + KARGO_LATEST=$(get_latest_version "akuity/kargo") + PACK_LATEST=$(get_latest_version "buildpacks/pack") + + echo "argo=${ARGO_LATEST}" >> "$GITHUB_OUTPUT" + echo "kargo=${KARGO_LATEST}" >> "$GITHUB_OUTPUT" + echo "pack=${PACK_LATEST}" >> "$GITHUB_OUTPUT" + + ARGO_CURRENT=$(grep -oP 'ARGO_VERSION=\K[0-9.]+' Containerfile) + KARGO_CURRENT=$(grep -oP 'KARGO_VERSION=\K[0-9.]+' Containerfile) + PACK_CURRENT=$(grep -oP 'PACK_VERSION=\K[0-9.]+' Containerfile) + + echo "argo_current=${ARGO_CURRENT}" >> "$GITHUB_OUTPUT" + echo "kargo_current=${KARGO_CURRENT}" >> "$GITHUB_OUTPUT" + echo "pack_current=${PACK_CURRENT}" >> "$GITHUB_OUTPUT" + + UPDATES="" + if [ "${ARGO_CURRENT}" != "${ARGO_LATEST}" ]; then + UPDATES="${UPDATES}- Argo Workflows CLI: ${ARGO_CURRENT} -> ${ARGO_LATEST}\n" + fi + if [ "${KARGO_CURRENT}" != "${KARGO_LATEST}" ]; then + UPDATES="${UPDATES}- Kargo CLI: ${KARGO_CURRENT} -> ${KARGO_LATEST}\n" + fi + if [ "${PACK_CURRENT}" != "${PACK_LATEST}" ]; then + UPDATES="${UPDATES}- pack (Buildpacks): ${PACK_CURRENT} -> ${PACK_LATEST}\n" + fi + + if [ -z "${UPDATES}" ]; then + echo "has_updates=false" >> "$GITHUB_OUTPUT" + echo "No updates found." + else + echo "has_updates=true" >> "$GITHUB_OUTPUT" + # Use a delimiter for multiline output + { + echo "summary<> "$GITHUB_OUTPUT" + echo -e "Updates found:\n${UPDATES}" + fi + + - name: Update versions in Containerfile and manifest + if: steps.versions.outputs.has_updates == 'true' + env: + ARGO_LATEST: ${{ steps.versions.outputs.argo }} + KARGO_LATEST: ${{ steps.versions.outputs.kargo }} + PACK_LATEST: ${{ steps.versions.outputs.pack }} + ARGO_CURRENT: ${{ steps.versions.outputs.argo_current }} + KARGO_CURRENT: ${{ steps.versions.outputs.kargo_current }} + PACK_CURRENT: ${{ steps.versions.outputs.pack_current }} + run: | + if [ "${ARGO_CURRENT}" != "${ARGO_LATEST}" ]; then + sed -i "s/ARGO_VERSION=${ARGO_CURRENT}/ARGO_VERSION=${ARGO_LATEST}/g" Containerfile manifest.yaml + fi + if [ "${KARGO_CURRENT}" != "${KARGO_LATEST}" ]; then + sed -i "s/KARGO_VERSION=${KARGO_CURRENT}/KARGO_VERSION=${KARGO_LATEST}/g" Containerfile manifest.yaml + fi + if [ "${PACK_CURRENT}" != "${PACK_LATEST}" ]; then + sed -i "s/PACK_VERSION=${PACK_CURRENT}/PACK_VERSION=${PACK_LATEST}/g" Containerfile manifest.yaml + fi + + - name: Create pull request + if: steps.versions.outputs.has_updates == 'true' + uses: peter-evans/create-pull-request@v7 + with: + commit-message: "chore: update tool versions" + branch: chore/update-tool-versions + title: "chore: update tool versions" + body: | + Automated tool version updates: + + ${{ steps.versions.outputs.summary }} + labels: dependencies From 6db1c23843ca87fa27e8c57429a3912a3f50d790 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 15 Feb 2026 10:11:07 +0000 Subject: [PATCH 04/15] feat: add semantic-release pipeline with build tools and best practices - Add build tools to image (dive, trivy, buildah, yq, hadolint) so the image can build itself as a self-hosted runner - Configure buildah vfs storage driver for container/rootless usage - Create semantic-release config for automated versioning from conventional commits with changelog generation - Add release workflow: semantic-release -> buildah build -> dive filesystem scan -> trivy vulnerability scan -> skopeo push with semver tags (major, major.minor, full, latest) - Add CI workflow: commitlint, hadolint lint, and build test on PRs - Update scheduled update-tools workflow with new tools (dive, hadolint, yq) - Add best practice configs: .hadolint.yaml (trusted registries), .commitlintrc.yaml (conventional commits), .containerignore (minimal build context) https://claude.ai/code/session_01RofXXAMZxK4irobNYjYn3W --- .commitlintrc.yaml | 2 + .containerignore | 12 +++ .github/workflows/ci.yaml | 40 ++++++++ .github/workflows/release.yaml | 145 ++++++++++++++++++++++++++++ .github/workflows/update-tools.yaml | 49 ++++++++-- .hadolint.yaml | 3 + .releaserc.yaml | 14 +++ Containerfile | 48 ++++++++- manifest.yaml | 5 +- 9 files changed, 305 insertions(+), 13 deletions(-) create mode 100644 .commitlintrc.yaml create mode 100644 .containerignore create mode 100644 .github/workflows/ci.yaml create mode 100644 .github/workflows/release.yaml create mode 100644 .hadolint.yaml create mode 100644 .releaserc.yaml diff --git a/.commitlintrc.yaml b/.commitlintrc.yaml new file mode 100644 index 0000000..9cb74a7 --- /dev/null +++ b/.commitlintrc.yaml @@ -0,0 +1,2 @@ +extends: + - "@commitlint/config-conventional" diff --git a/.containerignore b/.containerignore new file mode 100644 index 0000000..824db47 --- /dev/null +++ b/.containerignore @@ -0,0 +1,12 @@ +.git +.github +build +.env +*.md +LICENSE +.dive-ci +.hadolint.yaml +.releaserc.yaml +.commitlintrc.yaml +.containerignore +scripts/ diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..97e1871 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,40 @@ +name: CI + +on: + pull_request: + branches: [master] + +permissions: + contents: read + pull-requests: read + +jobs: + commitlint: + name: Lint commit messages + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: wagoid/commitlint-github-action@v6 + + hadolint: + name: Lint Containerfile + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: hadolint/hadolint-action@v3.1.0 + with: + dockerfile: Containerfile + + build: + name: Test build + runs-on: ubuntu-latest + needs: [hadolint] + steps: + - uses: actions/checkout@v4 + + - name: Build image + run: docker build -f Containerfile -t test-build . diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..0939c83 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,145 @@ +name: Release + +on: + push: + branches: [master] + +permissions: + contents: write + packages: write + +jobs: + release: + name: Semantic release + runs-on: ubuntu-latest + outputs: + new_release_published: ${{ steps.semantic.outputs.new_release_published }} + new_release_version: ${{ steps.semantic.outputs.new_release_version }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - uses: cycjimmy/semantic-release-action@v4 + id: semantic + with: + extra_plugins: | + @semantic-release/changelog + @semantic-release/git + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + build-and-push: + name: Build, scan & push + needs: release + if: needs.release.outputs.new_release_published == 'true' + runs-on: ubuntu-latest + env: + IMAGE_VERSION: ${{ needs.release.outputs.new_release_version }} + steps: + - uses: actions/checkout@v4 + + - name: Install build tools + run: ./scripts/install_tools.sh + + - name: Read manifest + id: manifest + run: | + echo "image_name=$(yq e '.name' manifest.yaml)" >> "$GITHUB_OUTPUT" + echo "registry=$(yq e '.registry' manifest.yaml)" >> "$GITHUB_OUTPUT" + echo "format=$(yq e '.build.format' manifest.yaml)" >> "$GITHUB_OUTPUT" + + - name: Validate Containerfile + run: | + docker pull -q ghcr.io/hadolint/hadolint:latest + docker run --rm -i hadolint/hadolint:latest < Containerfile + + - name: Build image + env: + IMAGE_NAME: ${{ steps.manifest.outputs.image_name }} + IMAGE_FORMAT: ${{ steps.manifest.outputs.format }} + run: | + # Build args from manifest + BUILD_ARGS="" + for arg in $(yq e '.build.args[]' manifest.yaml); do + BUILD_ARGS="${BUILD_ARGS} --build-arg ${arg}" + done + + # Labels from manifest + LABELS="" + while IFS= read -r label; do + if [[ -n "${label}" ]]; then + label_key="${label%%=*}" + label_value="${label#*=}" + label_value="${label_value%\"}" + label_value="${label_value#\"}" + LABELS="${LABELS} --label ${label_key}=${label_value}" + fi + done < <(yq e '.build.labels[]' manifest.yaml) + + # Add version label + LABELS="${LABELS} --label org.opencontainers.image.version=${IMAGE_VERSION}" + + # shellcheck disable=SC2086 + buildah build \ + --squash \ + --pull-always \ + --format "${IMAGE_FORMAT}" \ + ${BUILD_ARGS} \ + ${LABELS} \ + --tag "${IMAGE_NAME}:${IMAGE_VERSION}" \ + . + + # Save to OCI archive for scanning and pushing + mkdir -p build + buildah push "${IMAGE_NAME}:${IMAGE_VERSION}" "oci-archive:build/${IMAGE_NAME}.tar" + + # Load into Docker daemon for dive scan + docker load -i "build/${IMAGE_NAME}.tar" 2>/dev/null || true + docker tag "$(docker images -q | head -1)" "${IMAGE_NAME}:${IMAGE_VERSION}" 2>/dev/null || true + + - name: Dive filesystem scan + env: + IMAGE_NAME: ${{ steps.manifest.outputs.image_name }} + run: dive --ci --source=docker "${IMAGE_NAME}:${IMAGE_VERSION}" + + - name: Trivy vulnerability scan + env: + IMAGE_NAME: ${{ steps.manifest.outputs.image_name }} + run: | + trivy image \ + --input "build/${IMAGE_NAME}.tar" \ + --severity HIGH,CRITICAL \ + --exit-code 1 \ + "${IMAGE_NAME}:${IMAGE_VERSION}" + + - name: Login to GHCR + env: + REGISTRY: ${{ steps.manifest.outputs.registry }} + run: skopeo login ghcr.io -u "${{ github.actor }}" -p "${{ secrets.GITHUB_TOKEN }}" + + - name: Push to registry + env: + IMAGE_NAME: ${{ steps.manifest.outputs.image_name }} + REGISTRY: ${{ steps.manifest.outputs.registry }} + run: | + MAJOR_MINOR="${IMAGE_VERSION%.*}" + MAJOR="${IMAGE_VERSION%%.*}" + + # Push semantic version tag (1.2.3) + skopeo copy --all "oci-archive:build/${IMAGE_NAME}.tar" "docker://${REGISTRY}:${IMAGE_VERSION}" + + # Push major.minor tag (1.2) + skopeo copy --all "oci-archive:build/${IMAGE_NAME}.tar" "docker://${REGISTRY}:${MAJOR_MINOR}" + + # Push major tag (1) + skopeo copy --all "oci-archive:build/${IMAGE_NAME}.tar" "docker://${REGISTRY}:${MAJOR}" + + # Push latest tag + skopeo copy --all "oci-archive:build/${IMAGE_NAME}.tar" "docker://${REGISTRY}:latest" + + - name: Verify pushed image + env: + REGISTRY: ${{ steps.manifest.outputs.registry }} + run: | + skopeo inspect "docker://${REGISTRY}:${IMAGE_VERSION}" --format '{{.Labels}}' diff --git a/.github/workflows/update-tools.yaml b/.github/workflows/update-tools.yaml index 7ae0ac6..a2f9043 100644 --- a/.github/workflows/update-tools.yaml +++ b/.github/workflows/update-tools.yaml @@ -29,18 +29,30 @@ jobs: ARGO_LATEST=$(get_latest_version "argoproj/argo-workflows") KARGO_LATEST=$(get_latest_version "akuity/kargo") PACK_LATEST=$(get_latest_version "buildpacks/pack") + DIVE_LATEST=$(get_latest_version "wagoodman/dive") + HADOLINT_LATEST=$(get_latest_version "hadolint/hadolint") + YQ_LATEST=$(get_latest_version "mikefarah/yq") echo "argo=${ARGO_LATEST}" >> "$GITHUB_OUTPUT" echo "kargo=${KARGO_LATEST}" >> "$GITHUB_OUTPUT" echo "pack=${PACK_LATEST}" >> "$GITHUB_OUTPUT" + echo "dive=${DIVE_LATEST}" >> "$GITHUB_OUTPUT" + echo "hadolint=${HADOLINT_LATEST}" >> "$GITHUB_OUTPUT" + echo "yq=${YQ_LATEST}" >> "$GITHUB_OUTPUT" ARGO_CURRENT=$(grep -oP 'ARGO_VERSION=\K[0-9.]+' Containerfile) KARGO_CURRENT=$(grep -oP 'KARGO_VERSION=\K[0-9.]+' Containerfile) PACK_CURRENT=$(grep -oP 'PACK_VERSION=\K[0-9.]+' Containerfile) + DIVE_CURRENT=$(grep -oP 'DIVE_VERSION=\K[0-9.]+' Containerfile) + HADOLINT_CURRENT=$(grep -oP 'HADOLINT_VERSION=\K[0-9.]+' Containerfile) + YQ_CURRENT=$(grep -oP 'YQ_VERSION=\K[0-9.]+' Containerfile) echo "argo_current=${ARGO_CURRENT}" >> "$GITHUB_OUTPUT" echo "kargo_current=${KARGO_CURRENT}" >> "$GITHUB_OUTPUT" echo "pack_current=${PACK_CURRENT}" >> "$GITHUB_OUTPUT" + echo "dive_current=${DIVE_CURRENT}" >> "$GITHUB_OUTPUT" + echo "hadolint_current=${HADOLINT_CURRENT}" >> "$GITHUB_OUTPUT" + echo "yq_current=${YQ_CURRENT}" >> "$GITHUB_OUTPUT" UPDATES="" if [ "${ARGO_CURRENT}" != "${ARGO_LATEST}" ]; then @@ -52,6 +64,15 @@ jobs: if [ "${PACK_CURRENT}" != "${PACK_LATEST}" ]; then UPDATES="${UPDATES}- pack (Buildpacks): ${PACK_CURRENT} -> ${PACK_LATEST}\n" fi + if [ "${DIVE_CURRENT}" != "${DIVE_LATEST}" ]; then + UPDATES="${UPDATES}- dive: ${DIVE_CURRENT} -> ${DIVE_LATEST}\n" + fi + if [ "${HADOLINT_CURRENT}" != "${HADOLINT_LATEST}" ]; then + UPDATES="${UPDATES}- hadolint: ${HADOLINT_CURRENT} -> ${HADOLINT_LATEST}\n" + fi + if [ "${YQ_CURRENT}" != "${YQ_LATEST}" ]; then + UPDATES="${UPDATES}- yq: ${YQ_CURRENT} -> ${YQ_LATEST}\n" + fi if [ -z "${UPDATES}" ]; then echo "has_updates=false" >> "$GITHUB_OUTPUT" @@ -73,19 +94,29 @@ jobs: ARGO_LATEST: ${{ steps.versions.outputs.argo }} KARGO_LATEST: ${{ steps.versions.outputs.kargo }} PACK_LATEST: ${{ steps.versions.outputs.pack }} + DIVE_LATEST: ${{ steps.versions.outputs.dive }} + HADOLINT_LATEST: ${{ steps.versions.outputs.hadolint }} + YQ_LATEST: ${{ steps.versions.outputs.yq }} ARGO_CURRENT: ${{ steps.versions.outputs.argo_current }} KARGO_CURRENT: ${{ steps.versions.outputs.kargo_current }} PACK_CURRENT: ${{ steps.versions.outputs.pack_current }} + DIVE_CURRENT: ${{ steps.versions.outputs.dive_current }} + HADOLINT_CURRENT: ${{ steps.versions.outputs.hadolint_current }} + YQ_CURRENT: ${{ steps.versions.outputs.yq_current }} run: | - if [ "${ARGO_CURRENT}" != "${ARGO_LATEST}" ]; then - sed -i "s/ARGO_VERSION=${ARGO_CURRENT}/ARGO_VERSION=${ARGO_LATEST}/g" Containerfile manifest.yaml - fi - if [ "${KARGO_CURRENT}" != "${KARGO_LATEST}" ]; then - sed -i "s/KARGO_VERSION=${KARGO_CURRENT}/KARGO_VERSION=${KARGO_LATEST}/g" Containerfile manifest.yaml - fi - if [ "${PACK_CURRENT}" != "${PACK_LATEST}" ]; then - sed -i "s/PACK_VERSION=${PACK_CURRENT}/PACK_VERSION=${PACK_LATEST}/g" Containerfile manifest.yaml - fi + update_version() { + local name="$1" current="$2" latest="$3" + if [ "${current}" != "${latest}" ]; then + sed -i "s/${name}=${current}/${name}=${latest}/g" Containerfile manifest.yaml + fi + } + + update_version "ARGO_VERSION" "${ARGO_CURRENT}" "${ARGO_LATEST}" + update_version "KARGO_VERSION" "${KARGO_CURRENT}" "${KARGO_LATEST}" + update_version "PACK_VERSION" "${PACK_CURRENT}" "${PACK_LATEST}" + update_version "DIVE_VERSION" "${DIVE_CURRENT}" "${DIVE_LATEST}" + update_version "HADOLINT_VERSION" "${HADOLINT_CURRENT}" "${HADOLINT_LATEST}" + update_version "YQ_VERSION" "${YQ_CURRENT}" "${YQ_LATEST}" - name: Create pull request if: steps.versions.outputs.has_updates == 'true' diff --git a/.hadolint.yaml b/.hadolint.yaml new file mode 100644 index 0000000..bd3f280 --- /dev/null +++ b/.hadolint.yaml @@ -0,0 +1,3 @@ +trustedRegistries: + - docker.io + - ghcr.io diff --git a/.releaserc.yaml b/.releaserc.yaml new file mode 100644 index 0000000..3dfc0b8 --- /dev/null +++ b/.releaserc.yaml @@ -0,0 +1,14 @@ +branches: + - master + +plugins: + - "@semantic-release/commit-analyzer" + - "@semantic-release/release-notes-generator" + - - "@semantic-release/changelog" + - changelogFile: CHANGELOG.md + - - "@semantic-release/github" + - assets: [] + - - "@semantic-release/git" + - assets: + - CHANGELOG.md + message: "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" diff --git a/Containerfile b/Containerfile index a045ac8..136c78d 100644 --- a/Containerfile +++ b/Containerfile @@ -32,6 +32,51 @@ RUN apt-get update \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* +# Install buildah +# hadolint ignore=DL3008 +RUN apt-get update \ + && apt-get install --no-install-recommends -y buildah \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Configure buildah storage for container/rootless usage +RUN mkdir -p /etc/containers \ + && printf '[storage]\ndriver = "vfs"\n' > /etc/containers/storage.conf + +# Install trivy (vulnerability scanner) +# hadolint ignore=DL3008,DL4006 +RUN curl -fsSL https://aquasecurity.github.io/trivy-repo/deb/public.key \ + | gpg --dearmor -o /usr/share/keyrings/trivy.gpg \ + && echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main" \ + | tee /etc/apt/sources.list.d/trivy.list \ + && apt-get update \ + && apt-get install --no-install-recommends -y trivy \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Install dive (container filesystem analysis) +ARG DIVE_VERSION=0.12.0 +# hadolint ignore=DL3008 +RUN curl -sSL -o /tmp/dive.deb \ + "https://github.com/wagoodman/dive/releases/download/v${DIVE_VERSION}/dive_${DIVE_VERSION}_linux_amd64.deb" \ + && apt-get update \ + && apt-get install --no-install-recommends -y /tmp/dive.deb \ + && rm /tmp/dive.deb \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Install hadolint (Dockerfile/Containerfile linter) +ARG HADOLINT_VERSION=2.12.0 +RUN curl -sSL -o /usr/local/bin/hadolint \ + "https://github.com/hadolint/hadolint/releases/download/v${HADOLINT_VERSION}/hadolint-Linux-x86_64" \ + && chmod +x /usr/local/bin/hadolint + +# Install yq (YAML processor) +ARG YQ_VERSION=4.45.4 +RUN curl -sSL -o /usr/local/bin/yq \ + "https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/yq_linux_amd64" \ + && chmod +x /usr/local/bin/yq + # Install Argo Workflows CLI ARG ARGO_VERSION=3.6.4 RUN curl -sSL -o /tmp/argo-linux-amd64.gz \ @@ -85,6 +130,3 @@ RUN curl -LsSf https://astral.sh/uv/install.sh | sh # Add Poetry and UV to PATH RUN echo "export PATH=\"${APP_HOME}/.local/bin:\$PATH\"" >> ~/.bashrc - -# Placeholder command to keep the container running -# CMD ["/bin/bash", "-c", "while true; do sleep 1; done"] diff --git a/manifest.yaml b/manifest.yaml index 2d8063b..7774c8e 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -1,5 +1,5 @@ name: python-github-runner -tags: +tags: - latest registry: ghcr.io/deerhide/python-github-runner build: @@ -9,6 +9,9 @@ build: - ARGO_VERSION=3.6.4 - KARGO_VERSION=1.9.2 - PACK_VERSION=0.36.4 + - DIVE_VERSION=0.12.0 + - HADOLINT_VERSION=2.12.0 + - YQ_VERSION=4.45.4 labels: - org.opencontainers.image.source=https://github.com/deerhide/python-github-runner - org.opencontainers.image.description="Python GitHub Runner" From 8c2eb939c491e980352eda5058a82f68f67c26b4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 15 Feb 2026 10:13:12 +0000 Subject: [PATCH 05/15] docs: rewrite README with current tooling, CI/CD, and project structure https://claude.ai/code/session_01RofXXAMZxK4irobNYjYn3W --- README.md | 158 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 144 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 4075017..0c0905f 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,170 @@ -# Deerhide / Template for Container Image +# Deerhide / Python GitHub Runner -## Pre-requisites +Container image based on the [GitHub Actions Runner](https://github.com/actions/runner) with Python tooling, DevOps CLIs, and a full container build pipeline baked in. Designed to be used as a self-hosted runner that can build itself. -### Install `docker` -[See Docker documentation](https://docs.docker.com/get-docker/) +## What's included -### Install tools +### Base image + +`ghcr.io/actions/runner` (GitHub Actions Runner) + +### Python + +| Tool | Version | +|------|---------| +| Python | 3.12, 3.13, 3.14 (via deadsnakes PPA) | +| Poetry | latest | +| UV | latest | + +### DevOps CLIs + +| Tool | Description | +|------|-------------| +| [Argo Workflows CLI](https://github.com/argoproj/argo-workflows) | Workflow orchestration on Kubernetes | +| [Kargo CLI](https://github.com/akuity/kargo) | Application lifecycle orchestration | +| [pack](https://github.com/buildpacks/pack) | Cloud Native Buildpacks CLI | +| [skopeo](https://github.com/containers/skopeo) | Container image registry operations | + +### Build pipeline tools + +These tools allow the image to run its own build pipeline as a self-hosted runner. + +| Tool | Description | +|------|-------------| +| [buildah](https://github.com/containers/buildah) | OCI container image builder | +| [dive](https://github.com/wagoodman/dive) | Container filesystem analysis | +| [trivy](https://github.com/aquasecurity/trivy) | Vulnerability scanner | +| [hadolint](https://github.com/hadolint/hadolint) | Dockerfile/Containerfile linter | +| [yq](https://github.com/mikefarah/yq) | YAML processor | + +## CI/CD + +### Workflows + +| Workflow | Trigger | Description | +|----------|---------|-------------| +| **CI** | Pull request to `master` | Commitlint, hadolint lint, test build | +| **Release** | Push to `master` | Semantic release, build, scan, push to GHCR | +| **Update tools** | Weekly (Monday 08:00 UTC) / manual | Checks for new tool versions, opens a PR | + +### Release process + +Releases are fully automated via [semantic-release](https://github.com/semantic-release/semantic-release). Pushing to `master` triggers version analysis based on [Conventional Commits](https://www.conventionalcommits.org/): + +| Commit prefix | Version bump | +|---------------|-------------| +| `fix:` | Patch (1.0.0 -> 1.0.1) | +| `feat:` | Minor (1.0.0 -> 1.1.0) | +| `feat!:` / `BREAKING CHANGE:` | Major (1.0.0 -> 2.0.0) | + +When a new version is determined, the release workflow: + +1. Creates a GitHub release with auto-generated notes +2. Updates `CHANGELOG.md` +3. Builds the image with `buildah` (OCI format, squashed layers) +4. Runs `hadolint` lint validation +5. Runs `dive` filesystem efficiency scan +6. Runs `trivy` vulnerability scan (HIGH/CRITICAL) +7. Pushes to GHCR with semver tags: `1.2.3`, `1.2`, `1`, `latest` + +### Image tags + +``` +ghcr.io/deerhide/python-github-runner:latest +ghcr.io/deerhide/python-github-runner:1 +ghcr.io/deerhide/python-github-runner:1.2 +ghcr.io/deerhide/python-github-runner:1.2.3 +``` + +## Local development + +### Pre-requisites + +Install [Docker](https://docs.docker.com/get-docker/), then install the build tools: ```bash ./scripts/install_tools.sh ``` -## How to build the container image +### Configuration -### Update `manifest.yaml` +Build configuration is defined in `manifest.yaml`: ```yaml -name: deerhide_container_example -tags: +name: python-github-runner +tags: - latest -registry: ghcr.io/deerhide/template_container_image +registry: ghcr.io/deerhide/python-github-runner build: format: oci args: - - APP_UID=1000 - - UBUNTU_VERSION=24.04 + - RUNNER_VERSION=latest + - ARGO_VERSION=3.6.4 + - KARGO_VERSION=1.9.2 + - PACK_VERSION=0.36.4 + - DIVE_VERSION=0.12.0 + - HADOLINT_VERSION=2.12.0 + - YQ_VERSION=4.45.4 + labels: + - org.opencontainers.image.source=https://github.com/deerhide/python-github-runner + - org.opencontainers.image.description="Python GitHub Runner" + - org.opencontainers.image.licenses="MIT" + - org.opencontainers.image.authors="Deerhide" + - org.opencontainers.image.vendor="Deerhide" ``` -### Authenticate to the container registry +### Build + +Authenticate to the container registry: ```bash skopeo login ghcr.io ``` -### Launch Builder +Run the full build pipeline (lint, build, scan, push): ```bash ./scripts/builder.sh ``` + +### Contributing + +This project uses [Conventional Commits](https://www.conventionalcommits.org/). Commit messages are validated by commitlint on pull requests. + +```bash +# Good +git commit -m "feat: add kubectl to image" +git commit -m "fix: correct trivy scan exit code" +git commit -m "chore: update argo to v3.7.0" + +# Bad +git commit -m "added stuff" +git commit -m "WIP" +``` + +## Project structure + +``` +. +├── Containerfile # Multi-stage container definition +├── manifest.yaml # Build configuration and metadata +├── .releaserc.yaml # Semantic release configuration +├── .hadolint.yaml # Hadolint configuration +├── .commitlintrc.yaml # Commitlint configuration +├── .containerignore # Build context exclusions +├── .dive-ci # Dive efficiency thresholds +├── .github/ +│ └── workflows/ +│ ├── ci.yaml # PR validation +│ ├── release.yaml # Semantic release + build + push +│ └── update-tools.yaml # Automated tool version updates +└── scripts/ + ├── builder.sh # Local build orchestration + ├── install_tools.sh # Build tool installer + ├── lib_utils.sh # Logging utilities + └── login_skopeo.sh # Registry authentication helper +``` + +## License + +[MIT](LICENSE) From ea59872e5ea5a21d5f2198d2988fa76f46a2001c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 15 Feb 2026 10:17:38 +0000 Subject: [PATCH 06/15] feat: add pre-commit with hadolint, shellcheck, and commitlint hooks Install pre-commit in the container image and add .pre-commit-config.yaml with hooks for trailing whitespace, YAML validation, hadolint, shellcheck, and commitlint. https://claude.ai/code/session_01RofXXAMZxK4irobNYjYn3W --- .containerignore | 1 + .pre-commit-config.yaml | 30 ++++++++++++++++++++++++++++++ Containerfile | 4 ++++ README.md | 32 +++++++++++++++++++++++++++++++- 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 .pre-commit-config.yaml diff --git a/.containerignore b/.containerignore index 824db47..fc87576 100644 --- a/.containerignore +++ b/.containerignore @@ -9,4 +9,5 @@ LICENSE .releaserc.yaml .commitlintrc.yaml .containerignore +.pre-commit-config.yaml scripts/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b71bb13 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,30 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - id: check-merge-conflict + - id: detect-private-key + + - repo: https://github.com/hadolint/hadolint + rev: v2.12.0 + hooks: + - id: hadolint-docker + entry: hadolint/hadolint hadolint + args: ["--config", ".hadolint.yaml"] + + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: v0.10.0.1 + hooks: + - id: shellcheck + args: ["-e", "SC1091"] + + - repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook + rev: v9.21.0 + hooks: + - id: commitlint + stages: [commit-msg] + additional_dependencies: ["@commitlint/config-conventional"] diff --git a/Containerfile b/Containerfile index 136c78d..dacc042 100644 --- a/Containerfile +++ b/Containerfile @@ -98,6 +98,10 @@ RUN curl -sSL -o /tmp/pack.tgz \ && tar -xzf /tmp/pack.tgz -C /usr/local/bin/ \ && rm /tmp/pack.tgz +# Install pre-commit +# hadolint ignore=DL3013 +RUN pip install --no-cache-dir pre-commit + # Install Poetry latest version and add it to PATH # hadolint ignore=DL4006 RUN curl -sSL https://install.python-poetry.org | python3 - diff --git a/README.md b/README.md index 0c0905f..96aac0d 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ These tools allow the image to run its own build pipeline as a self-hosted runne | [trivy](https://github.com/aquasecurity/trivy) | Vulnerability scanner | | [hadolint](https://github.com/hadolint/hadolint) | Dockerfile/Containerfile linter | | [yq](https://github.com/mikefarah/yq) | YAML processor | +| [pre-commit](https://pre-commit.com/) | Git hooks framework | ## CI/CD @@ -127,9 +128,37 @@ Run the full build pipeline (lint, build, scan, push): ./scripts/builder.sh ``` +### Pre-commit hooks + +Install the git hooks locally: + +```bash +pre-commit install --hook-type pre-commit --hook-type commit-msg +``` + +Hooks run automatically on every commit: + +| Hook | Stage | Description | +|------|-------|-------------| +| trailing-whitespace | pre-commit | Remove trailing whitespace | +| end-of-file-fixer | pre-commit | Ensure files end with a newline | +| check-yaml | pre-commit | Validate YAML syntax | +| check-added-large-files | pre-commit | Prevent large files from being committed | +| check-merge-conflict | pre-commit | Detect merge conflict markers | +| detect-private-key | pre-commit | Prevent private keys from being committed | +| hadolint | pre-commit | Lint Containerfile | +| shellcheck | pre-commit | Lint shell scripts | +| commitlint | commit-msg | Validate conventional commit messages | + +Run all hooks manually against all files: + +```bash +pre-commit run --all-files +``` + ### Contributing -This project uses [Conventional Commits](https://www.conventionalcommits.org/). Commit messages are validated by commitlint on pull requests. +This project uses [Conventional Commits](https://www.conventionalcommits.org/). Commit messages are validated by commitlint on pull requests and locally via pre-commit hooks. ```bash # Good @@ -151,6 +180,7 @@ git commit -m "WIP" ├── .releaserc.yaml # Semantic release configuration ├── .hadolint.yaml # Hadolint configuration ├── .commitlintrc.yaml # Commitlint configuration +├── .pre-commit-config.yaml # Pre-commit hooks configuration ├── .containerignore # Build context exclusions ├── .dive-ci # Dive efficiency thresholds ├── .github/ From a178c21fb18a4bbb69bfad5a0e08d479fdedcc6a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 19 Feb 2026 06:51:02 +0000 Subject: [PATCH 07/15] fix: address PR review feedback - Pin RUNNER_VERSION to 2.321.0 instead of latest for reproducible builds - Combine skopeo and buildah into a single RUN/apt-get layer - Remove silent || true from docker load/tag in release workflow - Mount .hadolint.yaml in release workflow validation step - Fix semver parsing with proper validation in release push step - Pass manifest build args in CI test build to match release build - Fix malformed hadolint pre-commit hook entry - Track RUNNER_VERSION in update-tools workflow - Remove duplicate Poetry/UV installs from base stage (only needed for runner user) https://claude.ai/code/session_01RofXXAMZxK4irobNYjYn3W --- .github/workflows/ci.yaml | 13 ++++++++++++- .github/workflows/release.yaml | 15 +++++++++------ .github/workflows/update-tools.yaml | 10 ++++++++++ .pre-commit-config.yaml | 1 - Containerfile | 24 +++--------------------- manifest.yaml | 2 +- 6 files changed, 35 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 97e1871..ad2e43d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -36,5 +36,16 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Install yq + run: | + sudo curl -sSL -o /usr/local/bin/yq "https://github.com/mikefarah/yq/releases/download/v4.45.4/yq_linux_amd64" + sudo chmod +x /usr/local/bin/yq + - name: Build image - run: docker build -f Containerfile -t test-build . + run: | + BUILD_ARGS="" + for arg in $(yq e '.build.args[]' manifest.yaml); do + BUILD_ARGS="${BUILD_ARGS} --build-arg ${arg}" + done + # shellcheck disable=SC2086 + docker build -f Containerfile ${BUILD_ARGS} -t test-build . diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 0939c83..0beee3a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -52,7 +52,7 @@ jobs: - name: Validate Containerfile run: | docker pull -q ghcr.io/hadolint/hadolint:latest - docker run --rm -i hadolint/hadolint:latest < Containerfile + docker run --rm -i -v "$(pwd)/.hadolint.yaml:/.hadolint.yaml:ro" hadolint/hadolint:latest hadolint --config /.hadolint.yaml - < Containerfile - name: Build image env: @@ -95,8 +95,8 @@ jobs: buildah push "${IMAGE_NAME}:${IMAGE_VERSION}" "oci-archive:build/${IMAGE_NAME}.tar" # Load into Docker daemon for dive scan - docker load -i "build/${IMAGE_NAME}.tar" 2>/dev/null || true - docker tag "$(docker images -q | head -1)" "${IMAGE_NAME}:${IMAGE_VERSION}" 2>/dev/null || true + docker load -i "build/${IMAGE_NAME}.tar" + docker tag "$(docker images -q | head -1)" "${IMAGE_NAME}:${IMAGE_VERSION}" - name: Dive filesystem scan env: @@ -123,14 +123,17 @@ jobs: IMAGE_NAME: ${{ steps.manifest.outputs.image_name }} REGISTRY: ${{ steps.manifest.outputs.registry }} run: | - MAJOR_MINOR="${IMAGE_VERSION%.*}" - MAJOR="${IMAGE_VERSION%%.*}" + IFS='.' read -r MAJOR MINOR PATCH <<< "${IMAGE_VERSION}" + if [ -z "${MAJOR}" ] || [ -z "${MINOR}" ] || [ -z "${PATCH}" ]; then + echo "Error: IMAGE_VERSION '${IMAGE_VERSION}' is not valid semver (expected MAJOR.MINOR.PATCH)" + exit 1 + fi # Push semantic version tag (1.2.3) skopeo copy --all "oci-archive:build/${IMAGE_NAME}.tar" "docker://${REGISTRY}:${IMAGE_VERSION}" # Push major.minor tag (1.2) - skopeo copy --all "oci-archive:build/${IMAGE_NAME}.tar" "docker://${REGISTRY}:${MAJOR_MINOR}" + skopeo copy --all "oci-archive:build/${IMAGE_NAME}.tar" "docker://${REGISTRY}:${MAJOR}.${MINOR}" # Push major tag (1) skopeo copy --all "oci-archive:build/${IMAGE_NAME}.tar" "docker://${REGISTRY}:${MAJOR}" diff --git a/.github/workflows/update-tools.yaml b/.github/workflows/update-tools.yaml index a2f9043..e8fcf33 100644 --- a/.github/workflows/update-tools.yaml +++ b/.github/workflows/update-tools.yaml @@ -26,6 +26,7 @@ jobs: gh api "repos/${repo}/releases/latest" --jq '.tag_name' | sed 's/^v//' } + RUNNER_LATEST=$(get_latest_version "actions/runner") ARGO_LATEST=$(get_latest_version "argoproj/argo-workflows") KARGO_LATEST=$(get_latest_version "akuity/kargo") PACK_LATEST=$(get_latest_version "buildpacks/pack") @@ -33,6 +34,7 @@ jobs: HADOLINT_LATEST=$(get_latest_version "hadolint/hadolint") YQ_LATEST=$(get_latest_version "mikefarah/yq") + echo "runner=${RUNNER_LATEST}" >> "$GITHUB_OUTPUT" echo "argo=${ARGO_LATEST}" >> "$GITHUB_OUTPUT" echo "kargo=${KARGO_LATEST}" >> "$GITHUB_OUTPUT" echo "pack=${PACK_LATEST}" >> "$GITHUB_OUTPUT" @@ -40,6 +42,7 @@ jobs: echo "hadolint=${HADOLINT_LATEST}" >> "$GITHUB_OUTPUT" echo "yq=${YQ_LATEST}" >> "$GITHUB_OUTPUT" + RUNNER_CURRENT=$(grep -oP 'RUNNER_VERSION=\K[0-9.]+' Containerfile) ARGO_CURRENT=$(grep -oP 'ARGO_VERSION=\K[0-9.]+' Containerfile) KARGO_CURRENT=$(grep -oP 'KARGO_VERSION=\K[0-9.]+' Containerfile) PACK_CURRENT=$(grep -oP 'PACK_VERSION=\K[0-9.]+' Containerfile) @@ -47,6 +50,7 @@ jobs: HADOLINT_CURRENT=$(grep -oP 'HADOLINT_VERSION=\K[0-9.]+' Containerfile) YQ_CURRENT=$(grep -oP 'YQ_VERSION=\K[0-9.]+' Containerfile) + echo "runner_current=${RUNNER_CURRENT}" >> "$GITHUB_OUTPUT" echo "argo_current=${ARGO_CURRENT}" >> "$GITHUB_OUTPUT" echo "kargo_current=${KARGO_CURRENT}" >> "$GITHUB_OUTPUT" echo "pack_current=${PACK_CURRENT}" >> "$GITHUB_OUTPUT" @@ -55,6 +59,9 @@ jobs: echo "yq_current=${YQ_CURRENT}" >> "$GITHUB_OUTPUT" UPDATES="" + if [ "${RUNNER_CURRENT}" != "${RUNNER_LATEST}" ]; then + UPDATES="${UPDATES}- GitHub Actions Runner: ${RUNNER_CURRENT} -> ${RUNNER_LATEST}\n" + fi if [ "${ARGO_CURRENT}" != "${ARGO_LATEST}" ]; then UPDATES="${UPDATES}- Argo Workflows CLI: ${ARGO_CURRENT} -> ${ARGO_LATEST}\n" fi @@ -91,12 +98,14 @@ jobs: - name: Update versions in Containerfile and manifest if: steps.versions.outputs.has_updates == 'true' env: + RUNNER_LATEST: ${{ steps.versions.outputs.runner }} ARGO_LATEST: ${{ steps.versions.outputs.argo }} KARGO_LATEST: ${{ steps.versions.outputs.kargo }} PACK_LATEST: ${{ steps.versions.outputs.pack }} DIVE_LATEST: ${{ steps.versions.outputs.dive }} HADOLINT_LATEST: ${{ steps.versions.outputs.hadolint }} YQ_LATEST: ${{ steps.versions.outputs.yq }} + RUNNER_CURRENT: ${{ steps.versions.outputs.runner_current }} ARGO_CURRENT: ${{ steps.versions.outputs.argo_current }} KARGO_CURRENT: ${{ steps.versions.outputs.kargo_current }} PACK_CURRENT: ${{ steps.versions.outputs.pack_current }} @@ -111,6 +120,7 @@ jobs: fi } + update_version "RUNNER_VERSION" "${RUNNER_CURRENT}" "${RUNNER_LATEST}" update_version "ARGO_VERSION" "${ARGO_CURRENT}" "${ARGO_LATEST}" update_version "KARGO_VERSION" "${KARGO_CURRENT}" "${KARGO_LATEST}" update_version "PACK_VERSION" "${PACK_CURRENT}" "${PACK_LATEST}" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b71bb13..31aa69d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,6 @@ repos: rev: v2.12.0 hooks: - id: hadolint-docker - entry: hadolint/hadolint hadolint args: ["--config", ".hadolint.yaml"] - repo: https://github.com/shellcheck-py/shellcheck-py diff --git a/Containerfile b/Containerfile index dacc042..3875abf 100644 --- a/Containerfile +++ b/Containerfile @@ -1,4 +1,4 @@ -ARG RUNNER_VERSION=latest +ARG RUNNER_VERSION=2.321.0 FROM ghcr.io/actions/runner:${RUNNER_VERSION} as base @@ -25,17 +25,10 @@ RUN apt-get update \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -# Install skopeo +# Install skopeo and buildah # hadolint ignore=DL3008 RUN apt-get update \ - && apt-get install --no-install-recommends -y skopeo \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -# Install buildah -# hadolint ignore=DL3008 -RUN apt-get update \ - && apt-get install --no-install-recommends -y buildah \ + && apt-get install --no-install-recommends -y skopeo buildah \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* @@ -102,17 +95,6 @@ RUN curl -sSL -o /tmp/pack.tgz \ # hadolint ignore=DL3013 RUN pip install --no-cache-dir pre-commit -# Install Poetry latest version and add it to PATH -# hadolint ignore=DL4006 -RUN curl -sSL https://install.python-poetry.org | python3 - - -# Install UV -# hadolint ignore=DL4006 -RUN curl -LsSf https://astral.sh/uv/install.sh | sh - -# Add Poetry and UV to PATH -RUN echo "export PATH=\"${APP_HOME}/.local/bin:\$PATH\"" >> ~/.bashrc - FROM base as runtime LABEL org.opencontainers.image.source=https://github.com/deerhide/python-github-runner diff --git a/manifest.yaml b/manifest.yaml index 7774c8e..111c6ee 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -5,7 +5,7 @@ registry: ghcr.io/deerhide/python-github-runner build: format: oci args: - - RUNNER_VERSION=latest + - RUNNER_VERSION=2.321.0 - ARGO_VERSION=3.6.4 - KARGO_VERSION=1.9.2 - PACK_VERSION=0.36.4 From ba84de91c92b58218faad11667bb00dc509c866c Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 19 Feb 2026 06:52:32 +0000 Subject: [PATCH 08/15] feat: replace custom update-tools workflow with Renovate Replace the hand-rolled update-tools.yaml GitHub Actions workflow with a Renovate configuration. Renovate handles version updates for all tools (Runner, Argo, Kargo, pack, dive, hadolint, yq) across both Containerfile and manifest.yaml via regex custom managers. It also natively handles GitHub Actions and pre-commit hook updates. https://claude.ai/code/session_01RofXXAMZxK4irobNYjYn3W --- .github/workflows/update-tools.yaml | 142 ---------------------------- renovate.json | 71 ++++++++++++++ 2 files changed, 71 insertions(+), 142 deletions(-) delete mode 100644 .github/workflows/update-tools.yaml create mode 100644 renovate.json diff --git a/.github/workflows/update-tools.yaml b/.github/workflows/update-tools.yaml deleted file mode 100644 index e8fcf33..0000000 --- a/.github/workflows/update-tools.yaml +++ /dev/null @@ -1,142 +0,0 @@ -name: Update tool versions - -on: - schedule: - - cron: "0 8 * * 1" # Every Monday at 08:00 UTC - workflow_dispatch: - -permissions: - contents: write - pull-requests: write - -jobs: - update-tools: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Fetch latest tool versions - id: versions - env: - GH_TOKEN: ${{ github.token }} - run: | - get_latest_version() { - local repo="$1" - gh api "repos/${repo}/releases/latest" --jq '.tag_name' | sed 's/^v//' - } - - RUNNER_LATEST=$(get_latest_version "actions/runner") - ARGO_LATEST=$(get_latest_version "argoproj/argo-workflows") - KARGO_LATEST=$(get_latest_version "akuity/kargo") - PACK_LATEST=$(get_latest_version "buildpacks/pack") - DIVE_LATEST=$(get_latest_version "wagoodman/dive") - HADOLINT_LATEST=$(get_latest_version "hadolint/hadolint") - YQ_LATEST=$(get_latest_version "mikefarah/yq") - - echo "runner=${RUNNER_LATEST}" >> "$GITHUB_OUTPUT" - echo "argo=${ARGO_LATEST}" >> "$GITHUB_OUTPUT" - echo "kargo=${KARGO_LATEST}" >> "$GITHUB_OUTPUT" - echo "pack=${PACK_LATEST}" >> "$GITHUB_OUTPUT" - echo "dive=${DIVE_LATEST}" >> "$GITHUB_OUTPUT" - echo "hadolint=${HADOLINT_LATEST}" >> "$GITHUB_OUTPUT" - echo "yq=${YQ_LATEST}" >> "$GITHUB_OUTPUT" - - RUNNER_CURRENT=$(grep -oP 'RUNNER_VERSION=\K[0-9.]+' Containerfile) - ARGO_CURRENT=$(grep -oP 'ARGO_VERSION=\K[0-9.]+' Containerfile) - KARGO_CURRENT=$(grep -oP 'KARGO_VERSION=\K[0-9.]+' Containerfile) - PACK_CURRENT=$(grep -oP 'PACK_VERSION=\K[0-9.]+' Containerfile) - DIVE_CURRENT=$(grep -oP 'DIVE_VERSION=\K[0-9.]+' Containerfile) - HADOLINT_CURRENT=$(grep -oP 'HADOLINT_VERSION=\K[0-9.]+' Containerfile) - YQ_CURRENT=$(grep -oP 'YQ_VERSION=\K[0-9.]+' Containerfile) - - echo "runner_current=${RUNNER_CURRENT}" >> "$GITHUB_OUTPUT" - echo "argo_current=${ARGO_CURRENT}" >> "$GITHUB_OUTPUT" - echo "kargo_current=${KARGO_CURRENT}" >> "$GITHUB_OUTPUT" - echo "pack_current=${PACK_CURRENT}" >> "$GITHUB_OUTPUT" - echo "dive_current=${DIVE_CURRENT}" >> "$GITHUB_OUTPUT" - echo "hadolint_current=${HADOLINT_CURRENT}" >> "$GITHUB_OUTPUT" - echo "yq_current=${YQ_CURRENT}" >> "$GITHUB_OUTPUT" - - UPDATES="" - if [ "${RUNNER_CURRENT}" != "${RUNNER_LATEST}" ]; then - UPDATES="${UPDATES}- GitHub Actions Runner: ${RUNNER_CURRENT} -> ${RUNNER_LATEST}\n" - fi - if [ "${ARGO_CURRENT}" != "${ARGO_LATEST}" ]; then - UPDATES="${UPDATES}- Argo Workflows CLI: ${ARGO_CURRENT} -> ${ARGO_LATEST}\n" - fi - if [ "${KARGO_CURRENT}" != "${KARGO_LATEST}" ]; then - UPDATES="${UPDATES}- Kargo CLI: ${KARGO_CURRENT} -> ${KARGO_LATEST}\n" - fi - if [ "${PACK_CURRENT}" != "${PACK_LATEST}" ]; then - UPDATES="${UPDATES}- pack (Buildpacks): ${PACK_CURRENT} -> ${PACK_LATEST}\n" - fi - if [ "${DIVE_CURRENT}" != "${DIVE_LATEST}" ]; then - UPDATES="${UPDATES}- dive: ${DIVE_CURRENT} -> ${DIVE_LATEST}\n" - fi - if [ "${HADOLINT_CURRENT}" != "${HADOLINT_LATEST}" ]; then - UPDATES="${UPDATES}- hadolint: ${HADOLINT_CURRENT} -> ${HADOLINT_LATEST}\n" - fi - if [ "${YQ_CURRENT}" != "${YQ_LATEST}" ]; then - UPDATES="${UPDATES}- yq: ${YQ_CURRENT} -> ${YQ_LATEST}\n" - fi - - if [ -z "${UPDATES}" ]; then - echo "has_updates=false" >> "$GITHUB_OUTPUT" - echo "No updates found." - else - echo "has_updates=true" >> "$GITHUB_OUTPUT" - # Use a delimiter for multiline output - { - echo "summary<> "$GITHUB_OUTPUT" - echo -e "Updates found:\n${UPDATES}" - fi - - - name: Update versions in Containerfile and manifest - if: steps.versions.outputs.has_updates == 'true' - env: - RUNNER_LATEST: ${{ steps.versions.outputs.runner }} - ARGO_LATEST: ${{ steps.versions.outputs.argo }} - KARGO_LATEST: ${{ steps.versions.outputs.kargo }} - PACK_LATEST: ${{ steps.versions.outputs.pack }} - DIVE_LATEST: ${{ steps.versions.outputs.dive }} - HADOLINT_LATEST: ${{ steps.versions.outputs.hadolint }} - YQ_LATEST: ${{ steps.versions.outputs.yq }} - RUNNER_CURRENT: ${{ steps.versions.outputs.runner_current }} - ARGO_CURRENT: ${{ steps.versions.outputs.argo_current }} - KARGO_CURRENT: ${{ steps.versions.outputs.kargo_current }} - PACK_CURRENT: ${{ steps.versions.outputs.pack_current }} - DIVE_CURRENT: ${{ steps.versions.outputs.dive_current }} - HADOLINT_CURRENT: ${{ steps.versions.outputs.hadolint_current }} - YQ_CURRENT: ${{ steps.versions.outputs.yq_current }} - run: | - update_version() { - local name="$1" current="$2" latest="$3" - if [ "${current}" != "${latest}" ]; then - sed -i "s/${name}=${current}/${name}=${latest}/g" Containerfile manifest.yaml - fi - } - - update_version "RUNNER_VERSION" "${RUNNER_CURRENT}" "${RUNNER_LATEST}" - update_version "ARGO_VERSION" "${ARGO_CURRENT}" "${ARGO_LATEST}" - update_version "KARGO_VERSION" "${KARGO_CURRENT}" "${KARGO_LATEST}" - update_version "PACK_VERSION" "${PACK_CURRENT}" "${PACK_LATEST}" - update_version "DIVE_VERSION" "${DIVE_CURRENT}" "${DIVE_LATEST}" - update_version "HADOLINT_VERSION" "${HADOLINT_CURRENT}" "${HADOLINT_LATEST}" - update_version "YQ_VERSION" "${YQ_CURRENT}" "${YQ_LATEST}" - - - name: Create pull request - if: steps.versions.outputs.has_updates == 'true' - uses: peter-evans/create-pull-request@v7 - with: - commit-message: "chore: update tool versions" - branch: chore/update-tool-versions - title: "chore: update tool versions" - body: | - Automated tool version updates: - - ${{ steps.versions.outputs.summary }} - labels: dependencies diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..33e4886 --- /dev/null +++ b/renovate.json @@ -0,0 +1,71 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended" + ], + "customManagers": [ + { + "customType": "regex", + "description": "Update GitHub Actions Runner version", + "fileMatch": ["^Containerfile$", "^manifest\\.yaml$"], + "matchStrings": ["RUNNER_VERSION=(?\\S+)"], + "depNameTemplate": "actions/runner", + "datasourceTemplate": "github-releases", + "extractVersionTemplate": "^v?(?.+)$" + }, + { + "customType": "regex", + "description": "Update Dive version", + "fileMatch": ["^Containerfile$", "^manifest\\.yaml$"], + "matchStrings": ["DIVE_VERSION=(?\\S+)"], + "depNameTemplate": "wagoodman/dive", + "datasourceTemplate": "github-releases", + "extractVersionTemplate": "^v?(?.+)$" + }, + { + "customType": "regex", + "description": "Update Hadolint version", + "fileMatch": ["^Containerfile$", "^manifest\\.yaml$"], + "matchStrings": ["HADOLINT_VERSION=(?\\S+)"], + "depNameTemplate": "hadolint/hadolint", + "datasourceTemplate": "github-releases", + "extractVersionTemplate": "^v?(?.+)$" + }, + { + "customType": "regex", + "description": "Update yq version", + "fileMatch": ["^Containerfile$", "^manifest\\.yaml$"], + "matchStrings": ["YQ_VERSION=(?\\S+)"], + "depNameTemplate": "mikefarah/yq", + "datasourceTemplate": "github-releases", + "extractVersionTemplate": "^v?(?.+)$" + }, + { + "customType": "regex", + "description": "Update Argo Workflows CLI version", + "fileMatch": ["^Containerfile$", "^manifest\\.yaml$"], + "matchStrings": ["ARGO_VERSION=(?\\S+)"], + "depNameTemplate": "argoproj/argo-workflows", + "datasourceTemplate": "github-releases", + "extractVersionTemplate": "^v?(?.+)$" + }, + { + "customType": "regex", + "description": "Update Kargo CLI version", + "fileMatch": ["^Containerfile$", "^manifest\\.yaml$"], + "matchStrings": ["KARGO_VERSION=(?\\S+)"], + "depNameTemplate": "akuity/kargo", + "datasourceTemplate": "github-releases", + "extractVersionTemplate": "^v?(?.+)$" + }, + { + "customType": "regex", + "description": "Update pack (Buildpacks) CLI version", + "fileMatch": ["^Containerfile$", "^manifest\\.yaml$"], + "matchStrings": ["PACK_VERSION=(?\\S+)"], + "depNameTemplate": "buildpacks/pack", + "datasourceTemplate": "github-releases", + "extractVersionTemplate": "^v?(?.+)$" + } + ] +} From d1e532f82656a860305a99ce2f8acbad3ef1339b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 19 Feb 2026 07:25:18 +0000 Subject: [PATCH 09/15] fix: address PR review feedback - Switch all branch references from `master` to `main` to match the repo's actual default branch (ci.yaml, release.yaml, .releaserc.yaml, README.md). Without this, CI won't trigger on PRs and releases won't run on merge. - Replace nondeterministic `docker load | docker tag $(head -1)` with `skopeo copy oci-archive:... docker-daemon:...` for a deterministic image load into the Docker daemon. - Combine the first three apt-get RUN layers (system upgrade, deadsnakes Python, skopeo/buildah) into a single layer to reduce image size and redundant apt-get update calls. - Fix README: pin RUNNER_VERSION=2.321.0 in the example manifest (was `latest`), replace update-tools workflow reference with Renovate, update project structure tree. https://claude.ai/code/session_01RofXXAMZxK4irobNYjYn3W --- .github/workflows/ci.yaml | 2 +- .github/workflows/release.yaml | 5 ++--- .releaserc.yaml | 2 +- Containerfile | 24 ++++++------------------ README.md | 14 +++++++------- 5 files changed, 17 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ad2e43d..c95b179 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2,7 +2,7 @@ name: CI on: pull_request: - branches: [master] + branches: [main] permissions: contents: read diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 0beee3a..73d9f06 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -2,7 +2,7 @@ name: Release on: push: - branches: [master] + branches: [main] permissions: contents: write @@ -95,8 +95,7 @@ jobs: buildah push "${IMAGE_NAME}:${IMAGE_VERSION}" "oci-archive:build/${IMAGE_NAME}.tar" # Load into Docker daemon for dive scan - docker load -i "build/${IMAGE_NAME}.tar" - docker tag "$(docker images -q | head -1)" "${IMAGE_NAME}:${IMAGE_VERSION}" + skopeo copy "oci-archive:build/${IMAGE_NAME}.tar" "docker-daemon:${IMAGE_NAME}:${IMAGE_VERSION}" - name: Dive filesystem scan env: diff --git a/.releaserc.yaml b/.releaserc.yaml index 3dfc0b8..9473609 100644 --- a/.releaserc.yaml +++ b/.releaserc.yaml @@ -1,5 +1,5 @@ branches: - - master + - main plugins: - "@semantic-release/commit-analyzer" diff --git a/Containerfile b/Containerfile index 3875abf..e4f932b 100644 --- a/Containerfile +++ b/Containerfile @@ -6,29 +6,17 @@ ARG APP_HOME=/home/runner USER root -# Update and upgrade the system -RUN apt-get update \ - && apt-get upgrade -y \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* \ - && apt-get autoremove -y \ - && apt-get autoclean -y - -# Add Python 3.12, 3.13 and 3.14 -# Add deadsnake apt repository +# System upgrade, Python 3.12/3.13/3.14 (deadsnakes), skopeo, buildah # hadolint ignore=DL3008 RUN apt-get update \ + && apt-get upgrade -y \ && apt-get install --no-install-recommends -y gnupg ca-certificates software-properties-common curl \ && DEBIAN_FRONTEND=noninteractive add-apt-repository -y ppa:deadsnakes/ppa \ && apt-get update \ - && apt-get install --no-install-recommends -y python3.12 python3.13 python3.14 \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -# Install skopeo and buildah -# hadolint ignore=DL3008 -RUN apt-get update \ - && apt-get install --no-install-recommends -y skopeo buildah \ + && apt-get install --no-install-recommends -y \ + python3.12 python3.13 python3.14 \ + skopeo buildah \ + && apt-get autoremove -y \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/README.md b/README.md index 96aac0d..b7c303f 100644 --- a/README.md +++ b/README.md @@ -44,13 +44,13 @@ These tools allow the image to run its own build pipeline as a self-hosted runne | Workflow | Trigger | Description | |----------|---------|-------------| -| **CI** | Pull request to `master` | Commitlint, hadolint lint, test build | -| **Release** | Push to `master` | Semantic release, build, scan, push to GHCR | -| **Update tools** | Weekly (Monday 08:00 UTC) / manual | Checks for new tool versions, opens a PR | +| **CI** | Pull request to `main` | Commitlint, hadolint lint, test build | +| **Release** | Push to `main` | Semantic release, build, scan, push to GHCR | +| **Renovate** | Automated | Keeps tool versions and dependencies up to date via PRs | ### Release process -Releases are fully automated via [semantic-release](https://github.com/semantic-release/semantic-release). Pushing to `master` triggers version analysis based on [Conventional Commits](https://www.conventionalcommits.org/): +Releases are fully automated via [semantic-release](https://github.com/semantic-release/semantic-release). Pushing to `main` triggers version analysis based on [Conventional Commits](https://www.conventionalcommits.org/). Tool versions are kept up to date automatically by [Renovate](https://docs.renovatebot.com/). | Commit prefix | Version bump | |---------------|-------------| @@ -99,7 +99,7 @@ registry: ghcr.io/deerhide/python-github-runner build: format: oci args: - - RUNNER_VERSION=latest + - RUNNER_VERSION=2.321.0 - ARGO_VERSION=3.6.4 - KARGO_VERSION=1.9.2 - PACK_VERSION=0.36.4 @@ -186,8 +186,8 @@ git commit -m "WIP" ├── .github/ │ └── workflows/ │ ├── ci.yaml # PR validation -│ ├── release.yaml # Semantic release + build + push -│ └── update-tools.yaml # Automated tool version updates +│ └── release.yaml # Semantic release + build + push +├── renovate.json # Renovate dependency update config └── scripts/ ├── builder.sh # Local build orchestration ├── install_tools.sh # Build tool installer From 30e558875c4c3a7b726cc794cad9c1717f4d9e89 Mon Sep 17 00:00:00 2001 From: miragecentury Date: Thu, 19 Feb 2026 20:50:30 +0100 Subject: [PATCH 10/15] fix: comments from pr (hadolint, python version) --- Containerfile | 7 +++++-- README.md | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Containerfile b/Containerfile index e4f932b..a7d5e4a 100644 --- a/Containerfile +++ b/Containerfile @@ -6,7 +6,7 @@ ARG APP_HOME=/home/runner USER root -# System upgrade, Python 3.12/3.13/3.14 (deadsnakes), skopeo, buildah +# System upgrade, Python 3.12/3.13 (deadsnakes), skopeo, buildah # hadolint ignore=DL3008 RUN apt-get update \ && apt-get upgrade -y \ @@ -14,7 +14,7 @@ RUN apt-get update \ && DEBIAN_FRONTEND=noninteractive add-apt-repository -y ppa:deadsnakes/ppa \ && apt-get update \ && apt-get install --no-install-recommends -y \ - python3.12 python3.13 python3.14 \ + python3.12 python3.13 \ skopeo buildah \ && apt-get autoremove -y \ && apt-get clean \ @@ -83,6 +83,9 @@ RUN curl -sSL -o /tmp/pack.tgz \ # hadolint ignore=DL3013 RUN pip install --no-cache-dir pre-commit +# Base stage must not end as root (hadolint DL3002) +USER runner + FROM base as runtime LABEL org.opencontainers.image.source=https://github.com/deerhide/python-github-runner diff --git a/README.md b/README.md index b7c303f..8038d15 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Container image based on the [GitHub Actions Runner](https://github.com/actions/ | Tool | Version | |------|---------| -| Python | 3.12, 3.13, 3.14 (via deadsnakes PPA) | +| Python | 3.12, 3.13 (via deadsnakes PPA) | | Poetry | latest | | UV | latest | From 697a7a6ec3ca7f4026098701c4aa4a2ec30e293a Mon Sep 17 00:00:00 2001 From: miragecentury Date: Thu, 19 Feb 2026 20:53:50 +0100 Subject: [PATCH 11/15] fix: use ghcr.io/actions/actions-runner base image and fix FromAsCasing - Change base image from ghcr.io/actions/runner to ghcr.io/actions/actions-runner to fix 403 on pull - Use uppercase AS for stage aliases (AS base, AS runtime) to satisfy hadolint - Update README base image reference Co-authored-by: Cursor --- Containerfile | 4 ++-- README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Containerfile b/Containerfile index a7d5e4a..2e88263 100644 --- a/Containerfile +++ b/Containerfile @@ -1,6 +1,6 @@ ARG RUNNER_VERSION=2.321.0 -FROM ghcr.io/actions/runner:${RUNNER_VERSION} as base +FROM ghcr.io/actions/actions-runner:${RUNNER_VERSION} AS base ARG APP_HOME=/home/runner @@ -86,7 +86,7 @@ RUN pip install --no-cache-dir pre-commit # Base stage must not end as root (hadolint DL3002) USER runner -FROM base as runtime +FROM base AS runtime LABEL org.opencontainers.image.source=https://github.com/deerhide/python-github-runner LABEL org.opencontainers.image.description="Python GitHub Runner" diff --git a/README.md b/README.md index 8038d15..7972bf8 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Container image based on the [GitHub Actions Runner](https://github.com/actions/ ### Base image -`ghcr.io/actions/runner` (GitHub Actions Runner) +`ghcr.io/actions/actions-runner` (GitHub Actions Runner) ### Python From 559933d8724b6b3e9d16f3ad8b38d3feaf812654 Mon Sep 17 00:00:00 2001 From: miragecentury Date: Thu, 19 Feb 2026 20:58:33 +0100 Subject: [PATCH 12/15] feat(container): add Python pip packages and GCC build tooling - Install python3.12-pip, python3.13-pip for pip on PATH - Install build-essential and python3.12-dev, python3.13-dev to build C extensions Co-authored-by: Cursor --- Containerfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Containerfile b/Containerfile index 2e88263..535002b 100644 --- a/Containerfile +++ b/Containerfile @@ -14,7 +14,9 @@ RUN apt-get update \ && DEBIAN_FRONTEND=noninteractive add-apt-repository -y ppa:deadsnakes/ppa \ && apt-get update \ && apt-get install --no-install-recommends -y \ - python3.12 python3.13 \ + build-essential \ + python3.12 python3.12-dev python3.12-pip \ + python3.13 python3.13-dev python3.13-pip \ skopeo buildah \ && apt-get autoremove -y \ && apt-get clean \ @@ -81,7 +83,7 @@ RUN curl -sSL -o /tmp/pack.tgz \ # Install pre-commit # hadolint ignore=DL3013 -RUN pip install --no-cache-dir pre-commit +RUN pip3 install --no-cache-dir pre-commit # Base stage must not end as root (hadolint DL3002) USER runner From 76980ea1de1d5f1ee7c249ad07c50c8fefe086a8 Mon Sep 17 00:00:00 2001 From: miragecentury Date: Thu, 19 Feb 2026 21:03:59 +0100 Subject: [PATCH 13/15] fix(container): install pip for Python 3.12/3.13 via ensurepip deadsnakes PPA does not provide python3.12-pip or python3.13-pip. Bootstrap pip for both interpreters using ensurepip instead. Co-authored-by: Cursor --- Containerfile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Containerfile b/Containerfile index 535002b..714408a 100644 --- a/Containerfile +++ b/Containerfile @@ -15,13 +15,17 @@ RUN apt-get update \ && apt-get update \ && apt-get install --no-install-recommends -y \ build-essential \ - python3.12 python3.12-dev python3.12-pip \ - python3.13 python3.13-dev python3.13-pip \ + python3.12 python3.12-dev \ + python3.13 python3.13-dev \ skopeo buildah \ && apt-get autoremove -y \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* +# deadsnakes PPA does not ship python3.x-pip; bootstrap pip via ensurepip +RUN python3.12 -m ensurepip --upgrade \ + && python3.13 -m ensurepip --upgrade + # Configure buildah storage for container/rootless usage RUN mkdir -p /etc/containers \ && printf '[storage]\ndriver = "vfs"\n' > /etc/containers/storage.conf From 9554b0db6bb8a0dff8ced22aa0edf2a3bec98c7b Mon Sep 17 00:00:00 2001 From: miragecentury Date: Thu, 19 Feb 2026 21:03:26 +0100 Subject: [PATCH 14/15] docs: align documentation and comments with implementation - Use Containerfile terminology in builder.sh hadolint message - Fix comment typos (trailing backslashes) in builder.sh and install_tools.sh - Reorder README release workflow so hadolint runs before build - Run hadolint with .hadolint.yaml in builder.sh to match release workflow - Add files: ^Containerfile$ to pre-commit hadolint hook - Add CHANGELOG.md and install-man-page.sh to project structure - Note that local install may use different tool versions than image/CI Co-authored-by: Cursor --- .pre-commit-config.yaml | 1 + README.md | 8 ++++++-- scripts/builder.sh | 6 +++--- scripts/install_tools.sh | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 31aa69d..782123a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,6 +14,7 @@ repos: hooks: - id: hadolint-docker args: ["--config", ".hadolint.yaml"] + files: ^Containerfile$ - repo: https://github.com/shellcheck-py/shellcheck-py rev: v0.10.0.1 diff --git a/README.md b/README.md index 7972bf8..18eb208 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,8 @@ When a new version is determined, the release workflow: 1. Creates a GitHub release with auto-generated notes 2. Updates `CHANGELOG.md` -3. Builds the image with `buildah` (OCI format, squashed layers) -4. Runs `hadolint` lint validation +3. Validates the Containerfile with hadolint +4. Builds the image with `buildah` (OCI format, squashed layers) 5. Runs `dive` filesystem efficiency scan 6. Runs `trivy` vulnerability scan (HIGH/CRITICAL) 7. Pushes to GHCR with semver tags: `1.2.3`, `1.2`, `1`, `latest` @@ -87,6 +87,8 @@ Install [Docker](https://docs.docker.com/get-docker/), then install the build to ./scripts/install_tools.sh ``` +Local install may use different (e.g. latest) versions for some tools than the pinned versions in the image and CI. + ### Configuration Build configuration is defined in `manifest.yaml`: @@ -177,6 +179,8 @@ git commit -m "WIP" . ├── Containerfile # Multi-stage container definition ├── manifest.yaml # Build configuration and metadata +├── CHANGELOG.md # Generated by semantic-release +├── install-man-page.sh # Optional: install yq man page locally ├── .releaserc.yaml # Semantic release configuration ├── .hadolint.yaml # Hadolint configuration ├── .commitlintrc.yaml # Commitlint configuration diff --git a/scripts/builder.sh b/scripts/builder.sh index e552af1..394bb8f 100755 --- a/scripts/builder.sh +++ b/scripts/builder.sh @@ -47,13 +47,13 @@ clean_build_dir(){ hadolint_validate(){ local hadolint_exec local hadolint_exit_code - log_info "Validating Dockerfile with hadolint" + log_info "Validating Containerfile with hadolint" ${CLI} pull -q ghcr.io/hadolint/hadolint:latest > /dev/null log_trace "$(${CLI} run --rm -i hadolint/hadolint:latest hadolint -v)" set +e hadolint_exec=$( - ${CLI} run --rm -i hadolint/hadolint:latest < Containerfile \ + ${CLI} run --rm -i -v "$(pwd)/.hadolint.yaml:/.hadolint.yaml:ro" hadolint/hadolint:latest hadolint --config /.hadolint.yaml - < Containerfile \ 2>&1 ) hadolint_exit_code=$? @@ -312,7 +312,7 @@ trivy_scan () { # Main clean_build_dir -check_for_manifest # Check for manifest file existence\ +check_for_manifest # Check for manifest file existence IMAGE_NAME=$(retrieve_name_from_manifest) # Retrieve image name from manifest log_info "Starting build process" diff --git a/scripts/install_tools.sh b/scripts/install_tools.sh index 774d721..342355f 100755 --- a/scripts/install_tools.sh +++ b/scripts/install_tools.sh @@ -27,7 +27,7 @@ sudo apt-get install trivy -y # Install buildah sudo apt-get install buildah -y -# Install yq\ +# Install yq VERSION="v4.45.4" BINARY="yq_linux_amd64" wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY}.tar.gz -O - |\ From 48a8477832e60affc29cb559a2379a2f88cab3bb Mon Sep 17 00:00:00 2001 From: miragecentury Date: Thu, 19 Feb 2026 21:14:32 +0100 Subject: [PATCH 15/15] fix(container): bootstrap pip via get-pip.py for deadsnakes Python deadsnakes PPA does not ship ensurepip; use PyPA get-pip.py instead. Co-authored-by: Cursor --- Containerfile | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Containerfile b/Containerfile index 714408a..a348265 100644 --- a/Containerfile +++ b/Containerfile @@ -22,9 +22,12 @@ RUN apt-get update \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -# deadsnakes PPA does not ship python3.x-pip; bootstrap pip via ensurepip -RUN python3.12 -m ensurepip --upgrade \ - && python3.13 -m ensurepip --upgrade +# deadsnakes PPA does not ship python3.x-pip or ensurepip; bootstrap via get-pip.py +# hadolint ignore=DL4006 +RUN curl -sSL https://bootstrap.pypa.io/get-pip.py -o /tmp/get-pip.py \ + && python3.12 /tmp/get-pip.py --no-cache-dir \ + && python3.13 /tmp/get-pip.py --no-cache-dir \ + && rm /tmp/get-pip.py # Configure buildah storage for container/rootless usage RUN mkdir -p /etc/containers \