Build and Push Docker Image (GHCR + Docker Hub) #71
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: Build and Push Docker Image (GHCR + Docker Hub) | |
| on: | |
| push: | |
| branches: [ main ] | |
| tags: | |
| - 'v*' | |
| paths: | |
| - 'src/**' | |
| - 'Cargo.toml' | |
| - 'Cargo.lock' | |
| - 'Dockerfile' | |
| - '.dockerignore' | |
| - '.github/workflows/docker-build-push.yml' | |
| pull_request: | |
| branches: [ main ] | |
| paths: | |
| - 'src/**' | |
| - 'Cargo.toml' | |
| - 'Cargo.lock' | |
| - 'Dockerfile' | |
| - '.dockerignore' | |
| workflow_dispatch: | |
| env: | |
| GHCR_IMAGE: ghcr.io/clutchprotocol/clutch-node | |
| DH_IMAGE: docker.io/9194010019/clutch-node | |
| permissions: | |
| contents: read | |
| packages: write | |
| jobs: | |
| # One job per architecture on a NATIVE runner. arm64 used to be cross-built with | |
| # QEMU on ubuntu-latest, which emulates every instruction — rocksdb's C++ compile | |
| # alone ran 5-10x slower there. GitHub-hosted arm64 runners are free for public | |
| # repos, so both arches now compile natively and in parallel. | |
| build: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: linux/amd64 | |
| runner: ubuntu-latest | |
| - platform: linux/arm64 | |
| runner: ubuntu-24.04-arm | |
| runs-on: ${{ matrix.runner }} | |
| steps: | |
| - name: Platform slug (linux/amd64 -> linux-amd64) | |
| run: echo "SLUG=${{ matrix.platform }}" | tr '/' '-' >> "$GITHUB_ENV" | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GHCR | |
| if: github.event_name != 'pull_request' | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| # Cache lives in GHCR, not type=gha. The Actions cache backend caps at 10 GB | |
| # per repo; mode=max on a Rust build (deps target dir, x2 arches) blew past | |
| # that every run, so the dependency layer was evicted and rocksdb rebuilt from | |
| # scratch. A registry cache has no such cap. One cache tag per arch — a shared | |
| # tag makes the two runners overwrite each other's manifest. | |
| # | |
| # Pushed by digest only; the `merge` job assembles the tagged manifest lists. | |
| - name: Build and push by digest | |
| id: build | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| file: ./Dockerfile | |
| platforms: ${{ matrix.platform }} | |
| provenance: false | |
| cache-from: type=registry,ref=${{ env.GHCR_IMAGE }}:buildcache-${{ env.SLUG }} | |
| # ignore-error: cache export intermittently fails after a successful push | |
| # (seen 2026-07-24, run 30084470164) — that must not fail the job, since | |
| # trigger-stage-deploy needs this and a failed export would block deploys. | |
| cache-to: ${{ github.event_name != 'pull_request' && format('type=registry,ref={0}:buildcache-{1},mode=max,ignore-error=true', env.GHCR_IMAGE, env.SLUG) || '' }} | |
| # Empty on PRs: build-only verification, no output, no registry writes. | |
| outputs: ${{ github.event_name != 'pull_request' && format('type=image,name={0},push-by-digest=true,name-canonical=true,push=true', env.GHCR_IMAGE) || '' }} | |
| - name: Export digest | |
| if: github.event_name != 'pull_request' | |
| run: | | |
| mkdir -p /tmp/digests | |
| digest='${{ steps.build.outputs.digest }}' | |
| touch "/tmp/digests/${digest#sha256:}" | |
| - name: Upload digest | |
| if: github.event_name != 'pull_request' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: digests-${{ env.SLUG }} | |
| path: /tmp/digests/* | |
| if-no-files-found: error | |
| retention-days: 1 | |
| # Assemble the multi-arch manifest from the per-arch digests. No rebuild happens | |
| # here — imagetools copies manifests/blobs, including cross-registry to Docker Hub. | |
| merge: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| if: github.event_name != 'pull_request' | |
| steps: | |
| - name: Download digests | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: /tmp/digests | |
| pattern: digests-* | |
| merge-multiple: true | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Log in to Docker Hub | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: docker.io | |
| username: ${{ secrets.DOCKERHUB_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| - name: Extract Docker metadata | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: | | |
| ${{ env.GHCR_IMAGE }} | |
| ${{ env.DH_IMAGE }} | |
| tags: | | |
| # sha-<short> | |
| type=sha,format=short | |
| # latest فقط روی برنچ پیشفرض (main) | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| # اگر تگ گیت (v1.2.3) داشتی همون رو tag کن | |
| type=ref,event=tag | |
| - name: Create manifest lists on both registries | |
| working-directory: /tmp/digests | |
| run: | | |
| # shellcheck disable=SC2046 # word splitting is the point: expands to | |
| # `-t img:tag -t img:tag ...` and one `img@sha256:...` per digest file. | |
| docker buildx imagetools create \ | |
| $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ | |
| $(printf '${{ env.GHCR_IMAGE }}@sha256:%s ' *) | |
| - name: Image digest | |
| run: docker buildx imagetools inspect ${{ env.GHCR_IMAGE }}:${{ steps.meta.outputs.version }} | |
| # Optional: same secret as other Clutch repos — PAT with repo access to dispatch on clutch-deploy. | |
| trigger-stage-deploy: | |
| needs: merge | |
| runs-on: ubuntu-latest | |
| if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' | |
| steps: | |
| - name: Trigger deploy-stage in clutch-deploy | |
| continue-on-error: true | |
| uses: peter-evans/repository-dispatch@v3 | |
| with: | |
| token: ${{ secrets.CLUTCH_DEPLOY_DISPATCH_TOKEN }} | |
| repository: clutchprotocol/clutch-deploy | |
| event-type: deploy-stage | |
| update-description: | |
| needs: merge | |
| runs-on: ubuntu-latest | |
| if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Update Docker Hub description | |
| uses: peter-evans/dockerhub-description@v3 | |
| with: | |
| username: ${{ secrets.DOCKERHUB_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| repository: 9194010019/clutch-node | |
| readme-filepath: ./README.md |