On Premise
Cirun supports spinning up runners on your own infrastructure. The agent picks
one of three executor backends based on the host and your .cirun.yml:
| Executor | Host requirement | Runner is a... |
|---|---|---|
meda (default on Linux) | Linux with KVM | lightweight VM (cloud-hypervisor) |
lume (default on macOS) | Apple Silicon (Mac Mini, Mac Pro, MacBook) | macOS VM (Apple Virtualization framework) |
docker | Linux with Docker, or macOS with Docker Desktop | container |
The setup is simple and can be done in a few minutes. You install
cirun-agent on your machine, and it connects to Cirun's API to spin up
on-demand ephemeral runners for GitHub Actions.
Setting up cirun-agent
-
Create an API token on the Cirun Dashboard.
-
Install the cirun-agent:
curl --proto '=https' --tlsv1.2 -LsSf https://raw.githubusercontent.com/cirunlabs/cirun-agent/refs/heads/main/install.sh | sh -
Install and start as a system service (recommended):
# Linux (systemd)export CIRUN_API_TOKEN=YOUR_TOKENsudo -E cirun-agent --install-service# macOS (launchd)export CIRUN_API_TOKEN=YOUR_TOKENcirun-agent --install-serviceThe service starts on boot and restarts on failure. Logs go to the system journal (Linux) or
~/Library/Logs/cirun-agent.log(macOS).Alternative: run manually in the foreground:
export CIRUN_API_TOKEN=YOUR_TOKENcirun-agent -
Create a
.cirun.ymlfile in your repository. Pick the executor that matches your host:macOS VM (lume):
runners:- name: cirun-macos-runnercloud: on_preminstance_type: 8vcpu-8gb-100gbmachine_image: "cirunlabs/macos-sequoia-xcode:16.4"region: RegionOnelabels:- cirun-macos-runnertipMatch
instance_typeto the template's native size. The agent resizes the macOS APFS disk vialume, which truncates and breaks boot if you request a size smaller than the source. Thexcode:16.4template ships as 8 vCPU / 8 GB / 100 GB.Linux VM (meda):
runners:- name: cirun-linux-runnercloud: on_preminstance_type: 8vcpu-8gb-75gbmachine_image: "ubuntu:latest"region: RegionOnelabels:- cirun-linux-runnerDocker container (linux or macOS host):
runners:- name: cirun-docker-runnercloud: on_preminstance_type: 4vcpu-8gb-50gbmachine_image: "ghcr.io/cirunlabs/cirun-docker-runner-image:latest"region: RegionOneextra_config:executor: docker# Optional: start dockerd inside the container so jobs can# `docker build` / `docker run` (docker-in-docker).privileged: truelabels:- cirun-docker-runnerThe
cirun-docker-runner-imageis a multi-arch (amd64 + arm64) Ubuntu 24.04 image with the GitHub Actions runner deps and Docker pre-installed.GPU Docker container (Linux host with NVIDIA):
runners:- name: cirun-gpu-docker-runnercloud: on_preminstance_type: 4vcpu-8gb-50gbmachine_image: "ghcr.io/cirunlabs/cirun-docker-runner-image:gpu"region: RegionOneextra_config:executor: dockergpu: alllabels:- cirun-gpu-docker-runnerThe
:gputag adds CUDA 13.2.1 runtime libs on top of the same toolchain. Host needsnvidia-container-toolkitinstalled;gpu: allis passed through todocker run --gpus.GPU VM (meda) — VFIO passthrough:
runners:- name: cirun-meda-gpucloud: on_preminstance_type: 4vcpu-8gb-50gbmachine_image: "ubuntu-gpu:latest"region: RegionOneextra_config:executor: medagpu: alllabels:- cirun-meda-gpuThis passes a physical NVIDIA GPU straight into a lightweight VM via VFIO, so the job gets the real device (
nvidia-smiworks inside the runner), not a shared container view.gpu: allleases every free GPU on the host to the VM;gpu: 1(or any integer) leases that many.Each GPU is leased exclusively to one runner. When all GPUs are in use, further GPU jobs are held as backpressure and retried once a device frees up — they are not failed. Non-GPU jobs are unaffected.
The
ubuntu-gpuimage ships the NVIDIA driver pre-installed and is built + published toghcr.io/cirunlabs/ubuntu-gpubypacker-plugin-meda.Host setupVFIO passthrough needs one-time host configuration: enable the IOMMU (
intel_iommu=onon the kernel cmdline, withoutiommu=pt), bind the target NVIDIA device to thevfio-pcidriver, installgenisoimageandqemu-utils, and givecirun-agentLimitMEMLOCK=infinity(VFIO pins guest RAM). See the reference host script in the meda repository. -
Configure your workflow to use the runner:
runs-on: cirun-linux-runner--${{ github.run_id }}Make sure you have installed the Cirun GitHub App and marked the repository as active on the Cirun Dashboard.
extra_config reference
Per-runner knobs under extra_config: in .cirun.yml. All are optional;
all apply to the docker executor unless noted.
| Key | Type | Effect |
|---|---|---|
executor | meda | lume | docker | Override the host default. Required if the host supports more than one (e.g. Linux with Docker installed but you want a VM). |
privileged | bool | Run the container with --privileged. Required for docker-in-docker. |
docker_socket_mount | bool | Bind-mount /var/run/docker.sock from the host into the runner so jobs share the host daemon (docker-out-of-docker). |
gpu | all | none | integer | GPU request. docker: passed to docker run --gpus (needs nvidia-container-toolkit). meda: VFIO passthrough of that many physical GPUs into the VM, leased exclusively per runner. all = every free GPU; an integer = that many. |
Capacity planning
Unlike cloud runners (where capacity is the provider's problem), on-prem runners share the finite CPU / RAM / disk of your host. Two knobs let you shape concurrency:
Cap concurrent runners with --max-runners
cirun-agent will not start more runners (VMs or containers, depending on
the executor) than --max-runners allows; extra jobs queue until a slot
frees up.
export CIRUN_API_TOKEN=YOUR_TOKEN
cirun-agent --max-runners 5
Defaults:
- macOS:
2(Apple Virtualization framework caps concurrent VMs at 2 on most Apple Silicon hardware). - Linux: unlimited — set this explicitly to match your host's CPU/RAM.
Re-install the service after changing the flag so the new limit is picked up:
export CIRUN_API_TOKEN=YOUR_TOKEN
sudo -E cirun-agent --install-service --max-runners 5 # Linux
cirun-agent --install-service --max-runners 5 # macOS
Zero-latency jobs with idle runners
Cloud runners take a couple of minutes to provision, but on-prem runners
provision in seconds — and even those seconds disappear if you keep a
warm pool with idle:. An idle runner is
already booted and registered with GitHub, so a job picks it up instantly.
runners:
- name: cirun-macos-runner
cloud: on_prem
instance_type: 8vcpu-8gb-100gb
machine_image: "cirunlabs/macos-sequoia-xcode:16.4"
region: RegionOne
labels:
- cirun-macos-runner
idle:
replicas: 2
Keep replicas ≤ --max-runners (otherwise the idle pool can't reach its
target). When using idle runners, drop the --${{ github.run_id }} suffix
on runs-on — see Idle Runners for full
details and time-windowed schedules.