diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml index 4354040..0833710 100644 --- a/.github/workflows/publish-image.yml +++ b/.github/workflows/publish-image.yml @@ -22,10 +22,20 @@ permissions: attestations: write jobs: - build-and-push: - name: Build and push image - runs-on: ubuntu-latest + build: + name: Build and push image (${{ matrix.arch }}) + runs-on: ${{ matrix.runner }} timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + include: + - runner: ubuntu-latest + platform: linux/amd64 + arch: amd64 + - runner: ubuntu-24.04-arm + platform: linux/arm64 + arch: arm64 env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} @@ -33,9 +43,6 @@ jobs: - name: Checkout uses: actions/checkout@v5.0.0 - - name: Set up QEMU (multi-arch) - uses: docker/setup-qemu-action@v3.6.0 - - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3.11.1 @@ -51,13 +58,79 @@ jobs: shell: bash run: | set -eu - # Use provided IMAGE_NAME or default to the GitHub repository path IMAGE_REF="${IMAGE_NAME:-$GITHUB_REPOSITORY}" - # Lowercase image ref for GHCR compatibility CANONICAL_IMAGE="${REGISTRY}/${IMAGE_REF,,}" echo "canonical=${CANONICAL_IMAGE}" >> "$GITHUB_OUTPUT" - - name: Extract Docker metadata (tags, labels) + - name: Extract Docker metadata (tags, labels) with arch suffix + id: meta + uses: docker/metadata-action@v5.8.0 + with: + images: ${{ steps.vars.outputs.canonical }} + tags: | + type=ref,event=branch + type=ref,event=tag + type=sha,format=short,prefix=sha- + type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=semver,pattern={{major}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} + flavor: | + latest=auto + suffix=-${{ matrix.arch }} + + - name: Build and push (${{ matrix.platform }}) + id: build + uses: docker/build-push-action@v6.18.0 + with: + context: . + file: Dockerfile + platforms: ${{ matrix.platform }} + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha,scope=${{ matrix.platform }} + cache-to: type=gha,scope=${{ matrix.platform }},mode=max + sbom: true + provenance: true + + - name: Attest image provenance (per-arch) + if: github.event_name != 'pull_request' + uses: actions/attest-build-provenance@v3.0.0 + with: + subject-name: ${{ steps.vars.outputs.canonical }} + subject-digest: ${{ steps.build.outputs.digest }} + push-to-registry: true + + manifest: + name: Create multi-arch manifest and sign + needs: build + if: github.event_name != 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 20 + env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + steps: + - name: Set up Docker Buildx (for imagetools) + uses: docker/setup-buildx-action@v3.11.1 + + - name: Log in to GHCR + uses: docker/login-action@v3.5.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Compute canonical image name (lowercase) + id: vars + shell: bash + run: | + set -eu + IMAGE_REF="${IMAGE_NAME:-$GITHUB_REPOSITORY}" + CANONICAL_IMAGE="${REGISTRY}/${IMAGE_REF,,}" + echo "canonical=${CANONICAL_IMAGE}" >> "$GITHUB_OUTPUT" + + - name: Compute base tags (no suffix) id: meta uses: docker/metadata-action@v5.8.0 with: @@ -72,34 +145,35 @@ jobs: flavor: | latest=auto - - name: Build and push - id: build - uses: docker/build-push-action@v6.18.0 - with: - context: . - file: Dockerfile - platforms: linux/amd64,linux/arm64 - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - sbom: true - provenance: true + - name: Create multi-arch manifests + shell: bash + run: | + set -eu + tags="${{ steps.meta.outputs.tags }}" + echo "Creating manifests for tags:" + printf '%s\n' "$tags" + while IFS= read -r tag; do + [ -z "$tag" ] && continue + echo "Creating manifest for $tag" + docker buildx imagetools create \ + --tag "$tag" \ + "${tag}-amd64" \ + "${tag}-arm64" + done <<< "$tags" - - name: Sign image with Cosign (keyless OIDC) - if: github.event_name != 'pull_request' + - name: Install Cosign uses: sigstore/cosign-installer@v3.9.2 - - name: Cosign sign - if: github.event_name != 'pull_request' - run: cosign sign --yes ${{ steps.vars.outputs.canonical }}@${{ steps.build.outputs.digest }} - - name: Attest image provenance - if: github.event_name != 'pull_request' - uses: actions/attest-build-provenance@v3.0.0 - with: - subject-name: ${{ steps.vars.outputs.canonical }} - subject-digest: ${{ steps.build.outputs.digest }} - push-to-registry: true + - name: Cosign sign all tags (keyless OIDC) + shell: bash + run: | + set -eu + tags="${{ steps.meta.outputs.tags }}" + printf '%s\n' "$tags" + while IFS= read -r tag; do + [ -z "$tag" ] && continue + echo "Signing $tag" + cosign sign --yes "$tag" + done <<< "$tags"