Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 230 additions & 0 deletions .github/workflows/build-staged-images.yml
Original file line number Diff line number Diff line change
@@ -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'
27 changes: 27 additions & 0 deletions renovate-shared-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@
"# renovate: datasource=(?<datasource>[a-z-]+) depName=(?<depName>[^\\s]+)\\n\\s*(?:export )?\\w*VERSION=(?<currentValue>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=(?<datasource>docker) depName=(?<depName>[^\\s]+)\\n\\s*\\w+:\\s*\\S+:(?<currentValue>[^@\\s]+)@(?<currentDigest>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
Expand Down Expand Up @@ -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),
Expand Down
2 changes: 2 additions & 0 deletions staged-images/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
source-config.json
out.ociarchive
33 changes: 33 additions & 0 deletions staged-images/Containerfile.staged
Original file line number Diff line number Diff line change
@@ -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