name: Publish # Builds `:X.Y.Z` and `:latest` tags of `ghcr.io/.../trawl` from # `apps/api/Dockerfile` on `v*` release tag pushes. The `v` prefix is stripped # per Docker convention (git tag `v1.0.0` -> docker tag `1.0.0`); `:latest` # auto-attaches via the default `latest=auto` flavor of `type=semver`. # # Lives separately from publish-nightly.yml (main branch pushes) and # publish-baseline.yml (baseline runtime pushes). `v*-baseline` is # intentionally excluded so a baseline-only release doesn't also push # `:X.Y.Z`/`:latest` — publish-baseline.yml owns that path. # # Tag contract: # - push `v*` -> :X.Y.Z + :latest # - push `v*-baseline` -> nothing here (excluded) # - push `main` -> nothing here (handled by publish-nightly.yml) on: push: tags: # Release tags — pure semver only. `v*-baseline` is intentionally excluded # so a baseline-only release (e.g., `v1.0.0-baseline`) does NOT push `:X.Y.Z` # or `:latest` from this workflow; publish-baseline.yml owns that path. - "v*" - "!v*-baseline" env: IMAGE: ghcr.io/${{ github.repository_owner }}/trawl jobs: build: strategy: matrix: include: - platform: linux/amd64 runner: ubuntu-latest - platform: linux/arm64 runner: ubuntu-24.04-arm runs-on: ${{ matrix.runner }} permissions: contents: read packages: write steps: - uses: actions/checkout@v7 - uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/setup-buildx-action@v4 - uses: docker/metadata-action@v6 id: meta with: images: ${{ env.IMAGE }} - name: Sanitize platform name run: | platform=${{ matrix.platform }} echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - uses: docker/build-push-action@v7 id: build with: context: . file: apps/api/Dockerfile platforms: ${{ matrix.platform }} push: true labels: ${{ steps.meta.outputs.labels }} outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true cache-from: type=gha,scope=trawl-release-${{ matrix.platform }} cache-to: type=gha,scope=trawl-release-${{ matrix.platform }},mode=max secrets: | GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} - name: Export digest run: | mkdir -p /tmp/digests digest="${{ steps.build.outputs.digest }}" touch "/tmp/digests/${digest#sha256:}" - uses: actions/upload-artifact@v7 with: name: digests-release-${{ env.PLATFORM_PAIR }} path: /tmp/digests/* if-no-files-found: error retention-days: 1 merge: runs-on: ubuntu-latest needs: build permissions: contents: read packages: write steps: - uses: actions/download-artifact@v8 with: path: /tmp/digests pattern: digests-release-* merge-multiple: true - uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/setup-buildx-action@v4 - uses: docker/metadata-action@v6 id: meta with: images: ${{ env.IMAGE }} tags: | # Versioned release tag: git tag v1.0.0 → docker tag 1.0.0 (strips the v prefix, # per Docker convention; type=ref would mirror the v through). type=semver also # auto-attaches :latest via the default latest=auto flavor. type=semver,pattern={{version}} - name: Create and push multi-arch manifest working-directory: /tmp/digests run: | docker buildx imagetools create \ $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ $(printf '${{ env.IMAGE }}@sha256:%s ' *) - name: Inspect manifest run: | # Pull the first tag actually pushed by docker/metadata-action in the previous # step. DOCKER_METADATA_OUTPUT_JSON.tags[] is already fully-qualified # ("ghcr.io/germondai/trawl:1.0.0" / ":latest") so no $IMAGE prefix needed. tag=$(jq -r '.tags[0]' <<< "$DOCKER_METADATA_OUTPUT_JSON") docker buildx imagetools inspect "$tag"