freedzhost.cloud

Homelab 8: your own code forge, where nothing reaches main without a green check

Level IntermediateEstimated time 2 hRead first Homelab 2: one HTTPS entrance for all your servicesA word you don't know? Glossary

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.

You or an agentworks on a branchPull requestthe proposed changeRunnerlabel ubuntu-latestJob containerimage with nodemainprotectedpicks the job by labelrequired check: greendirect push refused
Nobody, human or agent, pushes to main. A change arrives as a pull request; the runner picks its job by label and runs the tests; the protected branch accepts the merge only when the required check is green.

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.