# Release the single-container image (server + web) to Docker Hub for amd64 and arm64. # # tag v1.2.3 -> hectorqin/reader-pro:1.2.3, :1.2, :latest, :sha- # manual run -> the chosen branch's name (e.g. :main) and :sha- # # Never on a plain push: main is the long-lived development branch, and a release # is a decision (a tag, or "Run workflow" in the Actions tab), not a side effect. # # CI (ci.yml) runs first; nothing is published from a red build. Each architecture # is built on its own native runner — the arm64 image under QEMU would run npm ci # and the Vite build emulated, which is many times slower — and pushed by digest, # then one job stitches the digests into the multi-arch tags. # # Needs repository secrets DOCKER_USERNAME and DOCKER_PASSWORD (a Docker Hub # password or access token with read/write on hectorqin/reader-pro). name: Release on: push: tags: ['v*'] workflow_dispatch: permissions: contents: read concurrency: group: release-${{ github.ref }} cancel-in-progress: false env: IMAGE: hectorqin/reader-pro jobs: ci: uses: ./.github/workflows/ci.yml build: name: Image · ${{ matrix.platform }} needs: ci strategy: fail-fast: true matrix: include: - platform: linux/amd64 runner: ubuntu-24.04 - platform: linux/arm64 runner: ubuntu-24.04-arm runs-on: ${{ matrix.runner }} steps: - name: Platform slug run: echo "PLATFORM_PAIR=${PLATFORM//\//-}" >> "$GITHUB_ENV" env: PLATFORM: ${{ matrix.platform }} - uses: actions/checkout@v5 - id: meta uses: docker/metadata-action@v5 with: images: ${{ env.IMAGE }} - uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - uses: docker/setup-buildx-action@v3 - id: build uses: docker/build-push-action@v6 with: context: . file: server/Dockerfile platforms: ${{ matrix.platform }} labels: ${{ steps.meta.outputs.labels }} outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true cache-from: type=gha,scope=${{ env.PLATFORM_PAIR }} cache-to: type=gha,mode=max,scope=${{ env.PLATFORM_PAIR }} - name: Record digest run: | mkdir -p "$RUNNER_TEMP/digests" touch "$RUNNER_TEMP/digests/${DIGEST#sha256:}" env: DIGEST: ${{ steps.build.outputs.digest }} - uses: actions/upload-artifact@v4 with: name: digests-${{ env.PLATFORM_PAIR }} path: ${{ runner.temp }}/digests/* if-no-files-found: error retention-days: 1 publish: name: Image · multi-arch tags needs: build runs-on: ubuntu-latest steps: - uses: actions/download-artifact@v4 with: path: ${{ runner.temp }}/digests pattern: digests-* merge-multiple: true - id: meta uses: docker/metadata-action@v5 with: images: ${{ env.IMAGE }} tags: | type=ref,event=branch type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=sha flavor: | latest=${{ startsWith(github.ref, 'refs/tags/v') }} - uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - uses: docker/setup-buildx-action@v3 - name: Create manifest list and push working-directory: ${{ runner.temp }}/digests run: | tags=() while read -r tag; do tags+=(-t "$tag"); done < <(jq -r '.tags[]' <<< "$DOCKER_METADATA_OUTPUT_JSON") refs=() for digest in *; do refs+=("$IMAGE@sha256:$digest"); done docker buildx imagetools create "${tags[@]}" "${refs[@]}" - name: Inspect run: docker buildx imagetools inspect "$IMAGE:$VERSION" env: VERSION: ${{ steps.meta.outputs.version }}