Skip to content
Latchkey

Release workflow (pgrok/pgrok)

The Release workflow from pgrok/pgrok, explained and optimized by Latchkey.

A

CI health: A - excellent

Point runs-on at Latchkey and get job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.

Grade your own workflow free or run it on Latchkey →
Source: pgrok/pgrok.github/workflows/release.ymlLicense MITView source

What it does

This is the Release workflow from the pgrok/pgrok repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
name: Release

on:
  release:
    types: [published]
  push:
    branches:
      - main
  pull_request:
    paths:
      - '.github/workflows/release.yml'

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

env:
  GOPROXY: "https://proxy.golang.org"

permissions:
  contents: write

jobs:
  prepare-release:
    name: Prepare release
    if: ${{ github.repository == 'pgrok/pgrok' }}
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.version.outputs.version }}
      release_tag: ${{ steps.version.outputs.release_tag }}
    steps:
      - name: Check out code
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
      - name: Determine version
        id: version
        run: |
          if [ "${{ github.event_name }}" = "release" ]; then
            release_tag="${{ github.event.release.tag_name }}"
            version="${release_tag#v}"
          elif [ "${{ github.event_name }}" = "push" ] && [ "${{ github.ref }}" = "refs/heads/main" ]; then
            release_tag="latest-commit-build"
            version="$(git rev-parse --short HEAD)"
          else
            release_tag="release-archive-testing"
            version="$(git rev-parse --short HEAD)"
          fi

          {
            echo "version=$version"
            echo "release_tag=$release_tag"
          } >> "$GITHUB_OUTPUT"
      - name: Ensure rolling tag and release exist
        if: ${{ github.event_name != 'release' }}
        env:
          GH_TOKEN: ${{ github.token }}
          RELEASE_TAG: ${{ steps.version.outputs.release_tag }}
        run: |
          git tag -f "$RELEASE_TAG"
          git push origin "$RELEASE_TAG" --force

          RELEASE_TITLE="Release archive testing"
          RELEASE_NOTES="Automated testing release for workflow development."
          if [ "$RELEASE_TAG" = "latest-commit-build" ]; then
            RELEASE_TITLE="Latest commit build"
            RELEASE_NOTES="Automated build from the latest commit on main branch. This release is updated automatically with every push to main."
          fi

          # Single-flight: this job is the only one that creates the release,
          # so the build matrix never races on `gh release create`.
          if ! gh release view "$RELEASE_TAG" >/dev/null 2>&1; then
            gh release create "$RELEASE_TAG" \
              --title "$RELEASE_TITLE" \
              --notes "$RELEASE_NOTES" \
              --prerelease
          fi

  pgrok:
    name: pgrok ${{ matrix.goos }}/${{ matrix.goarch }}
    needs: prepare-release
    if: ${{ github.repository == 'pgrok/pgrok' }}
    runs-on: ${{ matrix.runner }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - {goos: linux, goarch: amd64, runner: ubuntu-latest}
          - {goos: linux, goarch: arm64, runner: ubuntu-latest}
          - {goos: linux, goarch: "386", runner: ubuntu-latest}
          - {goos: darwin, goarch: amd64, runner: macos-latest}
          - {goos: darwin, goarch: arm64, runner: macos-latest}
          - {goos: windows, goarch: amd64, runner: ubuntu-latest}
          - {goos: windows, goarch: arm64, runner: ubuntu-latest}
          - {goos: windows, goarch: "386", runner: ubuntu-latest}
    steps:
      - name: Check out code
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
      - name: Set up Go
        uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
        with:
          go-version: 1.26.x
      - name: Build binary
        env:
          GOOS: ${{ matrix.goos }}
          GOARCH: ${{ matrix.goarch }}
          CGO_ENABLED: "0"
        run: |
          mkdir -p dist
          BINARY_NAME="pgrok"
          if [ "${{ matrix.goos }}" = "windows" ]; then
            BINARY_NAME="pgrok.exe"
          fi

          go build -v \
            -ldflags "-X main.version=${{ needs.prepare-release.outputs.version }}" \
            -trimpath -o "dist/$BINARY_NAME" ./pgrok/cli
      - name: Import Apple signing certificate
        if: ${{ matrix.goos == 'darwin' }}
        env:
          APPLE_DEVELOPER_ID_CERTIFICATE_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_BASE64 }}
          APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }}
          APPLE_KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
        run: |
          if [ -z "$APPLE_DEVELOPER_ID_CERTIFICATE_BASE64" ] || [ -z "$APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD" ] || [ -z "$APPLE_KEYCHAIN_PASSWORD" ]; then
            echo "Missing required Apple signing secrets." >&2
            exit 1
          fi

          CERTIFICATE_PATH="$RUNNER_TEMP/developer_id_application.p12"
          KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"

          printf '%s' "$APPLE_DEVELOPER_ID_CERTIFICATE_BASE64" | base64 -d > "$CERTIFICATE_PATH"
          security create-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
          security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
          security unlock-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
          security import "$CERTIFICATE_PATH" -P "$APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
          security list-keychains -d user -s "$KEYCHAIN_PATH"
          security default-keychain -s "$KEYCHAIN_PATH"
          security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
      - name: Sign macOS binary
        if: ${{ matrix.goos == 'darwin' }}
        env:
          APPLE_DEVELOPER_IDENTITY: ${{ secrets.APPLE_DEVELOPER_IDENTITY }}
        run: |
          if [ -z "$APPLE_DEVELOPER_IDENTITY" ]; then
            echo "Missing required Apple signing identity secret." >&2
            exit 1
          fi

          security find-identity -v -p codesigning
          codesign --force --options runtime --timestamp --sign "$APPLE_DEVELOPER_IDENTITY" "dist/pgrok"
          codesign --verify --verbose=2 "dist/pgrok"
      - name: Create archives
        working-directory: dist
        run: |
          VERSION="${{ needs.prepare-release.outputs.version }}"
          ARCHIVE_BASE="pgrok_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}"

          BINARY_NAME="pgrok"
          if [ "${{ matrix.goos }}" = "windows" ]; then
            BINARY_NAME="pgrok.exe"
          fi

          zip "${ARCHIVE_BASE}.zip" "$BINARY_NAME"
          if [ "${{ matrix.goos }}" = "linux" ]; then
            tar -czvf "${ARCHIVE_BASE}.tar.gz" "$BINARY_NAME"
          fi
      - name: Notarize macOS archive
        if: ${{ matrix.goos == 'darwin' }}
        env:
          APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
          APPLE_NOTARY_KEY_BASE64: ${{ secrets.APPLE_NOTARY_KEY_BASE64 }}
          APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
        run: |
          if [ -z "$APPLE_NOTARY_ISSUER_ID" ] || [ -z "$APPLE_NOTARY_KEY_BASE64" ] || [ -z "$APPLE_NOTARY_KEY_ID" ]; then
            echo "Missing required Apple notarization secrets." >&2
            exit 1
          fi

          VERSION="${{ needs.prepare-release.outputs.version }}"
          ARCHIVE_PATH="dist/pgrok_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}.zip"
          NOTARY_KEY_PATH="$RUNNER_TEMP/AuthKey_${APPLE_NOTARY_KEY_ID}.p8"

          printf '%s' "$APPLE_NOTARY_KEY_BASE64" | base64 -d > "$NOTARY_KEY_PATH"
          xcrun notarytool submit "$ARCHIVE_PATH" \
            --key "$NOTARY_KEY_PATH" \
            --key-id "$APPLE_NOTARY_KEY_ID" \
            --issuer "$APPLE_NOTARY_ISSUER_ID" \
            --wait
      - name: Upload to release
        env:
          GH_TOKEN: ${{ github.token }}
          RELEASE_TAG: ${{ needs.prepare-release.outputs.release_tag }}
        run: |
          PATTERN="pgrok_.*_${{ matrix.goos }}_${{ matrix.goarch }}\."
          gh release view "$RELEASE_TAG" --json assets --jq ".assets[].name" | grep -E "$PATTERN" | while read -r asset; do
            gh release delete-asset "$RELEASE_TAG" "$asset" --yes || true
          done

          gh release upload "$RELEASE_TAG" dist/pgrok_*.zip --clobber
          if [ "${{ matrix.goos }}" = "linux" ]; then
            gh release upload "$RELEASE_TAG" dist/pgrok_*.tar.gz --clobber
          fi

  pgrokd:
    name: pgrokd ${{ matrix.goos }}/${{ matrix.goarch }}
    needs: prepare-release
    if: ${{ github.repository == 'pgrok/pgrok' }}
    runs-on: ${{ matrix.runner }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - {goos: linux, goarch: amd64, runner: ubuntu-latest}
          - {goos: linux, goarch: arm64, runner: ubuntu-latest}
          - {goos: linux, goarch: "386", runner: ubuntu-latest}
          - {goos: darwin, goarch: amd64, runner: macos-latest}
          - {goos: darwin, goarch: arm64, runner: macos-latest}
          - {goos: windows, goarch: amd64, runner: ubuntu-latest}
          - {goos: windows, goarch: arm64, runner: ubuntu-latest}
          - {goos: windows, goarch: "386", runner: ubuntu-latest}
    steps:
      - name: Check out code
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
      - name: Set up Go
        uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
        with:
          go-version: 1.26.x
      - name: Install pnpm
        uses: pnpm/action-setup@9fd676a19091d4595eefd76e4bd31c97133911f1 # v4.2.0
        with:
          version: 11
          run_install: |
            - cwd: pgrokd/web
      - name: Build web app
        run: pnpm --dir pgrokd/web run build
      - name: Build binary
        env:
          GOOS: ${{ matrix.goos }}
          GOARCH: ${{ matrix.goarch }}
          CGO_ENABLED: "0"
        run: |
          mkdir -p dist
          BINARY_NAME="pgrokd"
          if [ "${{ matrix.goos }}" = "windows" ]; then
            BINARY_NAME="pgrokd.exe"
          fi

          go build -v -tags prod \
            -ldflags "-X main.version=${{ needs.prepare-release.outputs.version }}" \
            -trimpath -o "dist/$BINARY_NAME" ./pgrokd/cli
      - name: Import Apple signing certificate
        if: ${{ matrix.goos == 'darwin' }}
        env:
          APPLE_DEVELOPER_ID_CERTIFICATE_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_BASE64 }}
          APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }}
          APPLE_KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
        run: |
          if [ -z "$APPLE_DEVELOPER_ID_CERTIFICATE_BASE64" ] || [ -z "$APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD" ] || [ -z "$APPLE_KEYCHAIN_PASSWORD" ]; then
            echo "Missing required Apple signing secrets." >&2
            exit 1
          fi

          CERTIFICATE_PATH="$RUNNER_TEMP/developer_id_application.p12"
          KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"

          printf '%s' "$APPLE_DEVELOPER_ID_CERTIFICATE_BASE64" | base64 -d > "$CERTIFICATE_PATH"
          security create-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
          security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
          security unlock-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
          security import "$CERTIFICATE_PATH" -P "$APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
          security list-keychains -d user -s "$KEYCHAIN_PATH"
          security default-keychain -s "$KEYCHAIN_PATH"
          security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
      - name: Sign macOS binary
        if: ${{ matrix.goos == 'darwin' }}
        env:
          APPLE_DEVELOPER_IDENTITY: ${{ secrets.APPLE_DEVELOPER_IDENTITY }}
        run: |
          if [ -z "$APPLE_DEVELOPER_IDENTITY" ]; then
            echo "Missing required Apple signing identity secret." >&2
            exit 1
          fi

          security find-identity -v -p codesigning
          codesign --force --options runtime --timestamp --sign "$APPLE_DEVELOPER_IDENTITY" "dist/pgrokd"
          codesign --verify --verbose=2 "dist/pgrokd"
      - name: Create archives
        working-directory: dist
        run: |
          VERSION="${{ needs.prepare-release.outputs.version }}"
          ARCHIVE_BASE="pgrokd_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}"

          BINARY_NAME="pgrokd"
          if [ "${{ matrix.goos }}" = "windows" ]; then
            BINARY_NAME="pgrokd.exe"
          fi

          zip "${ARCHIVE_BASE}.zip" "$BINARY_NAME"
          if [ "${{ matrix.goos }}" = "linux" ]; then
            tar -czvf "${ARCHIVE_BASE}.tar.gz" "$BINARY_NAME"
          fi
      - name: Notarize macOS archive
        if: ${{ matrix.goos == 'darwin' }}
        env:
          APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
          APPLE_NOTARY_KEY_BASE64: ${{ secrets.APPLE_NOTARY_KEY_BASE64 }}
          APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
        run: |
          if [ -z "$APPLE_NOTARY_ISSUER_ID" ] || [ -z "$APPLE_NOTARY_KEY_BASE64" ] || [ -z "$APPLE_NOTARY_KEY_ID" ]; then
            echo "Missing required Apple notarization secrets." >&2
            exit 1
          fi

          VERSION="${{ needs.prepare-release.outputs.version }}"
          ARCHIVE_PATH="dist/pgrokd_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}.zip"
          NOTARY_KEY_PATH="$RUNNER_TEMP/AuthKey_${APPLE_NOTARY_KEY_ID}.p8"

          printf '%s' "$APPLE_NOTARY_KEY_BASE64" | base64 -d > "$NOTARY_KEY_PATH"
          xcrun notarytool submit "$ARCHIVE_PATH" \
            --key "$NOTARY_KEY_PATH" \
            --key-id "$APPLE_NOTARY_KEY_ID" \
            --issuer "$APPLE_NOTARY_ISSUER_ID" \
            --wait
      - name: Upload to release
        env:
          GH_TOKEN: ${{ github.token }}
          RELEASE_TAG: ${{ needs.prepare-release.outputs.release_tag }}
        run: |
          PATTERN="pgrokd_.*_${{ matrix.goos }}_${{ matrix.goarch }}\."
          gh release view "$RELEASE_TAG" --json assets --jq ".assets[].name" | grep -E "$PATTERN" | while read -r asset; do
            gh release delete-asset "$RELEASE_TAG" "$asset" --yes || true
          done

          gh release upload "$RELEASE_TAG" dist/pgrokd_*.zip --clobber
          if [ "${{ matrix.goos }}" = "linux" ]; then
            gh release upload "$RELEASE_TAG" dist/pgrokd_*.tar.gz --clobber
          fi

  notify-failure:
    name: Notify on failure
    runs-on: ubuntu-latest
    needs: [pgrok, pgrokd]
    if: ${{ failure() && github.repository == 'pgrok/pgrok' && github.event_name == 'push' && github.ref == 'refs/heads/main' }}
    steps:
      - name: Send email on failure
        uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
        with:
          smtp_username: ${{ secrets.SMTP_USERNAME }}
          smtp_password: ${{ secrets.SMTP_PASSWORD }}

