Skip to content

Instantly share code, notes, and snippets.

@jeromebaude
Forked from steve-fraser/readme.md
Created April 13, 2026 10:01
Show Gist options
  • Select an option

  • Save jeromebaude/79bf5c5c48407d881878837ccc6f597b to your computer and use it in GitHub Desktop.

Select an option

Save jeromebaude/79bf5c5c48407d881878837ccc6f597b to your computer and use it in GitHub Desktop.

Custom Labels for GitHub Actions Runners with CAST.AI

This guide shows how to apply custom workload labels to GitHub Actions self-hosted runners so CAST.AI can track and attribute costs per workflow/job.

Overview

When using Actions Runner Controller (ARC) on Kubernetes, job pods are ephemeral. By injecting custom labels, CAST.AI can group costs by repository, workflow, job, or branch.

Architecture

GitHub Actions Workflow
         │
         ▼
    ARC Controller
         │
         ▼
   Runner Pod (listener)
         │
    ┌────┴────┐
    │ pre-run │  ← Injects GitHub env vars into pod template
    │  hook   │
    └────┬────┘
         ▼
    Job Pod with custom labels
         │
         ▼
   CAST.AI tracks costs via label:
   workloads.cast.ai/custom-workload

Setup

1. Create the Pod Template ConfigMap

This template defines the labels applied to each job pod:

apiVersion: v1
kind: ConfigMap
metadata:
  name: k8s-runner-template
  namespace: actions-runner-system
data:
  template: |
    metadata:
      labels:
        workloads.cast.ai/custom-workload: "$GITHUB_JOB-$GITHUB_REPOSITORY"

The label workloads.cast.ai/custom-workload is recognized by CAST.AI for cost allocation.

2. Create the Pre-Run Hook

The hook sanitizes GitHub environment variables (removing invalid characters) and substitutes them into the pod template:

apiVersion: v1
kind: ConfigMap
metadata:
  name: runner-hooks
  namespace: actions-runner-system
data:
  pre-run.sh: |
    #!/bin/sh

    # Sanitize values for Kubernetes labels (alphanumeric, -, _, . only)
    sanitize_label() {
      echo "$1" | sed 's/[^a-zA-Z0-9._-]/_/g'
    }

    SAFE_JOB=$(sanitize_label "$GITHUB_JOB")
    SAFE_REPOSITORY=$(sanitize_label "$GITHUB_REPOSITORY")

    sed -e 's|$GITHUB_JOB|'"$SAFE_JOB"'|g' \
        -e 's|$GITHUB_REPOSITORY|'"$SAFE_REPOSITORY"'|g' \
        "$ACTIONS_RUNNER_CONTAINER_HOOK_TEMPLATE_SRC" > "$ACTIONS_RUNNER_CONTAINER_HOOK_TEMPLATE"

3. Configure the Runner Scale Set

Mount the template and hook into the runner pod:

apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: arc-runner-set
  namespace: actions-runner-system
spec:
  values:
    githubConfigUrl: "https://github.com/YOUR_ORG"
    githubConfigSecret: controller-manager
    containerMode:
      type: "kubernetes-novolume"
    template:
      spec:
        containers:
          - name: runner
            image: ghcr.io/actions/actions-runner:latest
            command: ["/home/runner/run.sh"]
            env:
              - name: ACTIONS_RUNNER_CONTAINER_HOOK_TEMPLATE_SRC
                value: /home/runner/pod-template-src/template
              - name: ACTIONS_RUNNER_CONTAINER_HOOK_TEMPLATE
                value: /home/runner/pod-template/template
              - name: ACTIONS_RUNNER_HOOK_JOB_STARTED
                value: /home/runner/hooks/pre-run.sh
            volumeMounts:
              - name: job-pod-template
                mountPath: /home/runner/pod-template
              - name: pod-template-source
                mountPath: /home/runner/pod-template-src
              - name: runner-hooks
                mountPath: /home/runner/hooks
        volumes:
          - name: job-pod-template
            emptyDir: {}
          - name: pod-template-source
            configMap:
              name: k8s-runner-template
          - name: runner-hooks
            configMap:
              name: runner-hooks

Available GitHub Variables

You can include any of these in your labels:

Variable Example Value
$GITHUB_JOB build
$GITHUB_REPOSITORY myorg/myrepo
$GITHUB_WORKFLOW CI Pipeline
$GITHUB_REF_NAME main
$GITHUB_SHA abc123...
$GITHUB_RUN_ID 12345678

Viewing Costs in CAST.AI

Once labels are applied, use the CAST.AI console or API to filter workload costs:

  1. Navigate to Cost Monitoring > Workloads
  2. Group by the workloads.cast.ai/custom-workload label
  3. View costs per repository, job, or workflow

Troubleshooting

Labels not appearing?

  • Verify the pre-run hook has execute permissions
  • Check that ACTIONS_RUNNER_HOOK_JOB_STARTED env var is set
  • Inspect the generated template: cat /home/runner/pod-template/template

Invalid label characters?

  • Kubernetes labels only allow: a-z, A-Z, 0-9, -, _, .
  • The sanitize function replaces invalid chars with _
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment