Build Hypervisor Bootc Images #101
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |