
Homelab 8: your own code forge, where nothing reaches main without a green check
Forgejo is an open-source code forge, a self-hosted GitHub: repositories, pull requests, issues, and a CI (Forgejo Actions) that speaks the same workflow format. At home it becomes more than a backup of your code: it is the place where AI agents propose changes, and where a machine, not good will, decides what reaches main.
1. The forge
Forgejo runs as a container next to a PostgreSQL database. Follow the official Docker installation page, pin the version, and publish the web interface through your HTTPS proxy (Homelab 2).
Git over SSH needs its own port, separate from the web. The HTTPS proxy only carries the web interface: use HTTPS for remote clones, or reach the SSH port through your private network (Homelab 6). Do not open it to the internet.
2. The runner
The CI jobs run on a runner, a separate program, ideally on another VM, which starts each job in a fresh container. Create it in Site administration → Actions → Runners → Create new runner, then follow the registration page of the documentation for your version.
The runner announces labels; a workflow asks for one with runs-on. In the runner's configuration file, under runner → labels, map a label to an image:
ubuntu-latest:docker://node:lts
3. The rule that changes everything
In the repository settings, protect main: no direct push, and the CI check is required before any merge. From then on, a human or an AI agent proposes a change through a pull request; it merges when the tests pass, and only then. Every repository on our forge is born from a template that arms this protection at creation, so none can exist without it.
Traps we paid for
A job whose runs-on matches no label waits forever, without an error. If a run stays "waiting", compare its label with the runner's first.
Do not run jobs in a slim image: JavaScript actions such as checkout need node inside the job container, and the job dies before its first step.
Reference actions by their full URL, for example https://data.forgejo.org/actions/checkout@v4: short GitHub-style names are not resolved the same way.
When CI is red, read the forge's log before reproducing anything. Once, a local reproduction came out green on genuinely broken code; the log named the four failing assertions at once.
A green CI proves nothing if the tests never execute the code. Break the code on purpose (a mutation) and check that a test turns red. We merged a request header that was built and never sent, under a green CI.
Sources
Found this useful? Tell us what to measure next.