Skip to content

GitHub Actions

Automate cluster provisioning and configuration sync with GitHub Actions.

The quick path — the reusable workflow

lok8s ships a ready-made workflow that provisions a committed cluster and, when the cluster opts into kubehz visibility (spec.kubehz.access is registered or managed), registers it and prints the claim fingerprint to the job summary (the claim key lo uploads to your Hetzner project; see Claiming). It needs exactly one secret:

yaml
# .github/workflows/spinup.yml
name: Spin up cluster
on: workflow_dispatch

jobs:
  spinup:
    uses: kernpilot/lok8s/.github/workflows/spinup.yml@main
    with:
      domain: my-cluster.example.com
    secrets:
      HCLOUD_TOKEN: ${{ secrets.HCLOUD_TOKEN }}

@main is a moving reference

This calls the reusable workflow at @main, so it changes when lok8s changes. That is convenient while lok8s is young, but it means a run can behave differently from the last one without you changing anything. Substituting a commit SHA (spinup.yml@<sha>) pins the behaviour you tested and is the safer choice for anything that provisions real infrastructure. Resolve the SHA main points at right now, then paste it into uses::

bash
gh api repos/kernpilot/lok8s/commits/main --jq .sha

Pinning to the current release (@v0.2.0, August 2026) is not recommended yet: the workflow has gained claim-key provisioning since that tag, so the release is behind what this page documents.

The cluster.lok8s.yaml for the domain must be committed under clusters/<domain>/ in your repo. No platform token is needed: ownership is proven later, interactively, when you claim the cluster.

Prefer this unless you need custom steps; the workflows below show the full manual setup.

Writing your own workflows

Prerequisites

  • A repository containing your clusters/<domain>/cluster.lok8s.yaml
  • GitHub repository secrets configured (below)

Required secrets

Add these to your repository settings under Settings > Secrets and variables > Actions:

SecretDescription
HCLOUD_TOKENHetzner Cloud API token
SSH_PRIVATE_KEYSSH private key matching sshPrivateKey in your provider descriptor (node access)
KUBECONFIGBase64-encoded kubeconfig (for sync workflows)

Installing lok8s in CI

lok8s is distributed as a b environment, so a runner needs b first, then the pinned toolchain. If your repo already commits .bin/b.yaml (the lok8s installer creates it), b install alone restores the exact same toolchain; skip the b env add line.

yaml
      - name: Install b (binary manager)
        run: |
          curl -fsSL https://raw.githubusercontent.com/fentas/b/v4.18.4/install.sh \
            -o /tmp/b-install.sh
          B_INSTALL_DIR="$HOME/.local/bin" bash /tmp/b-install.sh
          echo "$HOME/.local/bin" >> "$GITHUB_PATH"

      - name: Install the lok8s toolchain
        env:
          # avoids GitHub API rate limits on binary downloads
          GITHUB_TOKEN: ${{ github.token }}
        run: |
          b env add github.com/kernpilot/lok8s#kubeone
          b install
          echo "$PWD/.bin" >> "$GITHUB_PATH"

Provision workflow

Create .github/workflows/provision.yml:

yaml
name: Provision Cluster
on:
  workflow_dispatch:
    inputs:
      action:
        description: 'Action to perform'
        required: true
        default: 'provision'
        type: choice
        options:
          - provision
          - destroy

jobs:
  provision:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install b (binary manager)
        run: |
          curl -fsSL https://raw.githubusercontent.com/fentas/b/v4.18.4/install.sh \
            -o /tmp/b-install.sh
          B_INSTALL_DIR="$HOME/.local/bin" bash /tmp/b-install.sh
          echo "$HOME/.local/bin" >> "$GITHUB_PATH"

      - name: Install the lok8s toolchain
        env:
          GITHUB_TOKEN: ${{ github.token }}
        run: |
          b env add github.com/kernpilot/lok8s#kubeone
          b install
          echo "$PWD/.bin" >> "$GITHUB_PATH"

      - name: Setup SSH key
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519

      - name: Run lo
        env:
          HCLOUD_TOKEN: ${{ secrets.HCLOUD_TOKEN }}
        run: lo --domain my-cluster.example.com ${{ inputs.action }}

The dispatch input selects lo provision or lo destroy, so one workflow covers spin-up and teardown.

Sync workflow

Create .github/workflows/sync.yml to apply configuration changes on push:

yaml
name: Sync Cluster
on:
  push:
    branches: [main]
    paths:
      - 'clusters/**'

jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install b (binary manager)
        run: |
          curl -fsSL https://raw.githubusercontent.com/fentas/b/v4.18.4/install.sh \
            -o /tmp/b-install.sh
          B_INSTALL_DIR="$HOME/.local/bin" bash /tmp/b-install.sh
          echo "$HOME/.local/bin" >> "$GITHUB_PATH"

      - name: Install the lok8s toolchain
        env:
          GITHUB_TOKEN: ${{ github.token }}
        run: |
          b env add github.com/kernpilot/lok8s#kubeone
          b install
          echo "$PWD/.bin" >> "$GITHUB_PATH"

      - name: Setup kubeconfig
        run: |
          mkdir -p ~/.kube
          echo "${{ secrets.KUBECONFIG }}" | base64 -d > ~/.kube/config

      - name: Reconcile infrastructure
        env:
          HCLOUD_TOKEN: ${{ secrets.HCLOUD_TOKEN }}
        run: lo --domain my-cluster.example.com provision

      - name: Render the manifests
        run: lo --domain my-cluster.example.com build

      - name: Deploy platform
        run: lo --domain my-cluster.example.com deploy

lo provision is idempotent: re-running it reconciles the cluster to your spec. lo build renders your services and addons into clusters/<domain>/artifacts.yaml, and lo deploy applies that file. lo deploy refuses to run before lo build has produced it.

Next steps


Doc status

AspectDetail
Last reviewed5 September 2026