# Runner providers

A runner provider is a service or autoscaler that starts a fresh runner for
each queued job and throws it away afterwards. This page says which ones
work with GitCafe [Actions](https://mn.staging.git.cafe/docs/ci/actions.md) and how a runner service connects to
GitCafe as an app.

## GitCafe-hosted runners

GitCafe's own hosted runners (`ubuntu-latest`, `ubuntu-24.04`,
`ubuntu-22.04`) are started by GitCafe's fleet. The protocol the fleet uses
to learn about queued jobs is internal to GitCafe and closed to third
parties; you don't need to do anything to use hosted runners.

## Third-party runner services

Services that run GitHub Actions jobs on their own machines connect to
GitHub, not to GitCafe:

- **Depot, Namespace and hosted Ubicloud** serve github.com only.
- **Blacksmith, WarpBuild** and others that support GitHub Enterprise Server
  could serve GitCafe.

A runner service can serve GitCafe by publishing a GitCafe
[app](https://mn.staging.git.cafe/docs/integrations/apps.md) that your organization installs; see
[Build a runner service as an app](https://mn.staging.git.cafe/docs/ci/runner-providers.md#build-a-runner-service-as-an-app). None
is available today. Until then, a workflow whose `runs-on` names such a
service's labels waits for a runner until its run times out. Change `runs-on`
to `ubuntu-latest` or to your own
[self-hosted runners](https://mn.staging.git.cafe/docs/ci/self-hosted-runners.md).

## Autoscalers you run yourself

**actions-runner-controller (ARC)** with runner scale sets,
**terraform-aws-github-runner** and **RunsOn** need GitHub's scale-set and
webhook APIs, which GitCafe doesn't offer yet; they don't work with GitCafe.

To start a fresh machine for each job with your own tooling, use
[ephemeral or just-in-time runners](https://mn.staging.git.cafe/docs/ci/self-hosted-runners.md#ephemeral-and-just-in-time-runners):
your script asks GitCafe for a JIT configuration, starts a machine with it,
and the runner unregisters itself after one job.

## Build a runner service as an app

A runner service publishes one GitCafe app; each organization that installs
it lets it start just-in-time runners in that organization's
[runner groups](https://mn.staging.git.cafe/docs/ci/self-hosted-runners.md). Its runners are GitHub's official
runner, as on GitHub Enterprise Server.

1. In the app's manifest, ask for the **Runners** permission
   (`{ "kind": "org", "family": "runners", "level": "write" }` in
   `permissions.autonomous.scopes`) and subscribe the app's webhook to
   `actions.workflow_job`. **Read** is enough for the events and the demand
   read below; **Write** also starts runners. Both levels are **Org-wide**
   and **Owner only**: the events and the demand name every repository the
   organization's runner groups serve, private ones and ones outside an
   installation's selected repositories included, and the runners take jobs,
   with their secrets, from all of them. An organization admin's API token
   with the same permission works the same way.
2. The organization installs the app, on all repositories so its webhook
   hears about every job (app webhooks send events only from the
   repositories an installation can reach).
3. Start runners for queued jobs: `actions.workflow_job` sends `queued` when
   a job one of the organization's runner groups serves is queued, with the
   job's `labels` and, when only one group serves it, its `runnerGroupId`.
   If you serve those labels, mint a runner with an
   [installation token](https://mn.staging.git.cafe/docs/integrations/apps.md#publish-an-app):

   ```sh
   curl -X POST https://git.cafe/api/v3/orgs/acme/actions/runners/generate-jitconfig \
     -H "Authorization: Bearer $INSTALLATION_TOKEN" -H "Content-Type: application/json" \
     -d '{"name": "depot-7f3a", "runner_group_id": 12, "labels": ["depot-ubuntu-24.04"]}'
   ```

   Start a fresh machine with `./run.sh --jitconfig <encoded_jit_config>`.
   The runner is ephemeral: it takes one job and unregisters itself. When
   `runnerGroupId` is `null`, several groups serve the job: use the group
   the organization chose while setting your app up
   (`cafe runner groups list` shows each group's number and pool ID).

4. Follow the job through its events: `in_progress` when a runner takes it,
   with the pool and runner, and `completed` when it ends, or when it is
   cancelled before any runner took it. See
   [Actions job events](https://mn.staging.git.cafe/docs/integrations/webhooks.md#actions-job-events).
5. Catch up after missed deliveries with the pool's demand: the **Read pool
   demand** operation of the [REST API](https://mn.staging.git.cafe/docs/reference/api.md), with the pool
   ID as `pool` and the same installation token. The answer lists the pool's
   oldest queued jobs with their repository and labels and carries an
   `ETag`; send it back as `If-None-Match` with `wait=<seconds>` (up to 50)
   to hold the request until the demand changes. A pool of another
   organization, or one the token cannot read, answers 404.

An app reaches only its installing organization's own pools: never the
GitCafe-hosted pool, another organization's pools or repository-level
runners. Registration tokens and listing or removing runners still need an
organization admin.
