Skip to content
Latchkey

CI workflow (yuzutech/kroki)

The CI workflow from yuzutech/kroki, explained and optimized by Latchkey.

F

CI health: F - at risk

Point runs-on at Latchkey and get caching, run de-duplication, job timeouts, 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: yuzutech/kroki.github/workflows/main.yamlLicense MITView source

What it does

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

on:
  push:
    paths-ignore:
      - 'docs/**'
    branches:
      - main
  pull_request:
    paths-ignore:
      - 'docs/**'
    branches:
      - '*'
  workflow_call:
    inputs:
      build_multiarch:
        default: false
        required: true
        type: boolean

jobs:
  test-containers:
    runs-on: ubuntu-24.04
    steps:
    - uses: actions/checkout@v6
    
    - name: Free up disk space
      #if: ${{ inputs.build_multiarch }}
      shell: bash
      # workaround: https://github.com/jlumbroso/free-disk-space/issues/9
      # copied from https://github.com/hirnidrin/free-disk-space/blob/4bacba7c412c8ace26b87b5b79977da05137e69d/action.yml
      run: |
        # ======
        # MACROS
        # ======
  
        # macro to print a line of equals
        # (silly but works)
        printSeparationLine() {
          str=${1:=}
          num=${2:-80}
          counter=1
          output=""
          while [ $counter -le $num ]
          do
             output="${output}${str}"
             counter=$((counter+1))
          done
          echo "${output}"
        }
  
        # macro to compute available space
        # REF: https://unix.stackexchange.com/a/42049/60849
        # REF: https://stackoverflow.com/a/450821/408734
        getAvailableSpace() { echo $(df -a $1 | awk 'NR > 1 {avail+=$4} END {print avail}'); }
  
        # macro to make Kb human readable (assume the input is Kb)
        # REF: https://unix.stackexchange.com/a/44087/60849
        formatByteCount() { echo $(numfmt --to=iec-i --suffix=B --padding=7 $1'000'); }
  
        # macro to output saved space
        printSavedSpace() {
          saved=${1}
          title=${2:-}
  
          echo ""
          printSeparationLine '*' 80
          if [ ! -z "${title}" ]; then
            echo "=> ${title}: Saved $(formatByteCount $saved)"
          else
            echo "=> Saved $(formatByteCount $saved)"
          fi
          printSeparationLine '*' 80
          echo ""
        }
  
        # macro to print output of dh with caption
        printDH() {
          caption=${1:-}
  
          printSeparationLine '=' 80
          echo "${caption}"
          echo ""
          echo "$ dh -h /"
          echo ""
          df -h /
          echo "$ dh -a /"
          echo ""
          df -a /
          echo "$ dh -a"
          echo ""
          df -a
          printSeparationLine '=' 80
        }

        # ======
        # SCRIPT
        # ======
  
        # Display initial disk space stats
  
        AVAILABLE_INITIAL=$(getAvailableSpace)
        AVAILABLE_ROOT_INITIAL=$(getAvailableSpace '/')
  
        printDH "BEFORE CLEAN-UP:"
        echo ""
  
        # Remove Android library
        BEFORE=$(getAvailableSpace)

        sudo rm -rf /usr/local/lib/android

        AFTER=$(getAvailableSpace)
        SAVED=$((AFTER-BEFORE))
        printSavedSpace $SAVED "Android library"
  
        BEFORE=$(getAvailableSpace)

        # https://github.community/t/bigger-github-hosted-runners-disk-space/17267/11
        sudo rm -rf /usr/share/dotnet

        AFTER=$(getAvailableSpace)
        SAVED=$((AFTER-BEFORE))
        printSavedSpace $SAVED ".NET runtime"
  
        BEFORE=$(getAvailableSpace)

        sudo rm -rf /opt/ghc

        AFTER=$(getAvailableSpace)
        SAVED=$((AFTER-BEFORE))
        printSavedSpace $SAVED "Haskell runtime"
  
        # Remove large packages
        # REF: https://github.com/apache/flink/blob/master/tools/azure-pipelines/free_disk_space.sh
  
        BEFORE=$(getAvailableSpace)

        sudo apt-get remove -y '^aspnetcore-.*'
        sudo apt-get remove -y '^dotnet-.*'
        sudo apt-get remove -y '^llvm-.*'
        sudo apt-get remove -y 'php.*'
        sudo apt-get remove -y '^mongodb-.*'
        sudo apt-get remove -y '^mysql-.*'
        sudo apt-get remove -y azure-cli google-chrome-stable firefox powershell mono-devel libgl1-mesa-dri
        sudo apt-get autoremove -y
        sudo apt-get clean

        AFTER=$(getAvailableSpace)
        SAVED=$((AFTER-BEFORE))
        printSavedSpace $SAVED "Large misc. packages"
  
        # Remove Docker images
        BEFORE=$(getAvailableSpace)

        sudo docker image prune --all --force

        AFTER=$(getAvailableSpace)
        SAVED=$((AFTER-BEFORE))
        printSavedSpace $SAVED "Docker images"

        # Remove Swap storage
        BEFORE=$(getAvailableSpace)

        sudo swapoff -a
        sudo rm -f /mnt/swapfile
        free -h

        AFTER=$(getAvailableSpace)
        SAVED=$((AFTER-BEFORE))
        printSavedSpace $SAVED "Swap storage"
  
        # Output saved space statistic
  
        AVAILABLE_END=$(getAvailableSpace)
        AVAILABLE_ROOT_END=$(getAvailableSpace '/')
  
        echo ""
        printDH "AFTER CLEAN-UP:"
  
        echo ""
        echo ""
  
        echo "/dev/root:"
        printSavedSpace $((AVAILABLE_ROOT_END - AVAILABLE_ROOT_INITIAL))
        echo "overall:"
        printSavedSpace $((AVAILABLE_END - AVAILABLE_INITIAL))

    - name: Install GraphViz
      run: sudo apt-get update && sudo apt-get install graphviz -y

    - name: Set up JDK 21
      uses: actions/setup-java@v5
      with:
        java-version: 21
        distribution: temurin

    - name: Install Task
      uses: go-task/setup-task@v2
      with:
        version: 3.x
        repo-token: ${{ secrets.GITHUB_TOKEN }}

    - name: Build Java server
      run: task mavenBuild

    - name: Set up QEMU
      if: ${{ inputs.build_multiarch }}
      uses: docker/setup-qemu-action@v4

    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v4
      with:
        version: 'v0.34.1'
        buildkitd-config-inline: |
          [worker.oci]
            max-parallelism = 2

    - name: Cache Docker layers
      uses: actions/cache@v5
      with:
        path: /tmp/.buildx-cache
        key: ${{ runner.os }}-buildx-${{ github.sha }}
        restore-keys: |
          ${{ runner.os }}-buildx-

    - name: Build container images
      run: task dockerBuildImages
      env:
        BUILD_MULTIARCH: ${{ inputs.build_multiarch }}
        # one cache subdirectory per bake target, see docker-bake.hcl
        CACHE_FROM_DIR: '/tmp/.buildx-cache'
        CACHE_TO_DIR: '/tmp/.buildx-cache-new'

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

    - name: Install Node dependencies
      run: npm install

    - name: Run smoke tests
      run: task smokeTests

    # This ugly bit is necessary if you don't want your cache to grow forever until it hits GitHub's limit of 5GB.
    # Temp fix
    # https://github.com/docker/build-push-action/issues/252
    # https://github.com/moby/buildkit/issues/1896
    - name: Move cache
      run: |
        rm -rf /tmp/.buildx-cache
        mv /tmp/.buildx-cache-new /tmp/.buildx-cache

