CI workflow (akuity/kargo)
The CI workflow from akuity/kargo, explained and optimized by Latchkey.
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.
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
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
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Cancel superseded runs when a branch or PR gets a newer push.
- Add a job timeout so a hung step cannot burn hours of runner time.
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:
- Dependency installs
- Container pulls and builds
This workflow runs 9 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.