CI workflow (9seconds/mtg)
The CI workflow from 9seconds/mtg, explained and optimized by Latchkey.
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.
What it does
This is the CI workflow from the 9seconds/mtg 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
---
name: CI
permissions:
actions: read
checks: read
contents: read
deployments: read
issues: read
discussions: read
pull-requests: read
repository-projects: read
security-events: read
statuses: read
on:
push:
tags:
- v*
branches:
- master
- stable
- v1
release:
types:
- published
- released
pull_request:
types:
- opened
- edited
- reopened
- synchronize
- ready_for_review
jobs:
test:
name: Test
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: recursive
- uses: jdx/mise-action@v3
name: Install mise
- name: Cache Go modules and build
uses: actions/cache@v5
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Run tests
run: mise tasks run covtest
- name: Collect coverage
uses: codecov/codecov-action@v5
with:
files: ./coverage.txt
fuzz:
name: Fuzzing
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: recursive
- uses: jdx/mise-action@v3
name: Install mise
- name: Cache Go modules and build
uses: actions/cache@v5
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Run fuzzing
run: mise tasks run 'test:fuzz:*'
lint:
name: Lint
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: recursive
- uses: jdx/mise-action@v3
name: Install mise
- name: Cache Go modules and build
uses: actions/cache@v5
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Run linter
run: mise tasks run lint
artifacts:
name: Build release artifacts
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: recursive
- uses: jdx/mise-action@v3
name: Install mise
- name: Cache Go modules
uses: actions/cache@v5
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-gomod-${{ hashFiles('go.sum') }}
restore-keys: |
${{ runner.os }}-gomod-
- name: Cache cross-compilation build
uses: actions/cache@v5
with:
path: ~/.cache/go-build
key: ${{ runner.os }}-goreleaser-${{ hashFiles('go.sum') }}-${{ hashFiles('**/*.go') }}
restore-keys: |
${{ runner.os }}-goreleaser-${{ hashFiles('go.sum') }}-
${{ runner.os }}-goreleaser-
- name: Run release
run: mise tasks run release
docker:
name: Docker
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: recursive
- name: Get Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
nineseconds/mtg
ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest,enable={{is_default_branch}}
type=raw,value=master,enable=${{ github.ref == 'refs/heads/master' }}
type=raw,value=stable,enable=${{ github.ref == 'refs/heads/stable' }}
- name: Setup QEMU
uses: docker/setup-qemu-action@v3
- name: Setup BuildX
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Login to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
pull: true
context: .
platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7,linux/arm/v6
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
The same workflow, on Latchkey
Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.
--- name: CI permissions: actions: read checks: read contents: read deployments: read issues: read discussions: read pull-requests: read repository-projects: read security-events: read statuses: read on: push: tags: - v* branches: - master - stable - v1 release: types: - published - released pull_request: types: - opened - edited - reopened - synchronize - ready_for_review concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: name: Test runs-on: latchkey-small timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@v6 with: submodules: recursive - uses: jdx/mise-action@v3 name: Install mise - name: Cache Go modules and build uses: actions/cache@v5 with: path: | ~/go/pkg/mod ~/.cache/go-build key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }} restore-keys: | ${{ runner.os }}-go- - name: Run tests run: mise tasks run covtest - name: Collect coverage uses: codecov/codecov-action@v5 with: files: ./coverage.txt fuzz: name: Fuzzing runs-on: latchkey-small timeout-minutes: 20 steps: - name: Checkout uses: actions/checkout@v6 with: submodules: recursive - uses: jdx/mise-action@v3 name: Install mise - name: Cache Go modules and build uses: actions/cache@v5 with: path: | ~/go/pkg/mod ~/.cache/go-build key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }} restore-keys: | ${{ runner.os }}-go- - name: Run fuzzing run: mise tasks run 'test:fuzz:*' lint: name: Lint runs-on: latchkey-small timeout-minutes: 5 steps: - name: Checkout uses: actions/checkout@v6 with: submodules: recursive - uses: jdx/mise-action@v3 name: Install mise - name: Cache Go modules and build uses: actions/cache@v5 with: path: | ~/go/pkg/mod ~/.cache/go-build key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }} restore-keys: | ${{ runner.os }}-go- - name: Run linter run: mise tasks run lint artifacts: name: Build release artifacts runs-on: latchkey-small timeout-minutes: 20 steps: - name: Checkout uses: actions/checkout@v6 with: submodules: recursive - uses: jdx/mise-action@v3 name: Install mise - name: Cache Go modules uses: actions/cache@v5 with: path: ~/go/pkg/mod key: ${{ runner.os }}-gomod-${{ hashFiles('go.sum') }} restore-keys: | ${{ runner.os }}-gomod- - name: Cache cross-compilation build uses: actions/cache@v5 with: path: ~/.cache/go-build key: ${{ runner.os }}-goreleaser-${{ hashFiles('go.sum') }}-${{ hashFiles('**/*.go') }} restore-keys: | ${{ runner.os }}-goreleaser-${{ hashFiles('go.sum') }}- ${{ runner.os }}-goreleaser- - name: Run release run: mise tasks run release docker: name: Docker runs-on: latchkey-small timeout-minutes: 20 permissions: contents: read packages: write steps: - name: Checkout uses: actions/checkout@v6 with: submodules: recursive - name: Get Docker meta id: meta uses: docker/metadata-action@v5 with: images: | nineseconds/mtg ghcr.io/${{ github.repository }} tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} type=raw,value=latest,enable={{is_default_branch}} type=raw,value=master,enable=${{ github.ref == 'refs/heads/master' }} type=raw,value=stable,enable=${{ github.ref == 'refs/heads/stable' }} - name: Setup QEMU uses: docker/setup-qemu-action@v3 - name: Setup BuildX uses: docker/setup-buildx-action@v3 - name: Login to DockerHub if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} - name: Login to GitHub Container Registry if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push uses: docker/build-push-action@v6 with: pull: true context: . platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7,linux/arm/v6 push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max
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.
7 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:
- Container pulls and builds
This workflow runs 5 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.