Skip to content

Self-hosted runners let you run CI/CD jobs on your own machines. Use for custom hardware, private networks, or specific environments.

Why Self-Hosted? ​

  • Custom hardware — GPUs, specialized CPUs
  • Private network — Access internal resources
  • Larger storage — Bigger disk than hosted
  • Specific OS — Custom Linux, Windows, macOS
  • Compliance — Data stays on-premise

Installing Runners ​

System Requirements ​

ResourceMinimumRecommended
CPU2 cores4+ cores
RAM4 GB8+ GB
Disk20 GB100+ GB
NetworkOutbound HTTPSLow latency

Download ​

bash
curl -fsSL https://kizuna.codes/install.sh | bash

The installer checks the release's signed SHA256SUMS against the Kizuna release key built into it, and the archive against SHA256SUMS, before it extracts anything. To install by hand instead, download the archive, SHA256SUMS and SHA256SUMS.minisig from the release at github.com/deep-thinking-lab/kizuna-releases and verify them first:

bash
minisign -V -P RWTZtRmNKVOR0U5V5FxnqiwHfz7nP/olquYcs6jQDckepXx7H7tzwxEt -m SHA256SUMS -x SHA256SUMS.minisig
sha256sum -c --ignore-missing SHA256SUMS
tar xzf kizuna-runner-linux-amd64.tar.gz

The trusted comment minisign prints must name the version you downloaded (kizuna-runner v1.2.3).

Configure ​

Get token from repository settings:

  1. Go to Settings → CI/CD → Runners
  2. Click New Self-Hosted Runner
  3. Copy registration token

Configure runner:

bash
./kizuna-runner configure \
  --url https://kizuna.example.com/org/repo \
  --token REGISTRATION_TOKEN \
  --name "my-runner" \
  --labels "gpu,self-hosted"

Install as Service ​

Linux (systemd) ​

bash
sudo ./kizuna-runner install
sudo systemctl start kizuna-runner
sudo systemctl enable kizuna-runner

macOS (launchd) ​

bash
sudo ./kizuna-runner install
sudo launchctl load /Library/LaunchDaemons/kizuna.runner.plist

Windows ​

powershell
.\kizuna-runner.exe install
Start-Service kizuna-runner

Manual Start ​

bash
# Run interactively
./kizuna-runner run

Runner Labels ​

Labels route jobs to specific runners:

Assign Labels ​

bash
./kizuna-runner configure \
  --labels "gpu,cuda,linux"

Use Labels in Workflow ​

yaml
jobs:
  train-model:
    runs-on: [self-hosted, gpu, cuda]
    steps:
      - run: nvidia-smi
      - run: python train.py

Common Labels ​

LabelUse Case
self-hostedAny self-hosted runner
linuxLinux OS
windowsWindows OS
macosmacOS
gpuGPU available
arm64ARM architecture
productionProduction deployment

Runner API (HTTP) ​

Under the hood the runner agent speaks a small HTTP protocol. The flow is enroll → register → heartbeat → claim → report.

1. Enroll (owner, one-time) ​

An authenticated owner mints a one-time registration token scoped to their account. Pass repository_id to pin the runner to a single repo; omit it for an owner-wide runner that serves any repo you own.

bash
curl -X POST /api/v1/self-hosted-runners \
  -H "Authorization: Bearer $USER_TOKEN" \
  -d '{"name": "builder-1", "labels": ["Linux", "X64", "voxeltron-ci"]}'
# → 201 {"id": "...", "registration_token": "<one-time secret>"}

The registration_token is shown once. Only its hash is stored.

2. Register (agent) ​

The agent trades the registration token for a long-lived auth token and declares its runtime labels. The registration token is single-use and cleared on success.

bash
curl -X POST /api/v1/runners/register \
  -d '{"registration_token": "<from step 1>",
       "labels": ["Linux", "X64", "voxeltron-ci"], "os": "linux", "arch": "x64"}'
# → 201 {"id": "...", "status": "online", "auth_token": "<long-lived bearer>"}

3. Heartbeat (agent) ​

The agent heartbeats to stay online. A runner that misses heartbeats for ~120 s is automatically marked offline and stops matching jobs.

bash
curl -X POST /api/v1/runners/heartbeat -H "Authorization: Bearer $AUTH_TOKEN"

4. Claim (agent) ​

Long-poll for the next job whose runs-on labels this runner satisfies. A claim is atomic — two agents racing for the same job cannot both win.

