Skip to content

Build Hypervisor Bootc Images #101

Build Hypervisor Bootc Images

Build Hypervisor Bootc Images #101

name: Build Hypervisor Bootc Images
on:
# Trigger after minimal build completes successfully
workflow_run:
workflows: ["Build Fedora Minimal Bootc"]
types:
- completed
branches:
- main
# Manual trigger
workflow_dispatch:
inputs:
variants:
description: 'Which variants to build'
required: true
default: 'base'
type: choice
options:
- 'base'
- 'all'
- 'base,nvidia-rpmfusion'
- 'base,nvidia-negativo17'
- 'base,amd'
jobs:
build-hypervisor:
runs-on: ubuntu-24.04 # Required by rechunk action for advanced podman features
# Only run if:
# 1. workflow_run: minimal build succeeded
# 2. Other events: allow (manual, schedule, push)
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name != 'workflow_run' }}
permissions:
contents: read
packages: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Generate policy.json from template
run: |
# owner needs to be lowercase, but workflow ref needs to preserve case
OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
REPO_NAME="${{ github.event.repository.name }}"
# Generate the minimal workflow ref (predictable pattern)
MINIMAL_WORKFLOW_REF="${{ github.repository }}/.github/workflows/build-minimal-bootc.yml@${{ github.ref }}"
# Use the current workflow ref for hypervisor
HYPERVISOR_WORKFLOW_REF="${{ github.workflow_ref }}"
sed -e "s|__REGISTRY_NAMESPACE__|${OWNER}|g" \
-e "s|__MINIMAL_WORKFLOW_REF__|${MINIMAL_WORKFLOW_REF}|g" \
-e "s|__HYPERVISOR_WORKFLOW_REF__|${HYPERVISOR_WORKFLOW_REF}|g" \
policy-hypervisor.json.template > policy.json
echo "Generated policy.json:"
cat policy.json
- name: Check if should skip build
id: should_build
run: |
# For push events, skip if minimal Containerfile changed (will trigger via workflow_run)
if [ "${{ github.event_name }}" == "push" ]; then
FILES=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }})
echo "Changed files:"
echo "$FILES"
if echo "$FILES" | grep -q "fedora-bootc-minimal.Containerfile"; then
echo "run=false" >> $GITHUB_OUTPUT
echo "⚠️ Minimal Containerfile changed - skipping push trigger"
echo " Hypervisor will build automatically after minimal completes"
else
echo "run=true" >> $GITHUB_OUTPUT
echo "✅ Proceeding with build"
fi
else
# For non-push events (workflow_run, manual, schedule), always run
echo "run=true" >> $GITHUB_OUTPUT
echo "✅ Event: ${{ github.event_name }} - proceeding with build"
fi
- name: Setup podman
if: steps.should_build.outputs.run == 'true'
run: |
sudo apt-get update
sudo apt-get install -y podman
podman --version
- name: Install cosign
if: steps.should_build.outputs.run == 'true'
uses: sigstore/cosign-installer@v3.7.0
- name: Login to GitHub Container Registry
if: steps.should_build.outputs.run == 'true'
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | sudo podman login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Determine variants to build
if: steps.should_build.outputs.run == 'true'
id: variants
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
VARIANTS="${{ inputs.variants }}"
elif [ "${{ github.event_name }}" == "workflow_run" ]; then
VARIANTS="all"
else
VARIANTS="base"
fi
echo "variants=${VARIANTS}" >> $GITHUB_OUTPUT
- name: Free up disk space
if: steps.should_build.outputs.run == 'true'
uses: jlumbroso/free-disk-space@main
with:
# Remove all unnecessary software (~31 GB freed in ~3 min)
tool-cache: true # ~5.9 GB - Remove Node, Python, Ruby caches (not needed for container builds)
android: true # ~14 GB - Remove Android libraries
dotnet: true # ~2.7 GB - Remove .NET runtime
haskell: true # Remove Haskell runtime (GHC)
large-packages: true # ~5.3 GB - Remove Azure CLI, Google Cloud SDK, etc.
docker-images: true # Remove cached Docker images (doesn't affect podman)
swap-storage: true # ~4 GB - Remove swap file
- name: Build base hypervisor
if: steps.should_build.outputs.run == 'true'
run: |
TAG=$(date +%Y%m%d-%H%M)
echo "base_tag=${TAG}" >> $GITHUB_ENV
# Build in root storage (rechunk action accesses root storage)
# --pull=always ensures we get the latest minimal base from GHCR
# Security options needed for dracut/bootc operations
sudo podman build \
--pull=always \
--security-opt=label=disable \
--security-opt=seccomp=unconfined \
--cap-add=all \
--ipc=host \
-f hypervisor.Containerfile \
-t localhost/hypervisor-bootc:${TAG} \
-t localhost/hypervisor-bootc:latest \
.
- name: Rechunk base hypervisor image
if: steps.should_build.outputs.run == 'true'
run: |
# Use bootc-base-imagectl rechunk (official bootc method)
sudo podman run --rm --privileged \
-v /var/lib/containers:/var/lib/containers \
quay.io/fedora/fedora-bootc:rawhide \
/usr/libexec/bootc-base-imagectl rechunk \
localhost/hypervisor-bootc:${{ env.base_tag }} \
localhost/hypervisor-bootc:rechunked
- name: Tag rechunked base image
if: steps.should_build.outputs.run == 'true'
run: |
# Apply all needed tags to rechunked image
sudo podman tag localhost/hypervisor-bootc:rechunked localhost/hypervisor-bootc:${{ env.base_tag }}
sudo podman tag localhost/hypervisor-bootc:rechunked localhost/hypervisor-bootc:latest
sudo podman rmi localhost/hypervisor-bootc:rechunked
- name: Push base hypervisor to GHCR
if: steps.should_build.outputs.run == 'true'
run: |
OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
sudo podman tag localhost/hypervisor-bootc:${{ env.base_tag }} ghcr.io/${OWNER}/hypervisor-bootc:${{ env.base_tag }}
sudo podman tag localhost/hypervisor-bootc:${{ env.base_tag }} ghcr.io/${OWNER}/hypervisor-bootc:latest
sudo podman push ghcr.io/${OWNER}/hypervisor-bootc:${{ env.base_tag }}
sudo podman push ghcr.io/${OWNER}/hypervisor-bootc:latest
sudo podman rmi ghcr.io/${OWNER}/hypervisor-bootc:${{ env.base_tag }} || true
sudo podman rmi ghcr.io/${OWNER}/hypervisor-bootc:latest || true
# Verify localhost tags still exist for variant builds
echo "Remaining localhost images:"
sudo podman images | grep hypervisor-bootc
- name: Login to GitHub Container Registry for cosign
if: steps.should_build.outputs.run == 'true'
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | cosign login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Sign base hypervisor images
id: base_complete
if: steps.should_build.outputs.run == 'true'
run: |
OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
cosign sign --yes ghcr.io/${OWNER}/hypervisor-bootc:${{ env.base_tag }}
cosign sign --yes ghcr.io/${OWNER}/hypervisor-bootc:latest
- name: Build AMD variant
continue-on-error: true
if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'amd') || contains(steps.variants.outputs.variants, 'all'))
run: |
# Build in root storage with only timestamped tag
sudo podman build \
--pull=always \
--security-opt=label=disable \
--security-opt=seccomp=unconfined \
--cap-add=all \
--ipc=host \
-f hypervisor-amd.Containerfile \
-t localhost/hypervisor-amd:${{ env.base_tag }} \
.
- name: Rechunk AMD variant image
continue-on-error: true
if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'amd') || contains(steps.variants.outputs.variants, 'all'))
run: |
# Use bootc-base-imagectl rechunk (official bootc method)
sudo podman run --rm --privileged \
-v /var/lib/containers:/var/lib/containers \
quay.io/fedora/fedora-bootc:rawhide \
/usr/libexec/bootc-base-imagectl rechunk \
localhost/hypervisor-amd:${{ env.base_tag }} \
localhost/hypervisor-amd:rechunked
- name: Tag rechunked AMD image
continue-on-error: true
if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'amd') || contains(steps.variants.outputs.variants, 'all'))
run: |
# Apply tags to rechunked image
sudo podman tag localhost/hypervisor-amd:rechunked localhost/hypervisor-amd:${{ env.base_tag }}
sudo podman tag localhost/hypervisor-amd:rechunked localhost/hypervisor-amd:latest
sudo podman rmi localhost/hypervisor-amd:rechunked
- name: Push AMD variant to GHCR
continue-on-error: true
if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'amd') || contains(steps.variants.outputs.variants, 'all'))
run: |
OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
sudo podman tag localhost/hypervisor-amd:${{ env.base_tag }} ghcr.io/${OWNER}/hypervisor-amd:${{ env.base_tag }}
sudo podman tag localhost/hypervisor-amd:${{ env.base_tag }} ghcr.io/${OWNER}/hypervisor-amd:latest
sudo podman push ghcr.io/${OWNER}/hypervisor-amd:${{ env.base_tag }}
sudo podman push ghcr.io/${OWNER}/hypervisor-amd:latest
sudo podman rmi localhost/hypervisor-amd:${{ env.base_tag }} || true
sudo podman rmi ghcr.io/${OWNER}/hypervisor-amd:${{ env.base_tag }} || true
sudo podman rmi ghcr.io/${OWNER}/hypervisor-amd:latest || true
# Only prune dangling images (no -a flag) to preserve base image for next variant
sudo podman system prune -f --volumes
- name: Sign AMD images
continue-on-error: true
if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'amd') || contains(steps.variants.outputs.variants, 'all'))
run: |
OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
cosign sign --yes ghcr.io/${OWNER}/hypervisor-amd:${{ env.base_tag }}
cosign sign --yes ghcr.io/${OWNER}/hypervisor-amd:latest
- name: Build NVIDIA negativo17 variant
continue-on-error: true
if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'nvidia-negativo17') || contains(steps.variants.outputs.variants, 'all'))
run: |
# Build in root storage with only timestamped tag
sudo podman build \
--pull=always \
--security-opt=label=disable \
--security-opt=seccomp=unconfined \
--cap-add=all \
--ipc=host \
-f hypervisor-nvidia-negativo17.Containerfile \
-t localhost/hypervisor-nvidia:negativo17-${{ env.base_tag }} \
.
- name: Rechunk NVIDIA negativo17 variant image
continue-on-error: true
if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'nvidia-negativo17') || contains(steps.variants.outputs.variants, 'all'))
run: |
# Use bootc-base-imagectl rechunk (official bootc method)
sudo podman run --rm --privileged \
-v /var/lib/containers:/var/lib/containers \
quay.io/fedora/fedora-bootc:rawhide \
/usr/libexec/bootc-base-imagectl rechunk \
localhost/hypervisor-nvidia:negativo17-${{ env.base_tag }} \
localhost/hypervisor-nvidia:rechunked
- name: Tag rechunked NVIDIA negativo17 image
continue-on-error: true
if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'nvidia-negativo17') || contains(steps.variants.outputs.variants, 'all'))
run: |
# Apply tag to rechunked image
sudo podman tag localhost/hypervisor-nvidia:rechunked localhost/hypervisor-nvidia:negativo17-${{ env.base_tag }}
sudo podman rmi localhost/hypervisor-nvidia:rechunked
- name: Push NVIDIA negativo17 variant to GHCR
continue-on-error: true
if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'nvidia-negativo17') || contains(steps.variants.outputs.variants, 'all'))
run: |
OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
sudo podman tag localhost/hypervisor-nvidia:negativo17-${{ env.base_tag }} ghcr.io/${OWNER}/hypervisor-nvidia:negativo17-${{ env.base_tag }}
sudo podman tag localhost/hypervisor-nvidia:negativo17-${{ env.base_tag }} ghcr.io/${OWNER}/hypervisor-nvidia:negativo17
sudo podman push ghcr.io/${OWNER}/hypervisor-nvidia:negativo17-${{ env.base_tag }}
sudo podman push ghcr.io/${OWNER}/hypervisor-nvidia:negativo17
sudo podman rmi localhost/hypervisor-nvidia:negativo17-${{ env.base_tag }} || true
sudo podman rmi ghcr.io/${OWNER}/hypervisor-nvidia:negativo17-${{ env.base_tag }} || true
sudo podman rmi ghcr.io/${OWNER}/hypervisor-nvidia:negativo17 || true
# Only prune dangling images (no -a flag) to preserve base image for next variant
sudo podman system prune -f --volumes
- name: Sign NVIDIA negativo17 images
continue-on-error: true
if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'nvidia-negativo17') || contains(steps.variants.outputs.variants, 'all'))
run: |
OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
cosign sign --yes ghcr.io/${OWNER}/hypervisor-nvidia:negativo17-${{ env.base_tag }}
cosign sign --yes ghcr.io/${OWNER}/hypervisor-nvidia:negativo17
- name: Build NVIDIA RPMFusion variant
continue-on-error: true
if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'nvidia-rpmfusion') || contains(steps.variants.outputs.variants, 'all'))
run: |
# Build in root storage with only timestamped tag
sudo podman build \
--pull=always \
--security-opt=label=disable \
--security-opt=seccomp=unconfined \
--cap-add=all \
--ipc=host \
-f hypervisor-nvidia-rpmfusion.Containerfile \
-t localhost/hypervisor-nvidia:rpmfusion-${{ env.base_tag }} \
.
- name: Rechunk NVIDIA RPMFusion variant image
continue-on-error: true
if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'nvidia-rpmfusion') || contains(steps.variants.outputs.variants, 'all'))
run: |
# Use bootc-base-imagectl rechunk (official bootc method)
sudo podman run --rm --privileged \
-v /var/lib/containers:/var/lib/containers \
quay.io/fedora/fedora-bootc:rawhide \
/usr/libexec/bootc-base-imagectl rechunk \
localhost/hypervisor-nvidia:rpmfusion-${{ env.base_tag }} \
localhost/hypervisor-nvidia:rechunked
- name: Tag rechunked NVIDIA RPMFusion image
continue-on-error: true
if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'nvidia-rpmfusion') || contains(steps.variants.outputs.variants, 'all'))
run: |
# Apply tag to rechunked image
sudo podman tag localhost/hypervisor-nvidia:rechunked localhost/hypervisor-nvidia:rpmfusion-${{ env.base_tag }}
sudo podman rmi localhost/hypervisor-nvidia:rechunked
- name: Push NVIDIA RPMFusion variant to GHCR
continue-on-error: true
if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'nvidia-rpmfusion') || contains(steps.variants.outputs.variants, 'all'))
run: |
OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
# Tag for GHCR
sudo podman tag localhost/hypervisor-nvidia:rpmfusion-${{ env.base_tag }} ghcr.io/${OWNER}/hypervisor-nvidia:rpmfusion-${{ env.base_tag }}
sudo podman tag localhost/hypervisor-nvidia:rpmfusion-${{ env.base_tag }} ghcr.io/${OWNER}/hypervisor-nvidia:rpmfusion
# Push to GHCR
sudo podman push ghcr.io/${OWNER}/hypervisor-nvidia:rpmfusion-${{ env.base_tag }}
sudo podman push ghcr.io/${OWNER}/hypervisor-nvidia:rpmfusion
# Clean up all local tags from root storage
sudo podman rmi localhost/hypervisor-nvidia:rpmfusion-${{ env.base_tag }} || true
sudo podman rmi ghcr.io/${OWNER}/hypervisor-nvidia:rpmfusion-${{ env.base_tag }} || true
sudo podman rmi ghcr.io/${OWNER}/hypervisor-nvidia:rpmfusion || true
# Final prune can be aggressive since all variants are done
sudo podman system prune -af --volumes
- name: Sign NVIDIA RPMFusion images
continue-on-error: true
if: steps.should_build.outputs.run == 'true' && steps.base_complete.outcome == 'success' && (contains(steps.variants.outputs.variants, 'nvidia-rpmfusion') || contains(steps.variants.outputs.variants, 'all'))
run: |
OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
cosign sign --yes ghcr.io/${OWNER}/hypervisor-nvidia:rpmfusion-${{ env.base_tag }}
cosign sign --yes ghcr.io/${OWNER}/hypervisor-nvidia:rpmfusion
- name: Summary
if: steps.should_build.outputs.run == 'true'
run: |
echo "## Build Complete!" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Built variants: ${{ steps.variants.outputs.variants }}" >> $GITHUB_STEP_SUMMARY
echo "Tag: ${{ env.base_tag }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Images:" >> $GITHUB_STEP_SUMMARY
OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
echo "- \`ghcr.io/${OWNER}/hypervisor-bootc:${{ env.base_tag }}\` ✅ Signed" >> $GITHUB_STEP_SUMMARY
echo "- \`ghcr.io/${OWNER}/hypervisor-bootc:latest\` ✅ Signed" >> $GITHUB_STEP_SUMMARY