name: Build and publish container image to GHCR on: push: branches: - main tags: - 'v*' workflow_dispatch: concurrency: group: publish-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: contents: read packages: write id-token: write attestations: write env: IMAGE_DESCRIPTION: Fizzy is Kanban as it should be. Not as it has been. SOURCE_URL: https://github.com/${{ github.repository }} jobs: 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 }} steps: - name: Checkout uses: actions/checkout@v6 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3.12.0 - name: Log in to GHCR uses: docker/login-action@v3.7.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: Extract Docker metadata (tags, labels) with arch suffix id: meta uses: docker/metadata-action@v5.10.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=false suffix=-${{ matrix.arch }} labels: | org.opencontainers.image.source=${{ env.SOURCE_URL }} - name: Build and push (${{ matrix.platform }}) id: build uses: docker/build-push-action@v6.19.2 with: context: . file: Dockerfile build-args: | OCI_SOURCE=${{ env.SOURCE_URL }} OCI_DESCRIPTION=${{ env.IMAGE_DESCRIPTION }} 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: false provenance: false - name: Attest image provenance (per-arch) if: github.event_name != 'pull_request' uses: actions/attest-build-provenance@v4.1.0 with: subject-name: ${{ steps.vars.outputs.canonical }} subject-digest: ${{ steps.build.outputs.digest }} push-to-registry: false 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.12.0 - name: Log in to GHCR uses: docker/login-action@v3.7.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.10.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') }} type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }} flavor: | latest=false labels: | org.opencontainers.image.source=${{ env.SOURCE_URL }} - 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" src_tag="$tag" if [[ "$tag" == *:latest && "${GITHUB_REF}" == refs/tags/* ]]; then ref="${GITHUB_REF#refs/tags/}" src_tag="${tag%:latest}:$ref" fi if [ -n "${IMAGE_DESCRIPTION:-}" ]; then docker buildx imagetools create \ --tag "$tag" \ --annotation "index:org.opencontainers.image.description=${IMAGE_DESCRIPTION}" \ "${src_tag}-amd64" \ "${src_tag}-arm64" else docker buildx imagetools create \ --tag "$tag" \ "${src_tag}-amd64" \ "${src_tag}-arm64" fi done <<< "$tags" - name: Install Cosign uses: sigstore/cosign-installer@v4.0.0 - 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"