freedzhost.cloud

Your own CI on Forgejo: the runner traps, one image for everything, and tests that cannot lie

Level IntermediateEstimated time 1 hRead first Homelab 8: your own code forge, where nothing reaches main without a green checkA word you don't know? Glossary

Running your own forge means running your own CI: every pull request tested on your hardware, no minutes to buy, nothing leaving the house. Forgejo Actions reads the same workflow files as GitHub Actions, almost. The "almost" is where the evenings go.

How these figures were obtained. Measured on our own machines (see the protocol), usually a single run, with speeds as reported by the server itself; we do not publish raw logs. Treat them as orders of magnitude on this hardware, not as guarantees on yours. Figures from outside are labelled with their source.

What you get. The three traps that block a first workflow, a runner image that holds every tool so jobs stop reinstalling them (one job spent 1 min 24 of its 1 min 30 reinstalling tools), and four habits that keep a green CI honest across about forty repositories here.

A bit of history

Forgejo descends from Gogs, a self-hosted Git service written in Go (2014), forked as Gitea in 2016 by its community. When Gitea's governance moved to a company in 2022, the non-profit Codeberg started Forgejo as a community fork, and made it fully independent in 2024. Its CI, Forgejo Actions, deliberately reads the workflow format popularised by GitHub Actions (2018), so most existing workflows run with small changes. The runner is a separate program: you decide which machine runs your jobs, and what tools it carries.

1. Three traps of a first workflow

  1. The runner only answers its labels. Ours announces ubuntu-latest. Any other runs-on: leaves the job waiting forever, with no error: nobody claims it.
  2. No slim image as job container. JavaScript actions such as checkout run with node inside the job's container. A python:slim image has no node: the job dies with exit code 127 before its first step.
  3. Write actions with their full URL: https://data.forgejo.org/actions/checkout@v4. Short names are not resolved to GitHub.

2. One runner image for every repository

Each workflow used to install pip, ruff, pytest, uv, node and friends at every run: 1 min 24 of a 1 min 30 job. We built one image that carries them all, and pointed the runner's label at it.

3. Four habits that keep green honest

Traps

A commit message written with a here-document on the same line as other commands takes those commands as its text. Keep git commit -F - <<'MSG' alone in its command, read the subject back with git log -1 --format=%s, and only then push.

Some API endpoints for runs return empty lists or 404 on certain versions, which looks like "no runner". Trust the job log and the runner's own status, not an empty list.

Sources

Found this useful? Tell us what to measure next.