From 10d0b70f5750e0be6db00d2f420357cc7a99e43c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 15 Feb 2026 09:57:39 +0000 Subject: [PATCH 1/6] 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 2/6] 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 3/6] 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 4/6] 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 5/6] 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 6/6] 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/