diff --git a/.github/workflows/build-staged-images.yml b/.github/workflows/build-staged-images.yml new file mode 100644 index 0000000..bd8db7f --- /dev/null +++ b/.github/workflows/build-staged-images.yml @@ -0,0 +1,230 @@ +name: Build chunkah-staged bootc base images + +on: + push: + branches: [main] + paths: + - 'staged-images/**' + - '.github/workflows/build-staged-images.yml' + schedule: + # Rebuild weekly to pick up upstream base image updates + - cron: '0 6 * * 1' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + REGISTRY: ghcr.io + +jobs: + # Mirror upstream source images to GHCR so we have our own copy. + # Upstream registries (quay.io) may delete old manifests, breaking + # digest-pinned pulls. skopeo copy --all preserves the full + # multi-arch manifest list. + mirror: + name: Mirror ${{ matrix.upstream_name }}:${{ matrix.tag }} + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + include: + - upstream_name: fedora/fedora-bootc + tag: "43" + mirror_name: fedora-bootc-source + # renovate: datasource=docker depName=quay.io/fedora/fedora-bootc + source: quay.io/fedora/fedora-bootc:43@sha256:60900cb506936af583704955830c72de1d7fc2622a92a9a3039442871a798260 + - upstream_name: fedora/fedora-bootc + tag: "44" + mirror_name: fedora-bootc-source + # renovate: datasource=docker depName=quay.io/fedora/fedora-bootc + source: quay.io/fedora/fedora-bootc:44@sha256:33811dcc3d56e69a810fab88e8af08a121db2897625eec859b004b869a2d3d4e + - upstream_name: centos-bootc/centos-bootc + tag: stream9 + mirror_name: centos-bootc-source + # renovate: datasource=docker depName=quay.io/centos-bootc/centos-bootc + source: quay.io/centos-bootc/centos-bootc:stream9@sha256:2b96ee6f7157ed79c0f3db4d2c56686444e61733cabddc6229711c6a6b5dd673 + - upstream_name: centos-bootc/centos-bootc + tag: stream10 + mirror_name: centos-bootc-source + # renovate: datasource=docker depName=quay.io/centos-bootc/centos-bootc + source: quay.io/centos-bootc/centos-bootc:stream10@sha256:43d2199dc2b147905ff3970b77957ac4775554b5ee6973abb577243a3fa28614 + steps: + - name: Set up podman + uses: bootc-dev/actions/bootc-ubuntu-setup@main + + - name: Log in to GHCR + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | \ + podman login -u "${{ github.actor }}" --password-stdin ${{ env.REGISTRY }} + + - name: Mirror image + id: mirror + run: | + set -euo pipefail + dest="${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ matrix.mirror_name }}:${{ matrix.tag }}" + # skopeo doesn't support tag@digest references, so strip the + # tag and use the digest-only form for the source. + src="${{ matrix.source }}" + src_by_digest="${src%%:*}@${src##*@}" + echo "Mirroring ${src_by_digest} -> ${dest}" + skopeo copy --all --retry-times 3 \ + "docker://${src_by_digest}" \ + "docker://${dest}" + echo "Mirrored ${dest}" + + build: + name: Build ${{ matrix.name }}:${{ matrix.tag }} (${{ matrix.arch }}) + needs: mirror + if: ${{ !cancelled() }} + runs-on: ${{ matrix.runner }} + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + include: + # fedora-bootc-staged:43 + - name: fedora-bootc-staged + tag: "43" + mirror_name: fedora-bootc-source + arch: amd64 + runner: ubuntu-24.04 + - name: fedora-bootc-staged + tag: "43" + mirror_name: fedora-bootc-source + arch: arm64 + runner: ubuntu-24.04-arm + # fedora-bootc-staged:44 + - name: fedora-bootc-staged + tag: "44" + mirror_name: fedora-bootc-source + arch: amd64 + runner: ubuntu-24.04 + - name: fedora-bootc-staged + tag: "44" + mirror_name: fedora-bootc-source + arch: arm64 + runner: ubuntu-24.04-arm + # centos-bootc-staged:stream9 + - name: centos-bootc-staged + tag: stream9 + mirror_name: centos-bootc-source + arch: amd64 + runner: ubuntu-24.04 + - name: centos-bootc-staged + tag: stream9 + mirror_name: centos-bootc-source + arch: arm64 + runner: ubuntu-24.04-arm + # centos-bootc-staged:stream10 + - name: centos-bootc-staged + tag: stream10 + mirror_name: centos-bootc-source + arch: amd64 + runner: ubuntu-24.04 + - name: centos-bootc-staged + tag: stream10 + mirror_name: centos-bootc-source + arch: arm64 + runner: ubuntu-24.04-arm + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up podman + uses: bootc-dev/actions/bootc-ubuntu-setup@main + + - name: Log in to GHCR + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | \ + podman login -u "${{ github.actor }}" --password-stdin ${{ env.REGISTRY }} + + - name: Pull source image from mirror + id: source + run: | + set -euo pipefail + source="${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ matrix.mirror_name }}:${{ matrix.tag }}" + podman pull "${source}" + echo "image=${source}" >> "$GITHUB_OUTPUT" + + - name: Write source image config to build context + working-directory: staged-images + run: podman inspect ${{ steps.source.outputs.image }} > source-config.json + + - name: Build staged image + working-directory: staged-images + run: | + buildah build --skip-unused-stages=false \ + --build-arg SOURCE_IMAGE=${{ steps.source.outputs.image }} \ + --build-arg MAX_LAYERS=128 \ + -f Containerfile.staged \ + -t localhost/${{ matrix.name }}:latest \ + . + + - name: Verify image + run: | + echo "=== Image labels ===" + podman inspect localhost/${{ matrix.name }}:latest | jq '.[0].Config.Labels' + echo "=== Layer count ===" + podman inspect localhost/${{ matrix.name }}:latest | jq '.[0].RootFS.Layers | length' + + - name: Push by digest + id: push + run: | + set -euo pipefail + image="${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ matrix.name }}" + podman tag localhost/${{ matrix.name }}:latest "${image}:latest" + podman push --retry 3 --digestfile "${{ runner.temp }}/digestfile" "${image}:latest" + digest=$(cat "${{ runner.temp }}/digestfile") + echo "digest=${digest}" >> "$GITHUB_OUTPUT" + echo "Pushed ${image}@${digest} (${{ matrix.arch }})" + + - name: Upload digest artifact + run: | + mkdir -p "${{ runner.temp }}/digests" + echo "${{ steps.push.outputs.digest }}" > "${{ runner.temp }}/digests/${{ matrix.arch }}" + - uses: actions/upload-artifact@v7 + with: + name: staged-digests-${{ matrix.name }}-${{ matrix.tag }}-${{ matrix.arch }} + path: ${{ runner.temp }}/digests/* + if-no-files-found: error + retention-days: 1 + + manifest: + name: Manifest ${{ matrix.name }}:${{ matrix.tag }} + needs: build + if: ${{ !cancelled() }} + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + include: + - name: fedora-bootc-staged + tag: "43" + - name: fedora-bootc-staged + tag: "44" + - name: centos-bootc-staged + tag: stream9 + - name: centos-bootc-staged + tag: stream10 + steps: + - name: Set up podman + uses: bootc-dev/actions/bootc-ubuntu-setup@main + + - name: Create and push manifest + uses: bootc-dev/actions/create-manifest@main + with: + image: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ matrix.name }} + tags: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ matrix.name }}:${{ matrix.tag }} + artifact-pattern: staged-digests-${{ matrix.name }}-${{ matrix.tag }}-* + registry-login-env: 'false' diff --git a/renovate-shared-config.json b/renovate-shared-config.json index 7ea7cfe..46d3c4c 100644 --- a/renovate-shared-config.json +++ b/renovate-shared-config.json @@ -57,6 +57,17 @@ "# renovate: datasource=(?[a-z-]+) depName=(?[^\\s]+)\\n\\s*(?:export )?\\w*VERSION=(?v?\\S+)" ] }, + // Container image digest pinning in YAML workflows + // Matches patterns like: + // # renovate: datasource=docker depName=quay.io/fedora/fedora-bootc + // source: quay.io/fedora/fedora-bootc:43@sha256:abc123... + { + "customType": "regex", + "managerFilePatterns": ["**/*.yml", "**/*.yaml"], + "matchStrings": [ + "# renovate: datasource=(?docker) depName=(?[^\\s]+)\\n\\s*\\w+:\\s*\\S+:(?[^@\\s]+)@(?sha256:[a-f0-9]+)" + ] + }, // Git refs (commit SHA) tracking in Justfiles and YAML workflows // Justfile example: // # renovate: datasource=git-refs depName=https://github.com/org/repo branch=main @@ -134,6 +145,22 @@ "groupName": "Docker", "enabled": true }, + // Group staged bootc base image digest updates separately + // + // These are the upstream source images for chunkah-staged builds. + // Digest updates trigger a rebuild of the staged images, so they + // get their own PR. Must come after the Docker group rule so it + // takes precedence (Renovate applies all matching rules in order, + // later rules win). + { + "description": ["Staged bootc base image digest updates"], + "matchManagers": ["custom.regex"], + "matchDepNames": [ + "quay.io/fedora/fedora-bootc", + "quay.io/centos-bootc/centos-bootc" + ], + "groupName": "staged-images" + }, // bcvk gets its own group so it isn't blocked by the weekly schedule // applied to other Docker group members. Without this, the Docker group // PR can only be created on Sundays (when all deps are in-schedule), diff --git a/staged-images/.gitignore b/staged-images/.gitignore new file mode 100644 index 0000000..465b9a5 --- /dev/null +++ b/staged-images/.gitignore @@ -0,0 +1,2 @@ +source-config.json +out.ociarchive diff --git a/staged-images/Containerfile.staged b/staged-images/Containerfile.staged new file mode 100644 index 0000000..2d67112 --- /dev/null +++ b/staged-images/Containerfile.staged @@ -0,0 +1,33 @@ +# Containerfile.staged — Rechunk a bootc base image with chunkah +# +# Takes an upstream bootc image (fedora-bootc or centos-bootc), strips +# the /sysroot (ostree data), and rechunks using chunkah for optimal +# layer reuse across updates. The result is a "staged" base image +# suitable for use as a FROM base in bootc development. +# +# Usage: +# podman pull quay.io/fedora/fedora-bootc:44 +# podman inspect quay.io/fedora/fedora-bootc:44 > source-config.json +# buildah build --skip-unused-stages=false \ +# --build-arg SOURCE_IMAGE=quay.io/fedora/fedora-bootc:44 \ +# -f Containerfile.staged . + +ARG SOURCE_IMAGE +ARG CHUNKAH=quay.io/jlebon/chunkah:latest +ARG MAX_LAYERS=128 + +FROM ${SOURCE_IMAGE} AS source + +FROM ${CHUNKAH} AS chunkah +ARG MAX_LAYERS +RUN --mount=type=bind,target=/run/src,rw \ + --mount=from=source,target=/chunkah,ro \ + chunkah build \ + --config /run/src/source-config.json \ + --prune /sysroot/ \ + --max-layers "${MAX_LAYERS}" \ + --label ostree.commit- \ + --label ostree.final-diffid- \ + > /run/src/out.ociarchive + +FROM oci-archive:out.ociarchive