Schedule icon
Script icon
If icon
Fail icon
Command icon
Log icon
OutputValues icon
Set icon
SlackIncomingWebhook icon

GitHub Actions Self-Hosted Runner Disk & Cache Pruner

Automate GitHub Actions runner disk maintenance, prune Docker volumes and temp caches above 85% usage, and report space to KV and Slack.

Categories
CoreInfrastructure

Self-hosted GitHub Actions runners accumulate unused Docker images, dangling container layers, and multi-gigabyte build artifacts in temporary workspace directories over time. Without automated hygiene, runner disks fill up to 100%, causing CI/CD pipeline jobs to fail mid-build with No space left on device errors and taking critical build compute offline.

This blueprint orchestrates end-to-end self-hosted runner maintenance. It polls GitHub's Actions API to verify runner group inventory and availability, executes SSH commands to measure root partition disk usage (df -h), and evaluates capacity against a configurable threshold (default 85%). If the runner exceeds the threshold, Kestra verifies the runner is not currently busy executing a build, triggers automated Docker pruning (docker system prune -af --volumes) and cleans /home/runner/_work/_temp, calculates storage reclaimed, writes persistent metrics to Kestra KV storage, and dispatches a detailed audit summary to Slack.

How it works

  1. poll_github_runners (io.kestra.plugin.scripts.python.Script) interrogates the GitHub Actions REST API (GET /orgs/{org}/actions/runners and GET /orgs/{org}/actions/runner-groups) to verify target runner registration, group membership, online status, and whether the node is currently running an active workflow job.
  2. verify_runner_online (io.kestra.plugin.core.flow.If) halts execution early if the runner is offline or uninstalled in GitHub, preventing spurious SSH attempts against decommissioned infrastructure.
  3. check_runner_disk_pre (io.kestra.plugin.fs.ssh.Command) connects to the runner host over SSH and reads partition disk utilization via df -Pk /, exporting disk percentage and used/available gigabytes into Kestra outputs.
  4. evaluate_disk_threshold (io.kestra.plugin.core.flow.If) compares current utilization against disk_threshold_pct (default 85%):
    • If utilization exceeds the threshold:
      • guard_active_job checks whether the runner is busy (busy == true). Unless force_when_busy is set, it safely aborts the prune to avoid corrupting concurrent CI workflows.
      • prune_docker_and_temp_cache (io.kestra.plugin.fs.ssh.Command) issues docker system prune -af --volumes to clear untagged images, stopped containers, build caches, and dangling volumes, and purges the runner temporary workspace directory (/home/runner/_work/_temp).
      • check_runner_disk_post (io.kestra.plugin.fs.ssh.Command) re-reads df -Pk / to measure post-cleanup capacity.
      • calculate_reclaimed_metrics (io.kestra.plugin.scripts.python.Script) computes delta metrics: gigabytes freed, percentage recovered, and status.
    • If utilization is within safe limits:
      • log_disk_healthy and record_healthy_metrics (io.kestra.plugin.core.output.OutputValues) record healthy baseline status without executing any cleanup commands.
  5. store_prune_metrics_kv (io.kestra.plugin.core.kv.Set) persists the runner's latest disk metrics, reclaimed capacity, and execution timestamp into Kestra KV under runner_maintenance_{runner_name}.
  6. notify_slack (io.kestra.plugin.slack.notifications.SlackIncomingWebhook) delivers an executive summary of the maintenance operation and storage deltas to the platform Slack channel.
  7. The errors block alerts Slack immediately if any GitHub API call, SSH command, or prune action encounters an unhandled exception.

What you get

  • Automated, scheduled capacity guardrails preventing runner disk exhaustion.
  • Active job safety guard preventing destructive image and cache pruning during active workflow runs.
  • Precise reclaimed storage auditing stored in Kestra Key-Value store for longitudinal capacity tracking.
  • Slack visibility into CI/CD runner disk headroom, Docker volume reclaim metrics, and fleet health.

Who it's for

  • Platform Engineers and Site Reliability Engineers managing self-hosted GitHub Actions runners.
  • DevOps Teams experiencing frequent ENOSPC / disk full errors on ephemeral CI build agents.
  • Cloud Infrastructure Teams seeking automated Day-2 maintenance across VM fleets.

Why orchestrate this with Kestra

Managing runner cleanup via local host cron jobs lacks central visibility, exposes unmasked credentials, and risks running docker system prune while a production CI job is in flight. Orchestrating maintenance through Kestra provides declarative scheduling, centralized secret masking, GitHub runner state awareness, persistent KV metric tracking, and multi-channel alerting from a single control plane.

Prerequisites

  • A GitHub Personal Access Token (PAT) or GitHub App with read:org and manage_runners:org permissions.
  • An SSH key pair configured for the runner host with sudo rights to invoke Docker and remove temp files.
  • An incoming Slack webhook URL for notifications.

Secrets

  • GITHUB_TOKEN: GitHub personal access token or fine-grained token with permission to view organization runners.
  • RUNNER_SSH_PRIVATE_KEY: Private SSH key utilized to authenticate onto the self-hosted runner node.
  • SLACK_WEBHOOK_URL: Slack Incoming Webhook endpoint used by notify_slack and failure handlers.

Quick start

  1. Configure GITHUB_TOKEN, RUNNER_SSH_PRIVATE_KEY, and SLACK_WEBHOOK_URL in your Kestra namespace secrets.
  2. Import this flow and set runner_host, runner_name, and github_organization inputs to match your runner fleet.
  3. Execute the flow with dry_run: true to verify GitHub API reachability and inspect current disk metrics safely.
  4. Run with dry_run: false to perform real Docker image and temp cache pruning when threshold is exceeded.
  5. Enable scheduled_runner_maintenance trigger to automate periodic health checks across your CI runner infrastructure.

How to extend

  • Multi-Runner Fleet Maintenance: Wrap the disk inspection and prune commands in a ForEach loop across all online runners returned by GitHub Actions API.
  • Custom Build Caches: Add additional cleanup paths for language-specific package caches like ~/.m2/repository, ~/.gradle/caches, or ~/.cache/pip.
  • PagerDuty / Opsgenie Incident Escalation: Integrate incident management webhooks in the errors handler when runner disks reach critical saturation (>95%).

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.