Skip to content
Latchkey

CI workflow (akuity/kargo)

The CI workflow from akuity/kargo, explained and optimized by Latchkey.

C

CI health: C - fair

Point runs-on at Latchkey and get run de-duplication, 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: akuity/kargo.github/workflows/ci.yamlLicense Apache-2.0View source

What it does

This is the CI workflow from the akuity/kargo 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: CI

on:
  pull_request:
    branches-ignore:
    - newdocs
  merge_group:
  push:
    branches:
    - main
    - release-*

permissions:
  contents: read

jobs:
  test-unit:
    runs-on: ubuntu-latest
    container:
      image: &golangImage golang:1.26.4-trixie
    steps:
    # Install Git from "trixie" repository to get a more recent version than
    # the one available in "stable". This can be removed once the version in
    # "stable" is updated to >= 2.42.0 (which supports `--orphan` for `git
    # worktree add`).
    - name: Harden the runner (Audit all outbound calls)
      uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
      with:
        egress-policy: audit

    - name: Update Git to >= 2.42
      run: |
        echo "deb http://deb.debian.org/debian trixie main" > /etc/apt/sources.list.d/trixie.list
        apt update && apt install -y -t trixie git
    - name: Checkout code
      uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
    - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
      with:
        path: /go/pkg/mod
        key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
        restore-keys: |
          ${{ runner.os }}-go-
    - name: Run unit tests
      run: make test-unit
    - name: Remove generated code from report
      run: |
        for report in $(find . -maxdepth 4 -type f -name 'coverage.txt'); do
          tmp_file=$(dirname $report)/coverage.tmp
          grep -v .pb.go $report | grep -v zz_ | grep -v .connect.go | grep -v 'pkg/client/generated/' > $tmp_file
          mv $tmp_file $report
        done
    - name: Upload coverage reports
      uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
      with:
        token: ${{ secrets.CODECOV_TOKEN }}
  
  lint-and-typecheck-ui:
    runs-on: ubuntu-latest
    steps:
    - name: Harden the runner (Audit all outbound calls)
      uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
      with:
        egress-policy: audit

    - name: Checkout code
      uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
    - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
      with:
        path: ~/.pnpm-store
        key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
        restore-keys: |
          ${{ runner.os }}-pnpm-
    - name: Install pnpm
      uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
      with:
        package_json_file: ui/package.json
    - name: Install nodejs
      uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
      with:
        node-version: "24.15.0"
        cache: "pnpm"
        cache-dependency-path: "**/pnpm-lock.yaml"
    - name: Run typecheck
      run: make typecheck-ui
    - name: Run linter
      run: make lint-ui
    - name: Run unit tests
      run: make test-unit-ui

  lint-go:
    permissions:
      contents: read # Permissions to read the repository, required because we override the default permissions
      checks: write # Used to create checks (linting comments) on PRs
    runs-on: ubuntu-latest
    container:
      image: *golangImage
    steps:
    - name: Harden the runner (Audit all outbound calls)
      uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
      with:
        egress-policy: audit

    - name: Checkout code
      uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
    - name: Cache golangci-lint
      id: cache-golangci-lint
      uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
      with:
        path: |
          hack/bin/golangci-lint
          hack/bin/golangci-lint-*
        key: ${{ runner.os }}-golangci-lint-${{ hashFiles('hack/tools.mk') }}
    - name: Install golangci-lint
      if: steps.cache-golangci-lint.outputs.cache-hit != 'true'
      run: make install-golangci-lint
    - name: Configure Git
      run: git config --global --add safe.directory '*'
    - name: Run linter
      run: make lint-go

  lint-charts:
    runs-on: ubuntu-latest
    container:
      image: *golangImage
    steps:
    - name: Harden the runner (Audit all outbound calls)
      uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
      with:
        egress-policy: audit

    - name: Checkout code
      uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
    - name: Cache helm
      id: cache-helm
      uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
      with:
        path: |
          hack/bin/helm
          hack/bin/helm-*
        key:
          ${{ runner.os }}-helm-${{ hashFiles('go.mod', 'hack/tools.mk') }}
    - name: Install helm
      if: steps.cache-helm.outputs.cache-hit != 'true'
      run: make install-helm
    - name: Run linter
      run: make lint-charts

  test-chart:
    runs-on: ubuntu-latest
    container:
      image: *golangImage
    steps:
    - name: Harden the runner (Audit all outbound calls)
      uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
      with:
        egress-policy: audit

    - name: Checkout code
      uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
    - name: Cache helm
      id: cache-helm
      uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
      with:
        path: |
          hack/bin/helm
          hack/bin/helm-*
        key:
          ${{ runner.os }}-helm-${{ hashFiles('go.mod', 'hack/tools.mk') }}
    - name: Install helm
      if: steps.cache-helm.outputs.cache-hit != 'true'
      run: make install-helm
    - name: Run chart unit tests
      run: |
        set -o pipefail
        make test-chart 2>&1 | tee /tmp/chart-tests.log
    - name: Publish chart test summary
      if: always()
      run: |
        [ -f /tmp/chart-tests.log ] || exit 0
        {
          echo '## Chart unit tests'
          echo '```'
          sed -n '/^### Chart/,$p' /tmp/chart-tests.log
          echo '```'
        } >> "$GITHUB_STEP_SUMMARY"

  lint-proto:
    permissions:
      contents: read # Permissions to read the repository, required because we override the default permissions
      checks: write # Used to create checks (linting comments) on PRs
    runs-on: ubuntu-latest
    container:
      image: *golangImage
    steps:
    - name: Harden the runner (Audit all outbound calls)
      uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
      with:
        egress-policy: audit

    - name: Checkout code
      uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
    - name: Cache Go modules
      uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
      with:
        path: /go/pkg/mod
        key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
    - name: Run linter
      env:
        BUF_LINT_ERROR_FORMAT: github-actions
      run: make lint-proto

  check-codegen:
    runs-on: ubuntu-latest
    container:
      image: *golangImage
    steps:
    - name: Harden the runner (Audit all outbound calls)
      uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
      with:
        egress-policy: audit

    - name: Checkout code
      uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
    - name: Install tools
      run: apt update && apt install unzip
    - name: Install pnpm
      uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
      with:
        package_json_file: ui/package.json
    - name: Install nodejs
      uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
      with:
        node-version: "24.15.0"
        cache: "pnpm"
        cache-dependency-path: "**/pnpm-lock.yaml"
    - name: Install nodejs dependencies
      run: pnpm install --dev
      working-directory: ./ui
    - name: Cache Go modules
      uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
      with:
        path: /go/pkg/mod
        key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
    - name: Cache tools
      uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
      with:
        path: |
          hack/bin
          hack/include
        key: ${{ runner.os }}-tools-${{ hashFiles('hack/tools.mk') }}
    - name: Git stuff
      # As of go 1.20, this seems to be necessary for invoking git commands
      # within the container
      run: git config --global --add safe.directory /__w/kargo/kargo
    - name: Run Codegen
      run: make codegen
    - name: Check nothing has changed
      run: git diff --exit-code -- .

  build-image:
    needs: [test-unit, lint-go, lint-charts, lint-proto, lint-and-typecheck-ui, check-codegen]
    runs-on: ubuntu-latest
    services:
      registry:
        image: registry:3.0.0
        ports:
        - 5000:5000
    steps:
    - name: Harden the runner (Audit all outbound calls)
      uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
      with:
        egress-policy: audit

    - name: Set up QEMU
      uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
      with:
        driver-opts: network=host
    - name: Checkout code
      uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
    - name: Build base image
      run: |
        BASE_IMAGE=localhost:5000/kargo-base make build-base-image
        docker push localhost:5000/kargo-base:latest-arm64
        docker push localhost:5000/kargo-base:latest-amd64
    - name: Build final image
      uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
      with:
        platforms: linux/amd64,linux/arm64
        build-args: |
          BASE_IMAGE=localhost:5000/kargo-base
        # Push to the job-local registry service (nothing leaves the runner)
        # so the built image can be scanned for CVEs below.
        tags: localhost:5000/kargo:scan
        push: true
        cache-from: type=gha
        cache-to: type=gha,mode=max
    - name: Install Grype
      id: grype
      uses: anchore/scan-action/download-grype@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2 # v7.4.0
      with:
        cache-db: true
    # cve-scan.sh re-stamps the VEX product @id to the scanned digest, which it
    # resolves from the job-local registry tag via crane.
    - name: Install Crane
      uses: imjasonh/setup-crane@feee3b6bb0d4c68370f256a4502498c9227e5c6b # v0.7
    - name: Scan image for CVEs
      env:
        GRYPE_CMD: ${{ steps.grype.outputs.cmd }}
        GRYPE_REGISTRY_INSECURE_USE_HTTP: "true" # job-local registry service
        CRANE_INSECURE: "true" # job-local registry is plain HTTP
        VEX_IMAGE: kargo-oss # monitored image name for the VEX lookup
        FAIL_ON: "" # report-only; set to "high" to enforce
      run: ./hack/cve-scan.sh "registry:localhost:5000/kargo:scan"

  build-cli:
    needs: [test-unit, lint-go, lint-charts, lint-proto, lint-and-typecheck-ui, check-codegen]
    runs-on: ubuntu-latest
    container:
      image: *golangImage
    steps:
    - name: Harden the runner (Audit all outbound calls)
      uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
      with:
        egress-policy: audit

    - name: Checkout code
      uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
    - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
      with:
        path: /go/pkg/mod
        key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
        restore-keys: |
          ${{ runner.os }}-go-
    - name: Build CLI
      env:
        GOFLAGS: -buildvcs=false
      run: make build-cli

