Skip to content
Latchkey

Build Release workflow (nicholas-fedor/watchtower)

The Build Release workflow from nicholas-fedor/watchtower, 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: nicholas-fedor/watchtower.github/workflows/build.yamlLicense Apache-2.0View source

What it does

This is the Build Release workflow from the nicholas-fedor/watchtower repository, a real project running GitHub Actions. It is shown here with attribution under its Apache-2.0 license.

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

The workflow

workflow (.yml)
---
name: Build Release

on:
  workflow_call:
    inputs:
      ENVIRONMENT:
        description: "GitHub environment for secrets access"
        required: true
        type: string
      BUILD_TYPE:
        description: "Type of release build (stable or nightly)"
        required: true
        type: string
      DRY_RUN:
        description: "Run in test mode without publishing artifacts"
        required: false
        default: false
        type: boolean
    secrets:
      DOCKERHUB_USERNAME:
        required: true
      DOCKERHUB_TOKEN:
        required: true

permissions:
  contents: read # Declare default permissions as read only.

jobs:
  build:
    name: Build
    runs-on: ubuntu-latest
    environment: ${{ inputs.ENVIRONMENT }}
    env:
      DOCKER_CLI_EXPERIMENTAL: "enabled"
      CGO_ENABLED: 0
      TAG: ${{ github.ref_name }}
    permissions:
      contents: write # For code checkout and publishing releases
      packages: write # For pushing images to registries
      attestations: write # For generating provenance and SBOMs
      id-token: write # For OIDC auth to Docker Hub and GHCR

    steps:
      - name: Harden the Runner (Step Security)
        uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
        with:
          egress-policy: audit

      - name: Validate BUILD_TYPE input
        env:
          BUILD_TYPE: ${{ inputs.BUILD_TYPE }}
        run: |
          if [[ "${BUILD_TYPE}" != "stable" && "${BUILD_TYPE}" != "nightly" ]]; then
            echo "Error: BUILD_TYPE must be 'stable' or 'nightly', got '${BUILD_TYPE}'"
            exit 1
          fi

      - name: Checkout Repo
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          fetch-depth: 0
          persist-credentials: false

      - name: Setup Go
        uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
        with:
          cache: false # Disable to prevent cache poisoning
          go-version-file: go.mod

      - name: Setup QEMU
        uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
        with:
          image: tonistiigi/binfmt@sha256:d3b963f787999e6c0219a48dba02978769286ff61a5f4d26245cb6a6e5567ea3 # latest
          platforms: linux/amd64,linux/386,linux/arm/v6,linux/arm64/v8,linux/riscv64

      - name: Setup Docker Buildx
        uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
        with:
          platforms: linux/amd64,linux/386,linux/arm/v6,linux/arm64/v8,linux/riscv64

      - name: Install Syft for SBOM generation
        uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
        if: ${{ inputs.BUILD_TYPE == 'stable' }}

      - name: Install Cosign
        uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2

      - name: Login to Docker Hub
        uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
        if: ${{ !inputs.DRY_RUN }}
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Login to GHCR
        uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
        if: ${{ !inputs.DRY_RUN }}
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Run GoReleaser
        uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          DRY_RUN: ${{ inputs.DRY_RUN }}
        with:
          distribution: goreleaser
          version: "~> v2"
          args: >-
            release
            --config build/goreleaser/${{ inputs.BUILD_TYPE }}.yaml
            ${{ inputs.BUILD_TYPE == 'nightly' && '--skip=validate,archive' || '' }}
            --clean
            ${{ inputs.DRY_RUN && '--skip=publish' || '' }}

      - name: Upload Binary SBOMs
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        if: ${{ !inputs.DRY_RUN && inputs.BUILD_TYPE == 'stable' }}
        with:
          name: ${{ inputs.BUILD_TYPE }}-binary-sboms
          path: dist/*.sbom
          if-no-files-found: ignore

      - name: Generate Artifact Attestation
        uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
        if: ${{ !inputs.DRY_RUN && inputs.BUILD_TYPE == 'stable' }}
        with:
          subject-checksums: ./dist/checksums.txt

      - name: Cleanup dist Directory
        if: always()
        run: rm -rf dist

The same workflow, on Latchkey

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

---
name: Build Release
 
on:
  workflow_call:
    inputs:
      ENVIRONMENT:
        description: "GitHub environment for secrets access"
        required: true
        type: string
      BUILD_TYPE:
        description: "Type of release build (stable or nightly)"
        required: true
        type: string
      DRY_RUN:
        description: "Run in test mode without publishing artifacts"
        required: false
        default: false
        type: boolean
    secrets:
      DOCKERHUB_USERNAME:
        required: true
      DOCKERHUB_TOKEN:
        required: true
 
permissions:
  contents: read # Declare default permissions as read only.
 
jobs:
  build:
    timeout-minutes: 30
    name: Build
    runs-on: latchkey-small
    environment: ${{ inputs.ENVIRONMENT }}
    env:
      DOCKER_CLI_EXPERIMENTAL: "enabled"
      CGO_ENABLED: 0
      TAG: ${{ github.ref_name }}
    permissions:
      contents: write # For code checkout and publishing releases
      packages: write # For pushing images to registries
      attestations: write # For generating provenance and SBOMs
      id-token: write # For OIDC auth to Docker Hub and GHCR
 
    steps:
      - name: Harden the Runner (Step Security)
        uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
        with:
          egress-policy: audit
 
      - name: Validate BUILD_TYPE input
        env:
          BUILD_TYPE: ${{ inputs.BUILD_TYPE }}
        run: |
          if [[ "${BUILD_TYPE}" != "stable" && "${BUILD_TYPE}" != "nightly" ]]; then
            echo "Error: BUILD_TYPE must be 'stable' or 'nightly', got '${BUILD_TYPE}'"
            exit 1
          fi
 
      - name: Checkout Repo
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          fetch-depth: 0
          persist-credentials: false
 
      - name: Setup Go
        uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
        with:
          cache: false # Disable to prevent cache poisoning
          go-version-file: go.mod
 
      - name: Setup QEMU
        uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
        with:
          image: tonistiigi/binfmt@sha256:d3b963f787999e6c0219a48dba02978769286ff61a5f4d26245cb6a6e5567ea3 # latest
          platforms: linux/amd64,linux/386,linux/arm/v6,linux/arm64/v8,linux/riscv64
 
      - name: Setup Docker Buildx
        uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
        with:
          platforms: linux/amd64,linux/386,linux/arm/v6,linux/arm64/v8,linux/riscv64
 
      - name: Install Syft for SBOM generation
        uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
        if: ${{ inputs.BUILD_TYPE == 'stable' }}
 
      - name: Install Cosign
        uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
 
      - name: Login to Docker Hub
        uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
        if: ${{ !inputs.DRY_RUN }}
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
 
      - name: Login to GHCR
        uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
        if: ${{ !inputs.DRY_RUN }}
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
 
      - name: Run GoReleaser
        uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          DRY_RUN: ${{ inputs.DRY_RUN }}
        with:
          distribution: goreleaser
          version: "~> v2"
          args: >-
            release
            --config build/goreleaser/${{ inputs.BUILD_TYPE }}.yaml
            ${{ inputs.BUILD_TYPE == 'nightly' && '--skip=validate,archive' || '' }}
            --clean
            ${{ inputs.DRY_RUN && '--skip=publish' || '' }}
 
      - name: Upload Binary SBOMs
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        if: ${{ !inputs.DRY_RUN && inputs.BUILD_TYPE == 'stable' }}
        with:
          name: ${{ inputs.BUILD_TYPE }}-binary-sboms
          path: dist/*.sbom
          if-no-files-found: ignore
 
      - name: Generate Artifact Attestation
        uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
        if: ${{ !inputs.DRY_RUN && inputs.BUILD_TYPE == 'stable' }}
        with:
          subject-checksums: ./dist/checksums.txt
 
      - name: Cleanup dist Directory
        if: always()
        run: rm -rf dist
 

What changed

What Latchkey heals here

This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:

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

Actions used in this workflow