ci(docker): lowercase the native GHCR image reference so the per-arch… #46
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: docker | |
| # Build (and, except on pull requests, publish) the multi-architecture Graphus container image — | |
| # linux/amd64 + linux/arm64 — to the GitHub Container Registry. | |
| # | |
| # NATIVE CPUs: on trusted events each architecture is built on its OWN native self-hosted runner — | |
| # amd64 on `wg1` (Linux x86_64) and arm64 on `pi516` (Linux aarch64 / Raspberry Pi) — with NO QEMU | |
| # emulation. Each per-arch build pushes its image by digest; a final `merge` job assembles the | |
| # multi-arch manifest list (`:vX.Y.Z`, `:X.Y`, `:latest` for tags, `:main` for main) from those | |
| # digests. | |
| # | |
| # * Pull requests → a quick amd64-only Dockerfile check on a GitHub-hosted runner (no push, and — | |
| # a public-repo security requirement — never on a self-hosted machine). | |
| # * Pushes to main → the native per-arch build + a `:main` multi-arch manifest (a dev image; the | |
| # release image is handled by dockerhub.yml). | |
| # * Tags `v*` / dispatch → the native per-arch build + a full `:vX.Y.Z` / `:X.Y` / `:latest` manifest. | |
| on: | |
| push: | |
| branches: [main] | |
| tags: ["v*"] | |
| pull_request: | |
| paths: | |
| - "Dockerfile" | |
| - ".dockerignore" | |
| - "docker/**" | |
| - "docker-compose.yml" | |
| - ".github/workflows/docker.yml" | |
| - "crates/**" | |
| - "Cargo.toml" | |
| - "Cargo.lock" | |
| workflow_dispatch: | |
| concurrency: | |
| group: docker-${{ github.ref }} | |
| # Only cancel superseded pull-request builds (they never push). A run on main, a tag, or a dispatch | |
| # publishes a manifest, and a half-pushed manifest is worse than waiting — so those are never cancelled. | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| # Build each architecture on its native self-hosted runner and push it BY DIGEST (no tag). | |
| build: | |
| if: github.event_name != 'pull_request' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: linux/amd64 | |
| runner: [self-hosted, Linux, X64] | |
| - platform: linux/arm64 | |
| runner: [self-hosted, Linux, ARM64] | |
| runs-on: ${{ matrix.runner }} | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Prepare platform pair + lowercase image name | |
| run: | | |
| platform="${{ matrix.platform }}" | |
| echo "PLATFORM_PAIR=${platform//\//-}" >> "$GITHUB_ENV" | |
| # GHCR (and Docker) reject uppercase repository names; github.repository preserves the | |
| # owner's case (FlavioCFOliveira/Graphus), so lowercase it for the image reference. | |
| echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV" | |
| - uses: actions/checkout@v7 | |
| - name: Derive image labels | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ${{ env.IMAGE }} | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build and push by digest (native ${{ matrix.platform }}) | |
| id: build | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| file: ./Dockerfile | |
| platforms: ${{ matrix.platform }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true | |
| provenance: true | |
| sbom: true | |
| cache-from: type=gha,scope=ghcr-${{ env.PLATFORM_PAIR }} | |
| cache-to: type=gha,mode=max,scope=ghcr-${{ env.PLATFORM_PAIR }} | |
| - name: Export digest | |
| run: | | |
| mkdir -p "${{ runner.temp }}/digests" | |
| digest="${{ steps.build.outputs.digest }}" | |
| touch "${{ runner.temp }}/digests/${digest#sha256:}" | |
| - name: Upload digest | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ghcr-digests-${{ env.PLATFORM_PAIR }} | |
| path: ${{ runner.temp }}/digests/* | |
| if-no-files-found: error | |
| retention-days: 1 | |
| # Assemble the multi-arch manifest list from the per-arch digests and push it with the real tags. | |
| merge: | |
| if: github.event_name != 'pull_request' | |
| needs: [build] | |
| runs-on: [self-hosted, Linux, X64] | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Lowercase image name | |
| run: echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV" | |
| - name: Download digests | |
| uses: actions/download-artifact@v7 | |
| with: | |
| path: ${{ runner.temp }}/digests | |
| pattern: ghcr-digests-* | |
| merge-multiple: true | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Derive image tags | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ${{ env.IMAGE }} | |
| tags: | | |
| type=ref,event=branch | |
| type=semver,pattern={{version}} | |
| type=semver,pattern={{major}}.{{minor}} | |
| type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }} | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Create and push the multi-arch manifest | |
| working-directory: ${{ runner.temp }}/digests | |
| run: | | |
| tag_args="" | |
| while IFS= read -r tag; do | |
| [ -n "$tag" ] && tag_args="$tag_args -t $tag" | |
| done <<< "${{ steps.meta.outputs.tags }}" | |
| # shellcheck disable=SC2046 | |
| docker buildx imagetools create $tag_args \ | |
| $(printf '${{ env.IMAGE }}@sha256:%s ' *) | |
| - name: Inspect the published manifest | |
| run: docker buildx imagetools inspect "${{ env.IMAGE }}:${{ steps.meta.outputs.version }}" | |
| # Pull requests: a fast amd64-only Dockerfile check on a GitHub-hosted runner (no push). Never runs | |
| # on a self-hosted machine, so untrusted fork-PR code cannot execute there. | |
| pr-check: | |
| if: github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Build (amd64, no push) | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| file: ./Dockerfile | |
| platforms: linux/amd64 | |
| push: false | |
| cache-from: type=gha,scope=ghcr-pr | |
| cache-to: type=gha,mode=max,scope=ghcr-pr |