The same workflow, on Latchkey

Estimated ~20% faster on cache hits, plus fewer wasted runs and a safer supply chain. Added and changed lines are highlighted.

name: CI
 
on:
  push:
    paths-ignore:
      - 'docs/**'
    branches:
      - main
  pull_request:
    paths-ignore:
      - 'docs/**'
    branches:
      - '*'
  workflow_call:
    inputs:
      build_multiarch:
        default: false
        required: true
        type: boolean
 
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  test-containers:
    timeout-minutes: 30
    runs-on: latchkey-small
    steps:
    - uses: actions/checkout@v6
    
    - name: Free up disk space
      #if: ${{ inputs.build_multiarch }}
      shell: bash
      # workaround: https://github.com/jlumbroso/free-disk-space/issues/9
      # copied from https://github.com/hirnidrin/free-disk-space/blob/4bacba7c412c8ace26b87b5b79977da05137e69d/action.yml
      run: |
        # ======
        # MACROS
        # ======
  
        # macro to print a line of equals
        # (silly but works)
        printSeparationLine() {
          str=${1:=}
          num=${2:-80}
          counter=1
          output=""
          while [ $counter -le $num ]
          do
             output="${output}${str}"
             counter=$((counter+1))
          done
          echo "${output}"
        }
  
        # macro to compute available space
        # REF: https://unix.stackexchange.com/a/42049/60849
        # REF: https://stackoverflow.com/a/450821/408734
        getAvailableSpace() { echo $(df -a $1 | awk 'NR > 1 {avail+=$4} END {print avail}'); }
  
        # macro to make Kb human readable (assume the input is Kb)
        # REF: https://unix.stackexchange.com/a/44087/60849
        formatByteCount() { echo $(numfmt --to=iec-i --suffix=B --padding=7 $1'000'); }
  
        # macro to output saved space
        printSavedSpace() {
          saved=${1}
          title=${2:-}
  
          echo ""
          printSeparationLine '*' 80
          if [ ! -z "${title}" ]; then
            echo "=> ${title}: Saved $(formatByteCount $saved)"
          else
            echo "=> Saved $(formatByteCount $saved)"
          fi
          printSeparationLine '*' 80
          echo ""
        }
  
        # macro to print output of dh with caption
        printDH() {
          caption=${1:-}
  
          printSeparationLine '=' 80
          echo "${caption}"
          echo ""
          echo "$ dh -h /"
          echo ""
          df -h /
          echo "$ dh -a /"
          echo ""
          df -a /
          echo "$ dh -a"
          echo ""
          df -a
          printSeparationLine '=' 80
        }
 
        # ======
        # SCRIPT
        # ======
  
        # Display initial disk space stats
  
        AVAILABLE_INITIAL=$(getAvailableSpace)
        AVAILABLE_ROOT_INITIAL=$(getAvailableSpace '/')
  
        printDH "BEFORE CLEAN-UP:"
        echo ""
  
        # Remove Android library
        BEFORE=$(getAvailableSpace)
 
        sudo rm -rf /usr/local/lib/android
 
        AFTER=$(getAvailableSpace)
        SAVED=$((AFTER-BEFORE))
        printSavedSpace $SAVED "Android library"
  
        BEFORE=$(getAvailableSpace)
 
        # https://github.community/t/bigger-github-hosted-runners-disk-space/17267/11
        sudo rm -rf /usr/share/dotnet
 
        AFTER=$(getAvailableSpace)
        SAVED=$((AFTER-BEFORE))
        printSavedSpace $SAVED ".NET runtime"
  
        BEFORE=$(getAvailableSpace)
 
        sudo rm -rf /opt/ghc
 
        AFTER=$(getAvailableSpace)
        SAVED=$((AFTER-BEFORE))
        printSavedSpace $SAVED "Haskell runtime"
  
        # Remove large packages
        # REF: https://github.com/apache/flink/blob/master/tools/azure-pipelines/free_disk_space.sh
  
        BEFORE=$(getAvailableSpace)
 
        sudo apt-get remove -y '^aspnetcore-.*'
        sudo apt-get remove -y '^dotnet-.*'
        sudo apt-get remove -y '^llvm-.*'
        sudo apt-get remove -y 'php.*'
        sudo apt-get remove -y '^mongodb-.*'
        sudo apt-get remove -y '^mysql-.*'
        sudo apt-get remove -y azure-cli google-chrome-stable firefox powershell mono-devel libgl1-mesa-dri
        sudo apt-get autoremove -y
        sudo apt-get clean
 
        AFTER=$(getAvailableSpace)
        SAVED=$((AFTER-BEFORE))
        printSavedSpace $SAVED "Large misc. packages"
  
        # Remove Docker images
        BEFORE=$(getAvailableSpace)
 
        sudo docker image prune --all --force
 
        AFTER=$(getAvailableSpace)
        SAVED=$((AFTER-BEFORE))
        printSavedSpace $SAVED "Docker images"
 
        # Remove Swap storage
        BEFORE=$(getAvailableSpace)
 
        sudo swapoff -a
        sudo rm -f /mnt/swapfile
        free -h
 
        AFTER=$(getAvailableSpace)
        SAVED=$((AFTER-BEFORE))
        printSavedSpace $SAVED "Swap storage"
  
        # Output saved space statistic
  
        AVAILABLE_END=$(getAvailableSpace)
        AVAILABLE_ROOT_END=$(getAvailableSpace '/')
  
        echo ""
        printDH "AFTER CLEAN-UP:"
  
        echo ""
        echo ""
  
        echo "/dev/root:"
        printSavedSpace $((AVAILABLE_ROOT_END - AVAILABLE_ROOT_INITIAL))
        echo "overall:"
        printSavedSpace $((AVAILABLE_END - AVAILABLE_INITIAL))
 
    - name: Install GraphViz
      run: sudo apt-get update && sudo apt-get install graphviz -y
 
    - name: Set up JDK 21
      uses: actions/setup-java@v5
      with:
        java-version: 21
        distribution: temurin
 
    - name: Install Task
      uses: go-task/setup-task@v2
      with:
        version: 3.x
        repo-token: ${{ secrets.GITHUB_TOKEN }}
 
    - name: Build Java server
      run: task mavenBuild
 
    - name: Set up QEMU
      if: ${{ inputs.build_multiarch }}
      uses: docker/setup-qemu-action@v4
 
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v4
      with:
        version: 'v0.34.1'
        buildkitd-config-inline: |
          [worker.oci]
            max-parallelism = 2
 
    - name: Cache Docker layers
      uses: actions/cache@v5
      with:
        path: /tmp/.buildx-cache
        key: ${{ runner.os }}-buildx-${{ github.sha }}
        restore-keys: |
          ${{ runner.os }}-buildx-
 
    - name: Build container images
      run: task dockerBuildImages
      env:
        BUILD_MULTIARCH: ${{ inputs.build_multiarch }}
        # one cache subdirectory per bake target, see docker-bake.hcl
        CACHE_FROM_DIR: '/tmp/.buildx-cache'
        CACHE_TO_DIR: '/tmp/.buildx-cache-new'
 
    - name: 'Setup Node.js 24'
      uses: actions/setup-node@v6
      with:
        cache: 'npm'
        node-version: 24
 
    - name: Install Node dependencies
      run: npm install
 
    - name: Run smoke tests
      run: task smokeTests
 
    # This ugly bit is necessary if you don't want your cache to grow forever until it hits GitHub's limit of 5GB.
    # Temp fix
    # https://github.com/docker/build-push-action/issues/252
    # https://github.com/moby/buildkit/issues/1896
    - name: Move cache
      run: |
        rm -rf /tmp/.buildx-cache
        mv /tmp/.buildx-cache-new /tmp/.buildx-cache
 
 

What changed

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