The same workflow, on Latchkey

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

name: CI
 
on:
  pull_request:
    branches-ignore:
    - newdocs
  merge_group:
  push:
    branches:
    - main
    - release-*
 
permissions:
  contents: read
 
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  test-unit:
    timeout-minutes: 30
    runs-on: latchkey-small
    container:
      image: &golangImage golang:1.26.4-trixie
    steps:
    # Install Git from "trixie" repository to get a more recent version than
    # the one available in "stable". This can be removed once the version in
    # "stable" is updated to >= 2.42.0 (which supports `--orphan` for `git
    # worktree add`).
    - name: Harden the runner (Audit all outbound calls)
      uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
      with:
        egress-policy: audit
 
    - name: Update Git to >= 2.42
      run: |
        echo "deb http://deb.debian.org/debian trixie main" > /etc/apt/sources.list.d/trixie.list
        apt update && apt install -y -t trixie git
    - name: Checkout code
      uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
    - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
      with:
        path: /go/pkg/mod
        key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
        restore-keys: |
          ${{ runner.os }}-go-
    - name: Run unit tests
      run: make test-unit
    - name: Remove generated code from report
      run: |
        for report in $(find . -maxdepth 4 -type f -name 'coverage.txt'); do
          tmp_file=$(dirname $report)/coverage.tmp
          grep -v .pb.go $report | grep -v zz_ | grep -v .connect.go | grep -v 'pkg/client/generated/' > $tmp_file
          mv $tmp_file $report
        done
    - name: Upload coverage reports
      uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
      with:
        token: ${{ secrets.CODECOV_TOKEN }}
  
  lint-and-typecheck-ui:
    timeout-minutes: 30
    runs-on: latchkey-small
    steps:
    - name: Harden the runner (Audit all outbound calls)
      uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
      with:
        egress-policy: audit
 
    - name: Checkout code
      uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
    - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
      with:
        path: ~/.pnpm-store
        key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
        restore-keys: |
          ${{ runner.os }}-pnpm-
    - name: Install pnpm
      uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
      with:
        package_json_file: ui/package.json
    - name: Install nodejs
      uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
      with:
        node-version: "24.15.0"
        cache: "pnpm"
        cache-dependency-path: "**/pnpm-lock.yaml"
    - name: Run typecheck
      run: make typecheck-ui
    - name: Run linter
      run: make lint-ui
    - name: Run unit tests
      run: make test-unit-ui
 
  lint-go:
    timeout-minutes: 30
    permissions:
      contents: read # Permissions to read the repository, required because we override the default permissions
      checks: write # Used to create checks (linting comments) on PRs
    runs-on: latchkey-small
    container:
      image: *golangImage
    steps:
    - name: Harden the runner (Audit all outbound calls)
      uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
      with:
        egress-policy: audit
 
    - name: Checkout code
      uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
    - name: Cache golangci-lint
      id: cache-golangci-lint
      uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
      with:
        path: |
          hack/bin/golangci-lint
          hack/bin/golangci-lint-*
        key: ${{ runner.os }}-golangci-lint-${{ hashFiles('hack/tools.mk') }}
    - name: Install golangci-lint
      if: steps.cache-golangci-lint.outputs.cache-hit != 'true'
      run: make install-golangci-lint
    - name: Configure Git
      run: git config --global --add safe.directory '*'
    - name: Run linter
      run: make lint-go
 
  lint-charts:
    timeout-minutes: 30
    runs-on: latchkey-small
    container:
      image: *golangImage
    steps:
    - name: Harden the runner (Audit all outbound calls)
      uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
      with:
        egress-policy: audit
 
    - name: Checkout code
      uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
    - name: Cache helm
      id: cache-helm
      uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
      with:
        path: |
          hack/bin/helm
          hack/bin/helm-*
        key:
          ${{ runner.os }}-helm-${{ hashFiles('go.mod', 'hack/tools.mk') }}
    - name: Install helm
      if: steps.cache-helm.outputs.cache-hit != 'true'
      run: make install-helm
    - name: Run linter
      run: make lint-charts
 
  test-chart:
    timeout-minutes: 30
    runs-on: latchkey-small
    container:
      image: *golangImage
    steps:
    - name: Harden the runner (Audit all outbound calls)
      uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
      with:
        egress-policy: audit
 
    - name: Checkout code
      uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
    - name: Cache helm
      id: cache-helm
      uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
      with:
        path: |
          hack/bin/helm
          hack/bin/helm-*
        key:
          ${{ runner.os }}-helm-${{ hashFiles('go.mod', 'hack/tools.mk') }}
    - name: Install helm
      if: steps.cache-helm.outputs.cache-hit != 'true'
      run: make install-helm
    - name: Run chart unit tests
      run: |
        set -o pipefail
        make test-chart 2>&1 | tee /tmp/chart-tests.log
    - name: Publish chart test summary
      if: always()
      run: |
        [ -f /tmp/chart-tests.log ] || exit 0
        {
          echo '## Chart unit tests'
          echo '```'
          sed -n '/^### Chart/,$p' /tmp/chart-tests.log
          echo '```'
        } >> "$GITHUB_STEP_SUMMARY"
 
  lint-proto:
    timeout-minutes: 30
    permissions:
      contents: read # Permissions to read the repository, required because we override the default permissions
      checks: write # Used to create checks (linting comments) on PRs
    runs-on: latchkey-small
    container:
      image: *golangImage
    steps:
    - name: Harden the runner (Audit all outbound calls)
      uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
      with:
        egress-policy: audit
 
    - name: Checkout code
      uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
    - name: Cache Go modules
      uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
      with:
        path: /go/pkg/mod
        key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
    - name: Run linter
      env:
        BUF_LINT_ERROR_FORMAT: github-actions
      run: make lint-proto
 
  check-codegen:
    timeout-minutes: 30
    runs-on: latchkey-small
    container:
      image: *golangImage
    steps:
    - name: Harden the runner (Audit all outbound calls)
      uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
      with:
        egress-policy: audit
 
    - name: Checkout code
      uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
    - name: Install tools
      run: apt update && apt install unzip
    - name: Install pnpm
      uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
      with:
        package_json_file: ui/package.json
    - name: Install nodejs
      uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
      with:
        node-version: "24.15.0"
        cache: "pnpm"
        cache-dependency-path: "**/pnpm-lock.yaml"
    - name: Install nodejs dependencies
      run: pnpm install --dev
      working-directory: ./ui
    - name: Cache Go modules
      uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
      with:
        path: /go/pkg/mod
        key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
    - name: Cache tools
      uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
      with:
        path: |
          hack/bin
          hack/include
        key: ${{ runner.os }}-tools-${{ hashFiles('hack/tools.mk') }}
    - name: Git stuff
      # As of go 1.20, this seems to be necessary for invoking git commands
      # within the container
      run: git config --global --add safe.directory /__w/kargo/kargo
    - name: Run Codegen
      run: make codegen
    - name: Check nothing has changed
      run: git diff --exit-code -- .
 
  build-image:
    timeout-minutes: 30
    needs: [test-unit, lint-go, lint-charts, lint-proto, lint-and-typecheck-ui, check-codegen]
    runs-on: latchkey-small
    services:
      registry:
        image: registry:3.0.0
        ports:
        - 5000:5000
    steps:
    - name: Harden the runner (Audit all outbound calls)
      uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
      with:
        egress-policy: audit
 
    - name: Set up QEMU
      uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
      with:
        driver-opts: network=host
    - name: Checkout code
      uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
    - name: Build base image
      run: |
        BASE_IMAGE=localhost:5000/kargo-base make build-base-image
        docker push localhost:5000/kargo-base:latest-arm64
        docker push localhost:5000/kargo-base:latest-amd64
    - name: Build final image
      uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
      with:
        platforms: linux/amd64,linux/arm64
        build-args: |
          BASE_IMAGE=localhost:5000/kargo-base
        # Push to the job-local registry service (nothing leaves the runner)
        # so the built image can be scanned for CVEs below.
        tags: localhost:5000/kargo:scan
        push: true
        cache-from: type=gha
        cache-to: type=gha,mode=max
    - name: Install Grype
      id: grype
      uses: anchore/scan-action/download-grype@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2 # v7.4.0
      with:
        cache-db: true
    # cve-scan.sh re-stamps the VEX product @id to the scanned digest, which it
    # resolves from the job-local registry tag via crane.
    - name: Install Crane
      uses: imjasonh/setup-crane@feee3b6bb0d4c68370f256a4502498c9227e5c6b # v0.7
    - name: Scan image for CVEs
      env:
        GRYPE_CMD: ${{ steps.grype.outputs.cmd }}
        GRYPE_REGISTRY_INSECURE_USE_HTTP: "true" # job-local registry service
        CRANE_INSECURE: "true" # job-local registry is plain HTTP
        VEX_IMAGE: kargo-oss # monitored image name for the VEX lookup
        FAIL_ON: "" # report-only; set to "high" to enforce
      run: ./hack/cve-scan.sh "registry:localhost:5000/kargo:scan"
 
  build-cli:
    timeout-minutes: 30
    needs: [test-unit, lint-go, lint-charts, lint-proto, lint-and-typecheck-ui, check-codegen]
    runs-on: latchkey-small
    container:
      image: *golangImage
    steps:
    - name: Harden the runner (Audit all outbound calls)
      uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
      with:
        egress-policy: audit
 
    - name: Checkout code
      uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
    - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
      with:
        path: /go/pkg/mod
        key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
        restore-keys: |
          ${{ runner.os }}-go-
    - name: Build CLI
      env:
        GOFLAGS: -buildvcs=false
      run: make build-cli
 

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