Skip to content
Latchkey

Release workflow (dwgx/WindsurfAPI)

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

B

CI health: B - good

Point runs-on at Latchkey and get run de-duplication, SHA-pinned actions, self-healing for flaky steps, and up to 58% lower cost, applied automatically.

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

What it does

This is the Release workflow from the dwgx/WindsurfAPI 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:
  push:
    tags: ['v*']

jobs:
  test:
    name: Test
    runs-on: ubuntu-latest
    timeout-minutes: 10
    permissions:
      contents: read

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '24'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Secret scan
        run: npm run secret-scan

      - name: Run tests
        run: npm run test:release

  docker:
    name: Docker Build & Push
    needs: test
    runs-on: ubuntu-latest
    timeout-minutes: 30
    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Build metadata
        run: |
          echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV"
          echo "COMMIT_DATE=$(git log -1 --pretty=%cI)" >> "$GITHUB_ENV"
          echo "BUILD_BRANCH=${GITHUB_REF_NAME}" >> "$GITHUB_ENV"
          {
            echo "COMMIT_MESSAGE<<EOF"
            git log -1 --pretty=%s
            echo "EOF"
          } >> "$GITHUB_ENV"

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Docker metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          # github.repository (= dwgx/WindsurfAPI) lowercases to "windsurfapi"
          # without the hyphen the package.json npm name uses. Hardcode the
          # kebab form so the image name stays "windsurf-api".
          images: ghcr.io/${{ github.repository_owner }}/windsurf-api
          tags: |
            # Only tag as latest for stable releases (no pre-release suffix like -rc, -alpha, -beta)
            type=raw,value=latest,enable=${{ !contains(github.ref_name, '-') }}
            # v2.0.6 -> 2.0.6
            type=semver,pattern={{version}}
            # v2.0.6 -> 2.0
            type=semver,pattern={{major}}.{{minor}}

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          platforms: linux/amd64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          build-args: |
            BUILD_VERSION=${{ env.VERSION }}
            BUILD_COMMIT=${{ github.sha }}
            BUILD_COMMIT_MESSAGE=${{ env.COMMIT_MESSAGE }}
            BUILD_COMMIT_DATE=${{ env.COMMIT_DATE }}
            BUILD_BRANCH=${{ env.BUILD_BRANCH }}

      - name: Summary
        run: |
          echo "## Docker Image Published" >> "$GITHUB_STEP_SUMMARY"
          echo "" >> "$GITHUB_STEP_SUMMARY"
          echo "**Tags:**" >> "$GITHUB_STEP_SUMMARY"
          echo '${{ steps.meta.outputs.tags }}' | tr ',' '\n' | sed 's/^/- `/' | sed 's/$/`/' >> "$GITHUB_STEP_SUMMARY"
          echo "" >> "$GITHUB_STEP_SUMMARY"
          echo "**Platform:** \`linux/amd64\`" >> "$GITHUB_STEP_SUMMARY"

  release:
    name: GitHub Release
    needs: docker
    runs-on: ubuntu-latest
    timeout-minutes: 10
    permissions:
      contents: write

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Get version from tag
        id: version
        run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"

      - name: Check for release notes
        id: notes
        run: |
          # New layout (docs/releases/) preferred; fall back to repo root
          # so historical tags still find their notes if anyone re-cuts an
          # old release.
          NEW_PATH="docs/releases/RELEASE_NOTES_${{ steps.version.outputs.VERSION }}.md"
          OLD_PATH="RELEASE_NOTES_${{ steps.version.outputs.VERSION }}.md"
          if [[ -f "$NEW_PATH" ]]; then
            echo "file=$NEW_PATH" >> "$GITHUB_OUTPUT"
            echo "found=true" >> "$GITHUB_OUTPUT"
          elif [[ -f "$OLD_PATH" ]]; then
            echo "file=$OLD_PATH" >> "$GITHUB_OUTPUT"
            echo "found=true" >> "$GITHUB_OUTPUT"
          else
            echo "found=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          name: v${{ steps.version.outputs.VERSION }}
          body_path: ${{ steps.notes.outputs.found == 'true' && steps.notes.outputs.file || '' }}
          generate_release_notes: ${{ steps.notes.outputs.found != 'true' }}
          draft: false
          prerelease: ${{ contains(github.ref_name, '-') }}

The same workflow, on Latchkey

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

name: Release
 
