Forgejo runner Ansible role
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-11 10:03:23 +00:00
.forgejo/workflows feat: initial version 2026-10-11 17:02:14 +07:00
defaults feat: initial version 2026-10-11 17:02:14 +07:00
meta feat: initial version 2026-10-11 17:02:14 +07:00
tasks feat: initial version 2026-10-11 17:02:14 +07:00
templates feat: initial version 2026-10-11 17:02:14 +07:00
.gitignore feat: initial version 2026-10-11 17:02:14 +07:00
CHANGELOG.md feat: initial version 2026-10-11 17:02:14 +07:00
LICENSE feat: initial version 2026-10-11 17:02:14 +07:00
README.md Prettified Code! 2026-10-11 10:03:23 +00:00

Forgejo Runner

Deploys Forgejo Actions runners that start from scratch for every job. Each runner has its own Docker-in-Docker daemon, runs exactly one job (forgejo-runner one-job --wait) and is then thrown away together with everything the job left behind: containers, images, build cache, volumes.

How it works

  • One systemd unit per runner, forgejo-runner@<name>. Each start runs docker compose up for two containers: dind (docker:dind, privileged) and runner.
  • The runner waits for a job, runs it inside its own Docker daemon and exits. Compose then stops the daemon, docker compose down --volumes deletes its storage, and systemd starts the next round.
  • Jobs can use Docker (docker build, docker compose, …) through DOCKER_HOST=tcp://dind:2375. Job containers themselves are unprivileged, get a new network per job and can't mount host paths.
  • Runners don't share anything: separate Docker daemons, capacity: 1.
  • The runner config (with the runner secret) is written to forgejo_runner_secret_path (/run, a tmpfs) only. After a reboot it is gone and the units are skipped until this role runs again.
  • Nothing is cached between jobs, so every job pulls its images again.

Requirements

  • Docker with the Compose plugin
  • Each runner registered on the Forgejo side with the same secret, e.g. via the forgejo role's forgejo_runners

Variables

Variable Default Description
forgejo_runner_url ~ Required. Forgejo URL with trailing slash.
forgejo_runner_instances [] Runners on this host. See below.
forgejo_runner_labels ubuntu-latest, docker <label>:docker://<image> entries offered to runs-on.
forgejo_runner_version 13 data.forgejo.org/forgejo/runner image tag.
forgejo_runner_dind_version 28-dind docker image tag of the Docker-in-Docker daemon.
forgejo_runner_extra_hosts {} hostname: IP entries for the runner and every job container, e.g. the Forgejo domain without NAT hairpin.
forgejo_runner_timeout 3h Job timeout.
forgejo_runner_docker_path /docker/forgejo-runner Compose files (no secrets).
forgejo_runner_secret_path /run/forgejo-runner Runner configs with secrets. Must be a tmpfs.
forgejo_runner_uninstall false Stops the runners and removes their files. They stay registered in Forgejo.

forgejo_runner_instances entries:

Key Required Description
name yes Unique on the host, [a-z0-9-].
secret yes 40 lowercase hex characters (openssl rand -hex 20), the same as on the Forgejo side.
labels no Overrides forgejo_runner_labels.
cpus no CPU limit of the runner's Docker daemon, which covers everything a job starts.
memory no Memory limit of the same, Compose syntax (4g).

Usage

# Forgejo host (forgejo role)
forgejo_runners:
  - name: runner-01
    secret: "{{ vault_runner_01_secret }}"

# Runner host (this role)
forgejo_runner_url: https://git.example.com/
forgejo_runner_extra_hosts:
  git.example.com: 10.0.0.2
forgejo_runner_instances:
  - name: runner-01
    secret: "{{ vault_runner_01_secret }}"
    cpus: 2
    memory: 4g

Notes

  • Offline registration: the first 16 characters of the secret identify the runner, its UUID is their hex encoding. Changing only the last 24 characters rotates the secret.
  • Changing a runner's config or compose file restarts it, which cancels a job that is running at that moment.
  • Runners removed from forgejo_runner_instances are not stopped; set forgejo_runner_uninstall or stop the unit by hand.
  • The built-in Actions cache is off (it would keep data between jobs).
  • The dind container is privileged. Jobs can't reach it directly, but run runners on a dedicated VM anyway.
  • Jobs can reach whatever the runner host can reach on the network. Restrict that with the firewall if needed.