The same workflow, on Latchkey

Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.

name: Release
 
on:
  release:
    types: [published]
  push:
    branches:
      - main
  pull_request:
    paths:
      - '.github/workflows/release.yml'
 
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
env:
  GOPROXY: "https://proxy.golang.org"
 
permissions:
  contents: write
 
jobs:
  prepare-release:
    timeout-minutes: 30
    name: Prepare release
    if: ${{ github.repository == 'pgrok/pgrok' }}
    runs-on: latchkey-small
    outputs:
      version: ${{ steps.version.outputs.version }}
      release_tag: ${{ steps.version.outputs.release_tag }}
    steps:
      - name: Check out code
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
      - name: Determine version
        id: version
        run: |
          if [ "${{ github.event_name }}" = "release" ]; then
            release_tag="${{ github.event.release.tag_name }}"
            version="${release_tag#v}"
          elif [ "${{ github.event_name }}" = "push" ] && [ "${{ github.ref }}" = "refs/heads/main" ]; then
            release_tag="latest-commit-build"
            version="$(git rev-parse --short HEAD)"
          else
            release_tag="release-archive-testing"
            version="$(git rev-parse --short HEAD)"
          fi
 
          {
            echo "version=$version"
            echo "release_tag=$release_tag"
          } >> "$GITHUB_OUTPUT"
      - name: Ensure rolling tag and release exist
        if: ${{ github.event_name != 'release' }}
        env:
          GH_TOKEN: ${{ github.token }}
          RELEASE_TAG: ${{ steps.version.outputs.release_tag }}
        run: |
          git tag -f "$RELEASE_TAG"
          git push origin "$RELEASE_TAG" --force
 
          RELEASE_TITLE="Release archive testing"
          RELEASE_NOTES="Automated testing release for workflow development."
          if [ "$RELEASE_TAG" = "latest-commit-build" ]; then
            RELEASE_TITLE="Latest commit build"
            RELEASE_NOTES="Automated build from the latest commit on main branch. This release is updated automatically with every push to main."
          fi
 
          # Single-flight: this job is the only one that creates the release,
          # so the build matrix never races on `gh release create`.
          if ! gh release view "$RELEASE_TAG" >/dev/null 2>&1; then
            gh release create "$RELEASE_TAG" \
              --title "$RELEASE_TITLE" \
              --notes "$RELEASE_NOTES" \
              --prerelease
          fi
 
  pgrok:
    timeout-minutes: 30
    name: pgrok ${{ matrix.goos }}/${{ matrix.goarch }}
    needs: prepare-release
    if: ${{ github.repository == 'pgrok/pgrok' }}
    runs-on: ${{ matrix.runner }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - {goos: linux, goarch: amd64, runner: ubuntu-latest}
          - {goos: linux, goarch: arm64, runner: ubuntu-latest}
          - {goos: linux, goarch: "386", runner: ubuntu-latest}
          - {goos: darwin, goarch: amd64, runner: macos-latest}
          - {goos: darwin, goarch: arm64, runner: macos-latest}
          - {goos: windows, goarch: amd64, runner: ubuntu-latest}
          - {goos: windows, goarch: arm64, runner: ubuntu-latest}
          - {goos: windows, goarch: "386", runner: ubuntu-latest}
    steps:
      - name: Check out code
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
      - name: Set up Go
        uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
        with:
          go-version: 1.26.x
      - name: Build binary
        env:
          GOOS: ${{ matrix.goos }}
          GOARCH: ${{ matrix.goarch }}
          CGO_ENABLED: "0"
        run: |
          mkdir -p dist
          BINARY_NAME="pgrok"
          if [ "${{ matrix.goos }}" = "windows" ]; then
            BINARY_NAME="pgrok.exe"
          fi
 
          go build -v \
            -ldflags "-X main.version=${{ needs.prepare-release.outputs.version }}" \
            -trimpath -o "dist/$BINARY_NAME" ./pgrok/cli
      - name: Import Apple signing certificate
        if: ${{ matrix.goos == 'darwin' }}
        env:
          APPLE_DEVELOPER_ID_CERTIFICATE_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_BASE64 }}
          APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }}
          APPLE_KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
        run: |
          if [ -z "$APPLE_DEVELOPER_ID_CERTIFICATE_BASE64" ] || [ -z "$APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD" ] || [ -z "$APPLE_KEYCHAIN_PASSWORD" ]; then
            echo "Missing required Apple signing secrets." >&2
            exit 1
          fi
 
          CERTIFICATE_PATH="$RUNNER_TEMP/developer_id_application.p12"
          KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
 
          printf '%s' "$APPLE_DEVELOPER_ID_CERTIFICATE_BASE64" | base64 -d > "$CERTIFICATE_PATH"
          security create-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
          security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
          security unlock-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
          security import "$CERTIFICATE_PATH" -P "$APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
          security list-keychains -d user -s "$KEYCHAIN_PATH"
          security default-keychain -s "$KEYCHAIN_PATH"
          security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
      - name: Sign macOS binary
        if: ${{ matrix.goos == 'darwin' }}
        env:
          APPLE_DEVELOPER_IDENTITY: ${{ secrets.APPLE_DEVELOPER_IDENTITY }}
        run: |
          if [ -z "$APPLE_DEVELOPER_IDENTITY" ]; then
            echo "Missing required Apple signing identity secret." >&2
            exit 1
          fi
 
          security find-identity -v -p codesigning
          codesign --force --options runtime --timestamp --sign "$APPLE_DEVELOPER_IDENTITY" "dist/pgrok"
          codesign --verify --verbose=2 "dist/pgrok"
      - name: Create archives
        working-directory: dist
        run: |
          VERSION="${{ needs.prepare-release.outputs.version }}"
          ARCHIVE_BASE="pgrok_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}"
 
          BINARY_NAME="pgrok"
          if [ "${{ matrix.goos }}" = "windows" ]; then
            BINARY_NAME="pgrok.exe"
          fi
 
          zip "${ARCHIVE_BASE}.zip" "$BINARY_NAME"
          if [ "${{ matrix.goos }}" = "linux" ]; then
            tar -czvf "${ARCHIVE_BASE}.tar.gz" "$BINARY_NAME"
          fi
      - name: Notarize macOS archive
        if: ${{ matrix.goos == 'darwin' }}
        env:
          APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
          APPLE_NOTARY_KEY_BASE64: ${{ secrets.APPLE_NOTARY_KEY_BASE64 }}
          APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
        run: |
          if [ -z "$APPLE_NOTARY_ISSUER_ID" ] || [ -z "$APPLE_NOTARY_KEY_BASE64" ] || [ -z "$APPLE_NOTARY_KEY_ID" ]; then
            echo "Missing required Apple notarization secrets." >&2
            exit 1
          fi
 
          VERSION="${{ needs.prepare-release.outputs.version }}"
          ARCHIVE_PATH="dist/pgrok_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}.zip"
          NOTARY_KEY_PATH="$RUNNER_TEMP/AuthKey_${APPLE_NOTARY_KEY_ID}.p8"
 
          printf '%s' "$APPLE_NOTARY_KEY_BASE64" | base64 -d > "$NOTARY_KEY_PATH"
          xcrun notarytool submit "$ARCHIVE_PATH" \
            --key "$NOTARY_KEY_PATH" \
            --key-id "$APPLE_NOTARY_KEY_ID" \
            --issuer "$APPLE_NOTARY_ISSUER_ID" \
            --wait
      - name: Upload to release
        env:
          GH_TOKEN: ${{ github.token }}
          RELEASE_TAG: ${{ needs.prepare-release.outputs.release_tag }}
        run: |
          PATTERN="pgrok_.*_${{ matrix.goos }}_${{ matrix.goarch }}\."
          gh release view "$RELEASE_TAG" --json assets --jq ".assets[].name" | grep -E "$PATTERN" | while read -r asset; do
            gh release delete-asset "$RELEASE_TAG" "$asset" --yes || true
          done
 
          gh release upload "$RELEASE_TAG" dist/pgrok_*.zip --clobber
          if [ "${{ matrix.goos }}" = "linux" ]; then
            gh release upload "$RELEASE_TAG" dist/pgrok_*.tar.gz --clobber
          fi
 
  pgrokd:
    timeout-minutes: 30
    name: pgrokd ${{ matrix.goos }}/${{ matrix.goarch }}
    needs: prepare-release
    if: ${{ github.repository == 'pgrok/pgrok' }}
    runs-on: ${{ matrix.runner }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - {goos: linux, goarch: amd64, runner: ubuntu-latest}
          - {goos: linux, goarch: arm64, runner: ubuntu-latest}
          - {goos: linux, goarch: "386", runner: ubuntu-latest}
          - {goos: darwin, goarch: amd64, runner: macos-latest}
          - {goos: darwin, goarch: arm64, runner: macos-latest}
          - {goos: windows, goarch: amd64, runner: ubuntu-latest}
          - {goos: windows, goarch: arm64, runner: ubuntu-latest}
          - {goos: windows, goarch: "386", runner: ubuntu-latest}
    steps:
      - name: Check out code
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
      - name: Set up Go
        uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
        with:
          go-version: 1.26.x
      - name: Install pnpm
        uses: pnpm/action-setup@9fd676a19091d4595eefd76e4bd31c97133911f1 # v4.2.0
        with:
          version: 11
          run_install: |
            - cwd: pgrokd/web
      - name: Build web app
        run: pnpm --dir pgrokd/web run build
      - name: Build binary
        env:
          GOOS: ${{ matrix.goos }}
          GOARCH: ${{ matrix.goarch }}
          CGO_ENABLED: "0"
        run: |
          mkdir -p dist
          BINARY_NAME="pgrokd"
          if [ "${{ matrix.goos }}" = "windows" ]; then
            BINARY_NAME="pgrokd.exe"
          fi
 
          go build -v -tags prod \
            -ldflags "-X main.version=${{ needs.prepare-release.outputs.version }}" \
            -trimpath -o "dist/$BINARY_NAME" ./pgrokd/cli
      - name: Import Apple signing certificate
        if: ${{ matrix.goos == 'darwin' }}
        env:
          APPLE_DEVELOPER_ID_CERTIFICATE_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_BASE64 }}
          APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }}
          APPLE_KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
        run: |
          if [ -z "$APPLE_DEVELOPER_ID_CERTIFICATE_BASE64" ] || [ -z "$APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD" ] || [ -z "$APPLE_KEYCHAIN_PASSWORD" ]; then
            echo "Missing required Apple signing secrets." >&2
            exit 1
          fi
 
          CERTIFICATE_PATH="$RUNNER_TEMP/developer_id_application.p12"
          KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
 
          printf '%s' "$APPLE_DEVELOPER_ID_CERTIFICATE_BASE64" | base64 -d > "$CERTIFICATE_PATH"
          security create-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
          security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
          security unlock-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
          security import "$CERTIFICATE_PATH" -P "$APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
          security list-keychains -d user -s "$KEYCHAIN_PATH"
          security default-keychain -s "$KEYCHAIN_PATH"
          security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
      - name: Sign macOS binary
        if: ${{ matrix.goos == 'darwin' }}
        env:
          APPLE_DEVELOPER_IDENTITY: ${{ secrets.APPLE_DEVELOPER_IDENTITY }}
        run: |
          if [ -z "$APPLE_DEVELOPER_IDENTITY" ]; then
            echo "Missing required Apple signing identity secret." >&2
            exit 1
          fi
 
          security find-identity -v -p codesigning
          codesign --force --options runtime --timestamp --sign "$APPLE_DEVELOPER_IDENTITY" "dist/pgrokd"
          codesign --verify --verbose=2 "dist/pgrokd"
      - name: Create archives
        working-directory: dist
        run: |
          VERSION="${{ needs.prepare-release.outputs.version }}"
          ARCHIVE_BASE="pgrokd_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}"
 
          BINARY_NAME="pgrokd"
          if [ "${{ matrix.goos }}" = "windows" ]; then
            BINARY_NAME="pgrokd.exe"
          fi
 
          zip "${ARCHIVE_BASE}.zip" "$BINARY_NAME"
          if [ "${{ matrix.goos }}" = "linux" ]; then
            tar -czvf "${ARCHIVE_BASE}.tar.gz" "$BINARY_NAME"
          fi
      - name: Notarize macOS archive
        if: ${{ matrix.goos == 'darwin' }}
        env:
          APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
          APPLE_NOTARY_KEY_BASE64: ${{ secrets.APPLE_NOTARY_KEY_BASE64 }}
          APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
        run: |
          if [ -z "$APPLE_NOTARY_ISSUER_ID" ] || [ -z "$APPLE_NOTARY_KEY_BASE64" ] || [ -z "$APPLE_NOTARY_KEY_ID" ]; then
            echo "Missing required Apple notarization secrets." >&2
            exit 1
          fi
 
          VERSION="${{ needs.prepare-release.outputs.version }}"
          ARCHIVE_PATH="dist/pgrokd_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}.zip"
          NOTARY_KEY_PATH="$RUNNER_TEMP/AuthKey_${APPLE_NOTARY_KEY_ID}.p8"
 
          printf '%s' "$APPLE_NOTARY_KEY_BASE64" | base64 -d > "$NOTARY_KEY_PATH"
          xcrun notarytool submit "$ARCHIVE_PATH" \
            --key "$NOTARY_KEY_PATH" \
            --key-id "$APPLE_NOTARY_KEY_ID" \
            --issuer "$APPLE_NOTARY_ISSUER_ID" \
            --wait
      - name: Upload to release
        env:
          GH_TOKEN: ${{ github.token }}
          RELEASE_TAG: ${{ needs.prepare-release.outputs.release_tag }}
        run: |
          PATTERN="pgrokd_.*_${{ matrix.goos }}_${{ matrix.goarch }}\."
          gh release view "$RELEASE_TAG" --json assets --jq ".assets[].name" | grep -E "$PATTERN" | while read -r asset; do
            gh release delete-asset "$RELEASE_TAG" "$asset" --yes || true
          done
 
          gh release upload "$RELEASE_TAG" dist/pgrokd_*.zip --clobber
          if [ "${{ matrix.goos }}" = "linux" ]; then
            gh release upload "$RELEASE_TAG" dist/pgrokd_*.tar.gz --clobber
          fi
 
  notify-failure:
    timeout-minutes: 30
    name: Notify on failure
    runs-on: latchkey-small
    needs: [pgrok, pgrokd]
    if: ${{ failure() && github.repository == 'pgrok/pgrok' && github.event_name == 'push' && github.ref == 'refs/heads/main' }}
    steps:
      - name: Send email on failure
        uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
        with:
          smtp_username: ${{ secrets.SMTP_USERNAME }}
          smtp_password: ${{ secrets.SMTP_PASSWORD }}
 

What changed

This workflow runs 4 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow