# CI runners

Both plans include hosted CI runners. Solo includes 200 minutes a month; Crew includes 1,000 minutes per seat, pooled. Top-ups add minutes to either plan. Jobs with `runs-on: ubuntu-latest` route to the hosted runners automatically. To add your first workflow and watch it run, follow [Set up CI](/docs/set-up-ci/). This page is the reference: sizes, image contents, compatibility, and bringing your own hardware.

## Runner sizes

Hosted runners run on Scaleway instances in the EU (Paris, `fr-par`). Pick the size with the `runs-on` label:

| Label | Spec | Cost factor |
| --- | --- | --- |
| `codebahn-small` | 3 vCPU, 4 GB | 1x |
| `codebahn-medium` | 4 vCPU, 8 GB | 2x |
| `codebahn-large` | 4 vCPU, 12 GB | 3x |

`ubuntu-latest` is an alias for `codebahn-small`.

`codebahn-small` gives each job 3 GB of usable memory. That covers most Go, Node, and Python builds. For memory-heavy workloads (large Next.js/Webpack builds, Rust compilation, Java/Gradle), use `codebahn-medium` (7 GB) or `codebahn-large` (11 GB). If a job is killed without a clear error, it likely hit the memory limit.

:::tip[Migrating from GitHub?]
GitHub's standard runner (`ubuntu-latest`) provides 8 GB of RAM on private repos. If your builds worked on GitHub but fail on Codebahn's default runner, switch to `codebahn-medium` for comparable memory. This costs 2x compute minutes per job.
:::

Larger runners consume compute minutes faster according to their cost factor. A 5-minute job on `codebahn-medium` (2x) uses 10 compute minutes.

Each organization gets its own ephemeral VM, destroyed when idle. Jobs from different organizations never share one. A cold start takes about 40 seconds from push to a running job. A second job landing on a warm runner starts in about 2 seconds, and Docker images and volumes from your earlier jobs are still there. Each job's wall-clock time is rounded up to a whole minute, then multiplied by the cost factor. Compute minutes are hard-capped per plan and toppable; see [Set up CI](/docs/set-up-ci/#how-minutes-are-counted) for how counting and top-ups work, and [Limits](/docs/limits/) for the caps.

## What the image ships

The image follows the GitHub Actions runner layout. It carries common build tools (git, curl, `build-essential`, the Docker CLI) and a system Python, plus pre-cached Go and Node toolchains in the GitHub-style toolcache so `actions/setup-go` and `actions/setup-node` resolve offline and instantly.

| Toolchain | Pre-cached major versions |
| --- | --- |
| Go | 1.25, 1.26 |
| Node | 22, 24, 26 |

```yaml
steps:
  - uses: actions/checkout@v7
  - uses: actions/setup-go@v5
    with:
      go-version: "1.26"
  - run: go build ./...
```

Other Go and Node versions still work; `setup-go` and `setup-node` fetch them at job time.

Not pre-installed: cloud provider CLIs, browsers, databases, and other language runtimes. If you need one, install it in your workflow:

```yaml
  - run: sudo apt-get update && sudo apt-get install -y sqlite3
  - uses: actions/setup-python@v5
    with:
      python-version: "3.13"
```

Runners reach package registries and download hosts over HTTPS and DNS only. Other egress is blocked: SSH-based git fetches, services on non-standard ports, and the cloud metadata endpoint (`169.254.0.0/16`). Fetch private dependencies over HTTPS.

## CI tokens

Every job receives three built-in tokens as environment variables:

| Variable | Identity | Scope |
| --- | --- | --- |
| `CODEBAHN_TOKEN` / `FORGEJO_TOKEN` / `GITEA_TOKEN` | Actions (system) | The running repo. Cannot trigger new CI runs. |
| `CODEBAHN_BOT_TOKEN` | `codebahn-bot` user | The running repo. Can trigger CI runs. |

`CODEBAHN_BOT_TOKEN` is a short-lived personal access token issued to a dedicated bot user. Use it when a CI job needs to act as a real user, for example when Renovate opens a pull request and expects CI to run on it (the built-in Actions token cannot trigger workflows). The token is scoped to the repository that owns the job, expires after the job timeout, and is not injected for fork pull requests.

To use it, the `codebahn-bot` user must be a collaborator on your repository. Codebahn adds it automatically for new repos.

## Compatibility

Codebahn runs Forgejo Actions, which uses the GitHub Actions workflow syntax. Your `.github/workflows/` directory works without renaming, and `actions/*` resolve from GitHub by default. Common build, test, and lint workflows typically need no changes. The most common actions are also [mirrored locally](/docs/action-mirrors/); you can opt in per step to keep CI traffic in the EU.

Some things work differently or do not run:

- **macOS and Windows jobs.** Hosted runners are Linux only. You can [bring your own runner](#bring-your-own-runner) on macOS or Windows using host execution mode, but there are no official forgejo-runner binaries for those platforms (you will need a [community build](https://code.forgejo.org/windows/runner) or build from source), and host mode runs steps directly on the machine with no container isolation.
- **Job-level `permissions` key.** Forgejo Actions does not scope tokens based on `permissions:` blocks. Workflows that rely on fine-grained token permissions need a different approach.
- **OIDC token federation, GitHub code scanning, and GitHub Pages.** These depend on GitHub-only services.

`.gitlab-ci.yml` files are not read. A new commit to the same branch auto-cancels in-progress runs on that branch; override with a `concurrency` block if you need runs to finish.

For the workflow syntax itself, see the [Forgejo Actions reference](https://forgejo.org/docs/latest/user/actions/).

## Bring your own runner

Register your own hardware alongside the hosted runners. Jobs route to it by exact label match. Your machine needs Docker and the [forgejo-runner binary](https://code.forgejo.org/forgejo/runner/releases).

### 1. Get a registration token

In your org, go to **Settings > Actions > Runners** and generate a registration token.

:::caution
The registration token is single-use. It is revoked after the first runner registers with it. Generate a fresh token for each runner you add.
:::

### 2. Create a config file

Create `config.yml` with your connection details and a distinct label:

```yaml
server:
  connections:
    codebahn:
      url: https://codebahn.net/
      uuid: YOUR_UUID
      token: YOUR_REGISTRATION_TOKEN

runner:
  capacity: 4
  timeout: 3h
  labels:
    - my-runner:docker://rg.fr-par.scw.cloud/codebahn-runner/ubuntu:24.04

container:
  docker_host: unix:///var/run/docker.sock
```

The Codebahn runner image (`rg.fr-par.scw.cloud/codebahn-runner/ubuntu:24.04`) gives you the same environment as the hosted runners, including the pre-cached Go and Node toolchains. Substitute any Docker image you prefer.

### 3. Start the runner

```bash
forgejo-runner daemon -c config.yml
```

For production, run this under systemd or an equivalent supervisor. The runner polls for jobs and executes them in Docker containers.

### 4. Route jobs to your runner

Use the label from your config in the workflow:

```yaml
jobs:
  build:
    runs-on: my-runner
```

`ubuntu-latest`, `codebahn-small`, `codebahn-medium`, and `codebahn-large` go to the hosted runners. A distinct label keeps your own hardware separate. Reserved labels are rejected at registration and on every daemon start.

Jobs on your own runners are not metered and never count against compute minutes or top-ups. Running CI on your own runners still requires an active plan.

For BYO runner setup and external CI alternatives, see [Run your own CI](/docs/run-your-own-ci/).
