Bootstrap script + compose + config checked in under ops/forgejo-runner/ so a second runner is a scripted setup. runner-setup.md corrects the register label format (<name>:docker://<image>, not bare names) and documents the Ubuntu systemd-resolved DNS gotcha. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4.6 KiB
Forgejo Runner Setup
How to stand up a forgejo-runner so the CI workflow in .forgejo/workflows/ci.yml actually executes on every push.
The runner is a long-running daemon that polls the Forgejo instance for queued jobs and runs them in Docker containers.
A ready-to-use bootstrap script and compose file live under ops/forgejo-runner/.
Choosing a host
| Option | Good for | Trade-off |
|---|---|---|
| Dedicated VPS | Production-ish CI that runs even when you're offline | Costs a few €/month; one more machine to maintain |
| Home server / NAS | Free; plenty of capacity | CI blocked if home network / power drops |
| Local dev machine | Quick to set up, fast runs | CI only works while the machine is on |
Recommendation for now: home server or a cheap VPS. Don't use a laptop that suspends.
Install
Pick either the binary or the Docker container path. Docker is easier to upgrade.
Path A: Docker Compose (recommended)
Copy ops/forgejo-runner/compose.yml and ops/forgejo-runner/config.yml from this repo to the host, e.g. into ~/forgejo-runner/ (compose file) and ~/forgejo-runner/data/ (config file). The runner talks to a sidecar Docker-in-Docker container via tcp://docker-in-docker:2375, so the host's own Docker socket is not exposed to jobs.
If the host is a fresh Ubuntu VM, run ops/forgejo-runner/bootstrap.sh first to install Docker Engine + the Compose plugin from the official repo.
Path B: Binary
Download the latest release from https://code.forgejo.org/forgejo/runner/releases and drop it somewhere in $PATH:
wget https://code.forgejo.org/forgejo/runner/releases/download/v6.0.0/forgejo-runner-6.0.0-linux-amd64
chmod +x forgejo-runner-6.0.0-linux-amd64
sudo mv forgejo-runner-6.0.0-linux-amd64 /usr/local/bin/forgejo-runner
Register
-
In the Forgejo web UI: go to Site Administration → Actions → Runners → Create new Runner. Copy the registration token. (For a repo-scoped runner instead, use Repo Settings → Actions → Runners.)
-
Register from the runner host by running the registration inside a one-shot container so the output lands in the mounted
data/directory:cd ~/forgejo-runner docker run --rm -v "$PWD/data:/data" code.forgejo.org/forgejo/runner:6 \ forgejo-runner register \ --instance https://forgejo.sourcegate.online \ --token <TOKEN> \ --name forge-runner-01 \ --labels 'docker:docker://catthehacker/ubuntu:act-latest,ubuntu-latest:docker://catthehacker/ubuntu:act-latest,self-hosted:docker://catthehacker/ubuntu:act-latest' \ --no-interactiveLabels must use the
<name>:docker://<image>form — bare labels (ubuntu-latest) get stored asubuntu-latest:host, which tells the runner to execute jobs directly inside the runner container (no Python, no git, nothing).catthehacker/ubuntu:act-latestis the common drop-in image with GitHub Actions tooling preinstalled. -
Start the daemon:
docker compose up -d. -
Verify the runner shows up as Idle in Forgejo's admin Runners page and the log prints
runner: forge-runner-01, ..., declared successfully.
First CI run
Push any commit; the Actions tab on the repo should show the workflow running. If nothing happens:
- Confirm the runner is online (Forgejo admin → Actions → Runners).
- Check the workflow has labels that match the runner (
runs-on: ubuntu-latestneeds a runner registered with that label). - Check the runner logs:
docker logs forgejo-runneror the systemd journal.
Systemd unit (for the binary path)
[Unit]
Description=Forgejo Actions Runner
After=docker.service
Requires=docker.service
[Service]
ExecStart=/usr/local/bin/forgejo-runner daemon
WorkingDirectory=/var/lib/forgejo-runner
User=forgejo-runner
Restart=on-failure
[Install]
WantedBy=multi-user.target
Save as /etc/systemd/system/forgejo-runner.service, then sudo systemctl enable --now forgejo-runner.
Security notes
- Jobs run inside a Docker-in-Docker sidecar, not against the host's Docker socket. Still, DinD runs privileged — give the runner its own VM, not a shared host.
- Registration tokens are one-shot; a stolen token can't re-register after the runner is live.
- Prefer repo-scoped runners over instance-wide if you're sharing the runner with other repos you don't control.
- Ubuntu's default systemd-resolved makes the host's stub resolver (
127.0.0.53) inherit a LAN DNS server that Docker containers may not be able to reach. If container DNS fails, set explicit upstream DNS in/etc/docker/daemon.json(e.g.{"dns": ["1.1.1.1", "8.8.8.8"]}) andsudo systemctl restart docker.