Improve Docker build reliability and add build idempotency: #150
Workflow file for this run
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: For each commit and PR | |
| on: | |
| push: | |
| branches: | |
| - "main" | |
| pull_request: | |
| permissions: | |
| contents: read | |
| packages: write | |
| env: | |
| KERNEL_VERSION: 6.18.16 | |
| jobs: | |
| # ------------------------------------------------------------------- | |
| # Lint — ruff (lint + format) and pyright (type checking) | |
| # ------------------------------------------------------------------- | |
| lint: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v7 | |
| - name: Install lint tools | |
| run: make lint-install | |
| - name: Lint | |
| run: make lint | |
| # ------------------------------------------------------------------- | |
| # Fast cache lookup – decides whether the OCI runner is needed | |
| # There are consistent issues with OCI runners not getting scheduled. | |
| # This is the workaround. | |
| # ------------------------------------------------------------------- | |
| check-kernel-cache: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| amd64-cache-hit: ${{ steps.amd64-cache.outputs.cache-hit }} | |
| arm64-cache-hit: ${{ steps.arm64-cache.outputs.cache-hit }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Check amd64 kernel cache | |
| id: amd64-cache | |
| uses: actions/cache/restore@v5 | |
| with: | |
| path: | | |
| mkosi.output/kernel/${{ env.KERNEL_VERSION }}/amd64 | |
| key: kernel-amd64-${{ env.KERNEL_VERSION }}-${{ hashFiles('kernel.configs/*', 'Dockerfile') }} | |
| lookup-only: true | |
| - name: Check arm64 kernel cache | |
| id: arm64-cache | |
| uses: actions/cache/restore@v5 | |
| with: | |
| path: | | |
| mkosi.output/kernel/${{ env.KERNEL_VERSION }}/arm64 | |
| key: kernel-arm64-${{ env.KERNEL_VERSION }}-${{ hashFiles('kernel.configs/*', 'Dockerfile') }} | |
| lookup-only: true | |
| # ------------------------------------------------------------------- | |
| # Build kernel (vmlinuz + modules) inside Docker | |
| # ------------------------------------------------------------------- | |
| build-kernel: | |
| needs: [lint, check-kernel-cache] | |
| # Forks / cache-hit → cheap default runner; mainline cache-miss → fast oracle runner | |
| runs-on: > | |
| ${{ github.repository != 'tinkerbell/captain' && matrix.fork_runner | |
| || needs.check-kernel-cache.outputs[format('{0}-cache-hit', matrix.arch)] == 'true' && matrix.fork_runner | |
| || fromJSON(matrix.mainline_runner) }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| arch: [amd64, arm64] | |
| include: | |
| - arch: amd64 | |
| fork_runner: ubuntu-latest | |
| mainline_runner: '{"group":"Default","labels":["oracle-vm-16cpu-64gb-x86-64"]}' | |
| - arch: arm64 | |
| fork_runner: ubuntu-24.04-arm | |
| mainline_runner: '{"group":"Default","labels":["oracle-vm-16cpu-64gb-arm64"]}' | |
| env: | |
| ARCH: ${{ matrix.arch }} | |
| KERNEL_MODE: docker | |
| MKOSI_MODE: skip | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Load shared config | |
| run: cat .github/config.env >> "$GITHUB_ENV" | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Compute Dockerfile hash | |
| id: dockerfile-hash | |
| run: echo "hash=$(sha256sum Dockerfile | awk '{print $1}')" >> "$GITHUB_OUTPUT" | |
| - name: Pull or build builder image | |
| id: builder | |
| run: | | |
| HASH="${{ steps.dockerfile-hash.outputs.hash }}" | |
| REMOTE="ghcr.io/${{ github.repository }}/${{ env.BUILDER_IMAGE }}" | |
| if docker pull "${REMOTE}:${HASH}-${{ matrix.arch }}"; then | |
| docker tag "${REMOTE}:${HASH}-${{ matrix.arch }}" "${{ env.BUILDER_IMAGE }}:${HASH}" | |
| docker tag "${REMOTE}:${HASH}-${{ matrix.arch }}" "${{ env.BUILDER_IMAGE }}" | |
| echo "built=false" >> "$GITHUB_OUTPUT" | |
| else | |
| docker buildx build --progress=plain -t "${{ env.BUILDER_IMAGE }}:${HASH}" -t "${{ env.BUILDER_IMAGE }}" . | |
| echo "built=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Push builder image to GHCR | |
| if: github.ref == 'refs/heads/main' && steps.builder.outputs.built == 'true' | |
| run: | | |
| HASH="${{ steps.dockerfile-hash.outputs.hash }}" | |
| REMOTE="ghcr.io/${{ github.repository }}/${{ env.BUILDER_IMAGE }}" | |
| docker tag "${{ env.BUILDER_IMAGE }}:${HASH}" "${REMOTE}:${HASH}-${{ matrix.arch }}" | |
| docker push "${REMOTE}:${HASH}-${{ matrix.arch }}" | |
| - name: Compute kernel cache key | |
| id: kernel-cache-key | |
| run: echo "key=kernel-${{ matrix.arch }}-${{ env.KERNEL_VERSION }}-${{ hashFiles('kernel.configs/*', 'Dockerfile') }}" >> "$GITHUB_OUTPUT" | |
| - name: Restore kernel cache | |
| id: kernel-cache | |
| uses: actions/cache/restore@v5 | |
| with: | |
| path: | | |
| mkosi.output/kernel/${{ env.KERNEL_VERSION }}/${{ matrix.arch }} | |
| key: ${{ steps.kernel-cache-key.outputs.key }} | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v7 | |
| - name: Build kernel | |
| run: uv run ./build.py kernel | |
| - name: Fix output file ownership | |
| run: sudo chown -R "$(id -u):$(id -g)" mkosi.output/ | |
| - name: Save kernel cache | |
| if: github.ref == 'refs/heads/main' && steps.kernel-cache.outputs.cache-hit != 'true' | |
| uses: actions/cache/save@v5 | |
| with: | |
| path: | | |
| mkosi.output/kernel/${{ env.KERNEL_VERSION }}/${{ matrix.arch }} | |
| key: ${{ steps.kernel-cache-key.outputs.key }} | |
| - name: Upload kernel artifacts | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: kernel-${{ matrix.arch }} | |
| path: | | |
| mkosi.output/kernel/${{ env.KERNEL_VERSION }}/${{ matrix.arch }} | |
| retention-days: 1 | |
| # ------------------------------------------------------------------- | |
| # Download tools (containerd, runc, nerdctl, CNI plugins) | |
| # ------------------------------------------------------------------- | |
| download-tools: | |
| needs: [lint] | |
| runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| arch: [amd64, arm64] | |
| env: | |
| ARCH: ${{ matrix.arch }} | |
| KERNEL_MODE: skip | |
| MKOSI_MODE: skip | |
| TOOLS_MODE: native | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Restore tools cache | |
| id: tools-cache | |
| uses: actions/cache/restore@v5 | |
| with: | |
| path: | | |
| mkosi.output/tools/${{ matrix.arch }}/usr/local/bin | |
| mkosi.output/tools/${{ matrix.arch }}/opt/cni | |
| key: tools-${{ matrix.arch }}-${{ hashFiles('captain/tools.py') }} | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v7 | |
| - name: Download tools | |
| run: uv run ./build.py tools | |
| - name: Save tools cache | |
| if: github.ref == 'refs/heads/main' && steps.tools-cache.outputs.cache-hit != 'true' | |
| uses: actions/cache/save@v5 | |
| with: | |
| path: | | |
| mkosi.output/tools/${{ matrix.arch }}/usr/local/bin | |
| mkosi.output/tools/${{ matrix.arch }}/opt/cni | |
| key: tools-${{ matrix.arch }}-${{ hashFiles('captain/tools.py') }} | |
| - name: Upload tools artifacts | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: tools-${{ matrix.arch }} | |
| path: | | |
| mkosi.output/tools/${{ matrix.arch }}/usr/local/bin | |
| mkosi.output/tools/${{ matrix.arch }}/opt/cni | |
| retention-days: 1 | |
| # ------------------------------------------------------------------- | |
| # Build initramfs via mkosi (depends on kernel + tools) | |
| # ------------------------------------------------------------------- | |
| build-initramfs: | |
| runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} | |
| needs: [build-kernel, download-tools] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| arch: [amd64, arm64] | |
| env: | |
| ARCH: ${{ matrix.arch }} | |
| KERNEL_MODE: skip | |
| MKOSI_MODE: native | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Download kernel artifacts | |
| uses: actions/download-artifact@v6 | |
| with: | |
| name: kernel-${{ matrix.arch }} | |
| path: mkosi.output/kernel/${{ env.KERNEL_VERSION }}/${{ matrix.arch }} | |
| - name: Download tools artifacts | |
| uses: actions/download-artifact@v6 | |
| with: | |
| name: tools-${{ matrix.arch }} | |
| path: mkosi.output/tools/${{ matrix.arch }} | |
| - name: Restore tool binary permissions | |
| run: | | |
| # GitHub Actions artifact upload/download strips execute permissions. | |
| # Restore +x on all tool binaries so they work inside the initramfs. | |
| chmod +x mkosi.output/tools/${{ matrix.arch }}/usr/local/bin/* | |
| chmod +x mkosi.output/tools/${{ matrix.arch }}/opt/cni/bin/* | |
| - name: Refresh apt cache | |
| run: sudo apt-get -o "Dpkg::Use-Pty=0" update | |
| - name: setup-mkosi | |
| uses: systemd/mkosi@v26 | |
| - name: Install bubblewrap | |
| run: | | |
| sudo apt-get -o "Dpkg::Use-Pty=0" update | |
| sudo apt-get -o "Dpkg::Use-Pty=0" install -y bubblewrap | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v7 | |
| - name: Build initramfs | |
| run: uv run ./build.py initramfs | |
| - name: Upload initramfs artifacts | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: initramfs-${{ matrix.arch }} | |
| path: out/ | |
| retention-days: 1 | |
| # ------------------------------------------------------------------- | |
| # Build UEFI-bootable ISO (depends on initramfs) | |
| # ------------------------------------------------------------------- | |
| build-iso: | |
| runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} | |
| needs: [build-initramfs] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| arch: [amd64, arm64] | |
| include: | |
| - arch: amd64 | |
| output_arch: x86_64 | |
| - arch: arm64 | |
| output_arch: aarch64 | |
| env: | |
| ARCH: ${{ matrix.arch }} | |
| KERNEL_MODE: skip | |
| MKOSI_MODE: skip | |
| ISO_MODE: docker | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Load shared config | |
| run: cat .github/config.env >> "$GITHUB_ENV" | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Compute Dockerfile hash | |
| id: dockerfile-hash | |
| run: echo "hash=$(sha256sum Dockerfile | awk '{print $1}')" >> "$GITHUB_OUTPUT" | |
| - name: Pull or build builder image | |
| id: builder | |
| run: | | |
| HASH="${{ steps.dockerfile-hash.outputs.hash }}" | |
| REMOTE="ghcr.io/${{ github.repository }}/${{ env.BUILDER_IMAGE }}" | |
| if docker pull "${REMOTE}:${HASH}-${{ matrix.arch }}"; then | |
| docker tag "${REMOTE}:${HASH}-${{ matrix.arch }}" "${{ env.BUILDER_IMAGE }}:${HASH}" | |
| docker tag "${REMOTE}:${HASH}-${{ matrix.arch }}" "${{ env.BUILDER_IMAGE }}" | |
| echo "built=false" >> "$GITHUB_OUTPUT" | |
| else | |
| docker build -t "${{ env.BUILDER_IMAGE }}:${HASH}" -t "${{ env.BUILDER_IMAGE }}" . | |
| echo "built=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Push builder image to GHCR | |
| if: github.ref == 'refs/heads/main' && steps.builder.outputs.built == 'true' | |
| run: | | |
| HASH="${{ steps.dockerfile-hash.outputs.hash }}" | |
| REMOTE="ghcr.io/${{ github.repository }}/${{ env.BUILDER_IMAGE }}" | |
| docker tag "${{ env.BUILDER_IMAGE }}:${HASH}" "${REMOTE}:${HASH}-${{ matrix.arch }}" | |
| docker push "${REMOTE}:${HASH}-${{ matrix.arch }}" | |
| - name: Download kernel artifacts | |
| uses: actions/download-artifact@v6 | |
| with: | |
| name: kernel-${{ matrix.arch }} | |
| path: mkosi.output/kernel/${{ env.KERNEL_VERSION }}/${{ matrix.arch }} | |
| - name: Download initramfs artifacts | |
| uses: actions/download-artifact@v6 | |
| with: | |
| name: initramfs-${{ matrix.arch }} | |
| path: out | |
| - name: Stage initramfs for ISO build | |
| run: | | |
| mkdir -p "mkosi.output/initramfs/${KERNEL_VERSION}/${{ matrix.arch }}" | |
| cp "out/initramfs-${KERNEL_VERSION}-${{ matrix.output_arch }}" \ | |
| "mkosi.output/initramfs/${KERNEL_VERSION}/${{ matrix.arch }}/image.cpio.zst" | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v7 | |
| - name: Build ISO | |
| run: uv run ./build.py iso | |
| - name: Upload ISO artifact | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: iso-${{ matrix.arch }} | |
| path: out/captainos-${{ env.KERNEL_VERSION }}-${{ matrix.output_arch }}.iso | |
| retention-days: 1 | |
| # ------------------------------------------------------------------- | |
| # Publish per-arch artifacts and compute checksums | |
| # ------------------------------------------------------------------- | |
| publish-per-arch: | |
| if: github.ref == 'refs/heads/main' | |
| runs-on: ${{ matrix.target == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} | |
| needs: [build-iso] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| target: [amd64, arm64] | |
| env: | |
| ARCH: ${{ matrix.target }} | |
| TARGET: ${{ matrix.target }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Load shared config | |
| run: cat .github/config.env >> "$GITHUB_ENV" | |
| - name: Download kernel artifacts | |
| uses: actions/download-artifact@v6 | |
| with: | |
| name: kernel-${{ matrix.target }} | |
| path: mkosi.output/kernel/${{ env.KERNEL_VERSION }}/${{ matrix.target }} | |
| - name: Download initramfs artifacts | |
| uses: actions/download-artifact@v6 | |
| with: | |
| name: initramfs-${{ matrix.target }} | |
| path: out | |
| - name: Download ISO artifact | |
| uses: actions/download-artifact@v6 | |
| with: | |
| name: iso-${{ matrix.target }} | |
| path: out | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v7 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Publish artifacts to GHCR | |
| run: uv run ./build.py release publish | |
| # ------------------------------------------------------------------- | |
| # Publish combined multi-arch image (reuses per-arch registry blobs) | |
| # ------------------------------------------------------------------- | |
| publish-combined: | |
| if: github.ref == 'refs/heads/main' | |
| runs-on: ubuntu-latest | |
| needs: [publish-per-arch] | |
| env: | |
| ARCH: amd64 | |
| TARGET: combined | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Load shared config | |
| run: cat .github/config.env >> "$GITHUB_ENV" | |
| - name: Download kernel artifacts (amd64) | |
| uses: actions/download-artifact@v6 | |
| with: | |
| name: kernel-amd64 | |
| path: mkosi.output/kernel/${{ env.KERNEL_VERSION }}/amd64 | |
| - name: Download initramfs artifacts (amd64) | |
| uses: actions/download-artifact@v6 | |
| with: | |
| name: initramfs-amd64 | |
| path: out | |
| - name: Download ISO artifact (amd64) | |
| uses: actions/download-artifact@v6 | |
| with: | |
| name: iso-amd64 | |
| path: out | |
| - name: Download kernel artifacts (arm64) | |
| uses: actions/download-artifact@v6 | |
| with: | |
| name: kernel-arm64 | |
| path: mkosi.output/kernel/${{ env.KERNEL_VERSION }}/arm64 | |
| - name: Download initramfs artifacts (arm64) | |
| uses: actions/download-artifact@v6 | |
| with: | |
| name: initramfs-arm64 | |
| path: out | |
| - name: Download ISO artifact (arm64) | |
| uses: actions/download-artifact@v6 | |
| with: | |
| name: iso-arm64 | |
| path: out | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v7 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Publish combined image to GHCR | |
| run: uv run ./build.py release publish |