Forgejo Runner
The runner that executes .forgejo/workflows/ —
the standby copy of the GitHub Actions pipelines. It lives on the same ubuntu-vm
host as github-runner, alongside it rather than instead of it, so either forge can
build at any time.
How it differs from github-runner
Section titled “How it differs from github-runner”github-runner runs jobs directly inside its own container. Forgejo Actions instead
starts a fresh container per job from an image named in the runner’s label map,
so the toolchain lives in a separate image — builder/Dockerfile, published to
Harbor as toolsera/forgejo-builder — and the runner container only brokers work.
That image pins the same fpm, yq and bws versions as github-runner, so a
.deb built here matches one built on GitHub Actions.
github-runner | forgejo-runner | |
|---|---|---|
| Job execution | in the runner container | one container per job |
| Toolchain | baked into the runner image | toolsera/forgejo-builder |
| Docker access | host socket | host socket, passed through to job containers |
| CA trust | ~/.step/certs/root_ca.crt bind mount | host CA bundle bind mount |
Deploying
Section titled “Deploying”Everything below runs on the ubuntu-vm host, from the repo checkout.
1. Build and publish the job image
Section titled “1. Build and publish the job image”Chicken-and-egg: the image that runs the workflows cannot be built by those workflows the first time. Build it by hand once —
cd docker-compose-apps/ubuntu-vm/apps/forgejo-runner/builderdocker build -t harbor.toolsera.lan/toolsera/forgejo-builder:latest .docker push harbor.toolsera.lan/toolsera/forgejo-builder:latest— and after that let CI own it, by pushing a tag:
git tag image/forgejo-builder-0.1.0 && git push origin image/forgejo-builder-0.1.0Both publish-docker-image.yml workflows carry forgejo-builder in their build
context lookup tables, so the tag builds it from either forge.
2. Declare the runner
Section titled “2. Declare the runner”Forgejo 15 does not issue legacy registration tokens, and forgejo-runner register
is deprecated — it fails here with invalid_argument: runner registration token not found. The runner is declared in config.yml instead, per
the Forgejo registration docs.
In the Forgejo UI, open the Runners page for the scope you want and use Create new runner:
| Scope | Path | Runs workflows from |
|---|---|---|
| Instance | /admin/actions/runners | every repository |
| Organisation | /org/{org}/settings/actions/runners | that org |
| User | /user/settings/actions/runners | that user’s repos |
| Repository | /{owner}/{repo}/settings/actions/runners | one repository |
It shows a UUID and a token. They go to two different places:
- the UUID is an identifier, not a secret — it lives in
config.ymlunderserver.connections.toolsera.uuid, committed to git; - the token is confidential and never enters the repo. Write it to
data/token.txt, whichconfig.ymlreads throughtoken_url.data/is gitignored.
cd /root/infrastructure-toolkit/docker-compose-apps/ubuntu-vm/apps/forgejo-runnermkdir -p data
# No trailing newline, no shell history: the file must hold the token and nothing else.read -rs TOKEN # paste the token, press Enterprintf '%s' "$TOKEN" > data/token.txtchmod 600 data/token.txtunset TOKEN
wc -c data/token.txt # sanity check: non-zero, and no stray newlineThen put the UUID into config.yml in the repo, commit, push, and
git pull on the host — not by editing the file in place, which the next pull would
conflict with.
There is no .runner file in this flow; a declared connection does not register, so
there is no registration state to lose.
3. Start it
Section titled “3. Start it”cd docker-compose-apps/ubuntu-vmdocker compose up -d forgejo-runnerdocker compose logs -f forgejo-runnerA healthy runner logs runner: ... , with ... labels and then polls quietly. It
should show as Idle under the repo’s Actions → Runners page.
Configuration
Section titled “Configuration”config.yml is mounted read-only and is the only place to change runner behaviour.
The two settings worth understanding:
runner.labelsmaps aruns-on:value to the image the job runs in. All three workflows useruns-on: deb-builder;homelabandubuntu-latestare mapped to the same image so a job pasted over from.github/workflows/still finds a runner.container.optionsbind-mounts the host CA bundle over each job container’s. The host already trusts the Step CA (thestep_ca_clientAnsible role installs it), and without this a job cannot even clone fromforgejo.toolsera.lan. Node keeps its own trust store and ignores the bundle, which is why every workflow setsNODE_EXTRA_CA_CERTS.
container.docker_host: "" leaves the host Docker socket mounted into job
containers — that is what lets publish-docker-image.yml run buildx — so a job
on this runner has root-equivalent access to the host daemon. Only run workflows
from branches and tags you trust.
Troubleshooting
Section titled “Troubleshooting”Failures live in one place, by symptom:
.forgejo/TROUBLESHOOTING.md. It covers
the ones that land here — jobs stuck in “Waiting”, x509: certificate signed by unknown authority, registration token not found, docker: command not found
inside a job — along with the forge-side problems that look like runner problems.