on:
  push:
    tags: ['v*']
 
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  test:
    name: Test
    runs-on: latchkey-small
    timeout-minutes: 10
    permissions:
      contents: read
 
    steps:
      - name: Checkout
        uses: actions/checkout@v4
 
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '24'
          cache: 'npm'
 
      - name: Install dependencies
        run: npm ci
 
      - name: Secret scan
        run: npm run secret-scan
 
      - name: Run tests
        run: npm run test:release
 
  docker:
    name: Docker Build & Push
    needs: test
    runs-on: latchkey-small
    timeout-minutes: 30
    permissions:
      contents: read
      packages: write
 
    steps:
      - name: Checkout
        uses: actions/checkout@v4
 
      - name: Build metadata
        run: |
          echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV"
          echo "COMMIT_DATE=$(git log -1 --pretty=%cI)" >> "$GITHUB_ENV"
          echo "BUILD_BRANCH=${GITHUB_REF_NAME}" >> "$GITHUB_ENV"
          {
            echo "COMMIT_MESSAGE<<EOF"
            git log -1 --pretty=%s
            echo "EOF"
          } >> "$GITHUB_ENV"
 
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
 
      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}
 
      - name: Docker metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          # github.repository (= dwgx/WindsurfAPI) lowercases to "windsurfapi"
          # without the hyphen the package.json npm name uses. Hardcode the
          # kebab form so the image name stays "windsurf-api".
          images: ghcr.io/${{ github.repository_owner }}/windsurf-api
          tags: |
            # Only tag as latest for stable releases (no pre-release suffix like -rc, -alpha, -beta)
            type=raw,value=latest,enable=${{ !contains(github.ref_name, '-') }}
            # v2.0.6 -> 2.0.6
            type=semver,pattern={{version}}
            # v2.0.6 -> 2.0
            type=semver,pattern={{major}}.{{minor}}
 
      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          platforms: linux/amd64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          build-args: |
            BUILD_VERSION=${{ env.VERSION }}
            BUILD_COMMIT=${{ github.sha }}
            BUILD_COMMIT_MESSAGE=${{ env.COMMIT_MESSAGE }}
            BUILD_COMMIT_DATE=${{ env.COMMIT_DATE }}
            BUILD_BRANCH=${{ env.BUILD_BRANCH }}
 
      - name: Summary
        run: |
          echo "## Docker Image Published" >> "$GITHUB_STEP_SUMMARY"
          echo "" >> "$GITHUB_STEP_SUMMARY"
          echo "**Tags:**" >> "$GITHUB_STEP_SUMMARY"
          echo '${{ steps.meta.outputs.tags }}' | tr ',' '\n' | sed 's/^/- `/' | sed 's/$/`/' >> "$GITHUB_STEP_SUMMARY"
          echo "" >> "$GITHUB_STEP_SUMMARY"
          echo "**Platform:** \`linux/amd64\`" >> "$GITHUB_STEP_SUMMARY"
 
  release:
    name: GitHub Release
    needs: docker
    runs-on: latchkey-small
    timeout-minutes: 10
    permissions:
      contents: write
 
    steps:
      - name: Checkout
        uses: actions/checkout@v4
 
      - name: Get version from tag
        id: version
        run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
 
      - name: Check for release notes
        id: notes
        run: |
          # New layout (docs/releases/) preferred; fall back to repo root
          # so historical tags still find their notes if anyone re-cuts an
          # old release.
          NEW_PATH="docs/releases/RELEASE_NOTES_${{ steps.version.outputs.VERSION }}.md"
          OLD_PATH="RELEASE_NOTES_${{ steps.version.outputs.VERSION }}.md"
          if [[ -f "$NEW_PATH" ]]; then
            echo "file=$NEW_PATH" >> "$GITHUB_OUTPUT"
            echo "found=true" >> "$GITHUB_OUTPUT"
          elif [[ -f "$OLD_PATH" ]]; then
            echo "file=$OLD_PATH" >> "$GITHUB_OUTPUT"
            echo "found=true" >> "$GITHUB_OUTPUT"
          else
            echo "found=false" >> "$GITHUB_OUTPUT"
          fi
 
      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          name: v${{ steps.version.outputs.VERSION }}
          body_path: ${{ steps.notes.outputs.found == 'true' && steps.notes.outputs.file || '' }}
          generate_release_notes: ${{ steps.notes.outputs.found != 'true' }}
          draft: false
          prerelease: ${{ contains(github.ref_name, '-') }}
 

What changed

5 third-party actions are referenced by a movable tag. Pin them to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.

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 3 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