bash
curl -X POST /api/v1/runners/jobs/claim -H "Authorization: Bearer $AUTH_TOKEN"
# → 200 {"job": {"pipeline_job_id": "...", "steps": [...], "auth_token": "...", "context": {...}}}
# → 204 when nothing is waiting

The per-job auth_token in the payload is scoped to {repository_id, ref} and is what the job uses for build-cache and artifact callbacks.

5. Report (agent) ​

Report status as the job progresses. Only the runner that claimed a job may report on it.

bash
curl -X POST /api/v1/runners/jobs/$JOB_ID/status \
  -H "Authorization: Bearer $AUTH_TOKEN" \
  -d '{"status": "success"}'   # running | success | failed | cancelled

A terminal status fans out to downstream (needs:) jobs exactly as cloud and host execution do.

Label routing & scoping ​

  • A job with runs-on: [self-hosted, …] is not sent to an ephemeral cloud runner — it is left claimable for a registered runner whose label set is a superset of the job's labels (the self-hosted sentinel is implicit).
  • A repo-pinned runner only serves its repository; an owner-wide runner serves any repository owned by the same account.
  • If a self-hosted job has no matching online runner, it waits (and the dispatcher logs a warning) until one registers — it is never silently dropped.

Runner Groups ​

Organize runners into groups:

bash
# Create group via API
curl -X POST /api/v1/runner-groups \
  -d '{
    "name": "gpu-runners",
    "visibility": "selected",
    "repositories": ["org/ml-project"]
  }'

Assign runner to group:

bash
./kizuna-runner configure \
  --group "gpu-runners"

Security ​

Job Isolation ​

Each job runs in clean environment:

  • Fresh working directory
  • Isolated network (optional)
  • No access to previous job state

Network Security ​

Restrict runner network:

bash
# Firewall rules
sudo ufw default deny outgoing
sudo ufw allow out to kizuna.example.com
sudo ufw allow out to npm registry
sudo ufw enable

Secrets ​

Runners access repository secrets. Protect them:

  • Limit runner access to specific repos
  • Use runner groups with visibility controls
  • Rotate credentials regularly

Monitoring ​

Runner Status ​

View in Settings → CI/CD → Runners:

my-runner
├── Status: Online
├── Last seen: 2 minutes ago
├── Jobs: 47 completed, 0 failed
└── Labels: [linux, gpu]

Logs ​

bash
# View runner logs
journalctl -u kizuna-runner -f

# Or from runner directory
./kizuna-runner logs

Metrics ​

bash
curl /api/v1/runners/my-runner/metrics

Returns:

json
{
  "status": "online",
  "jobs_completed": 47,
  "jobs_failed": 0,
  "average_job_duration": 245,
  "disk_usage_percent": 65,
  "cpu_percent": 23
}

Maintenance ​

Update Runner ​

bash
# Stop service
sudo systemctl stop kizuna-runner

# Install the new version (verified, as above)
curl -fsSL https://kizuna.codes/install.sh | bash

# Start service
sudo systemctl start kizuna-runner

Clean Working Directory ​

bash
# Remove old job directories
./kizuna-runner cleanup

# Or manually
rm -rf /opt/kizuna-runner/_work/*

Remove Runner ​

bash
# Unregister
./kizuna-runner remove --token REGISTRATION_TOKEN

# Remove service
sudo systemctl disable kizuna-runner
sudo rm /etc/systemd/system/kizuna-runner.service

Troubleshooting ​

Runner Won't Start ​

Check:

  1. Token is valid
  2. URL is correct
  3. Network connectivity
  4. Permissions on runner directory
bash
# Test connectivity
curl https://kizuna.example.com/api/v1/health

# Check logs
./kizuna-runner logs

Jobs Not Running ​

Check:

  1. Runner is online
  2. Labels match workflow
  3. Runner has access to repository

Out of Disk Space ​

bash
# Clean up
./kizuna-runner cleanup

# Or set auto-cleanup
./kizuna-runner configure --auto-cleanup

Network Timeouts ​

Increase timeouts in configuration:

bash
./kizuna-runner configure \
  --http-timeout 60 \
  --job-timeout 3600

Summary ​

Self-hosted runners provide:

  • Custom environments — Your hardware, your OS
  • Private resources — Internal network access
  • Scalability — Add runners as needed
  • Security — Data stays in your control

They're essential for specialized CI/CD needs.