Skip to main content

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:

ExecutorHost requirementRunner is a...
meda (default on Linux)Linux with KVMlightweight VM (cloud-hypervisor)
lume (default on macOS)Apple Silicon (Mac Mini, Mac Pro, MacBook)macOS VM (Apple Virtualization framework)
dockerLinux with Docker, or macOS with Docker Desktopcontainer

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

  1. Create an API token on the Cirun Dashboard.

  2. Install the cirun-agent:

    curl --proto '=https' --tlsv1.2 -LsSf https://raw.githubusercontent.com/cirunlabs/cirun-agent/refs/heads/main/install.sh | sh
  3. Install and start as a system service (recommended):

    # Linux (systemd)
    export CIRUN_API_TOKEN=YOUR_TOKEN
    sudo -E cirun-agent --install-service

    # macOS (launchd)
    export CIRUN_API_TOKEN=YOUR_TOKEN
    cirun-agent --install-service

    The 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_TOKEN
    cirun-agent
  4. Create a .cirun.yml file in your repository. Pick the executor that matches your host:

    macOS VM (lume):

    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
    tip

    Match instance_type to the template's native size. The agent resizes the macOS APFS disk via lume, which truncates and breaks boot if you request a size smaller than the source. The xcode:16.4 template ships as 8 vCPU / 8 GB / 100 GB.

    Linux VM (meda):

    runners:
    - name: cirun-linux-runner
    cloud: on_prem
    instance_type: 8vcpu-8gb-75gb
    machine_image: "ubuntu:latest"
    region: RegionOne
    labels:
    - cirun-linux-runner

    Docker container (linux or macOS host):

    runners:
    - name: cirun-docker-runner
    cloud: on_prem
    instance_type: 4vcpu-8gb-50gb
    machine_image: "ghcr.io/cirunlabs/cirun-docker-runner-image:latest"
    region: RegionOne
    extra_config:
    executor: docker
    # Optional: start dockerd inside the container so jobs can
    # `docker build` / `docker run` (docker-in-docker).
    privileged: true
    labels:
    - cirun-docker-runner

    The cirun-docker-runner-image is 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-runner
    cloud: on_prem
    instance_type: 4vcpu-8gb-50gb
    machine_image: "ghcr.io/cirunlabs/cirun-docker-runner-image:gpu"
    region: RegionOne
    extra_config:
    executor: docker
    gpu: all
    labels:
    - cirun-gpu-docker-runner

    The :gpu tag adds CUDA 13.2.1 runtime libs on top of the same toolchain. Host needs nvidia-container-toolkit installed; gpu: all is passed through to docker run --gpus.

    GPU VM (meda) — VFIO passthrough:

    runners:
    - name: cirun-meda-gpu
    cloud: on_prem
    instance_type: 4vcpu-8gb-50gb
    machine_image: "ubuntu-gpu:latest"
    region: RegionOne
    extra_config:
    executor: meda
    gpu: all
    labels:
    - cirun-meda-gpu

    This passes a physical NVIDIA GPU straight into a lightweight VM via VFIO, so the job gets the real device (nvidia-smi works inside the runner), not a shared container view. gpu: all leases 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-gpu image ships the NVIDIA driver pre-installed and is built + published to ghcr.io/cirunlabs/ubuntu-gpu by packer-plugin-meda.

    Host setup

    VFIO passthrough needs one-time host configuration: enable the IOMMU (intel_iommu=on on the kernel cmdline, without iommu=pt), bind the target NVIDIA device to the vfio-pci driver, install genisoimage and qemu-utils, and give cirun-agent LimitMEMLOCK=infinity (VFIO pins guest RAM). See the reference host script in the meda repository.

  5. 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.

KeyTypeEffect
executormeda | lume | dockerOverride the host default. Required if the host supports more than one (e.g. Linux with Docker installed but you want a VM).
privilegedboolRun the container with --privileged. Required for docker-in-docker.
docker_socket_mountboolBind-mount /var/run/docker.sock from the host into the runner so jobs share the host daemon (docker-out-of-docker).
gpuall | none | integerGPU 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.