Self-Hosting a Forgejo Runner
March 20, 2024 · 4 min
Codeberg hosts its own Woodpecker CI, but the software Codeberg runs, Forgejo, has recently released its own variation on Github Actions. While you are able to enable actions in Codeberg, they won’t actually run, because Codeberg doesn’t provide any runners (yet?).
Thankfully, it’s relatively easy for one to self-host their own runner. We’ll focus exclusively on hosting a Docker runner, not an LXC or “self-hosted” runner.
The Docker-Compose
Tldr; here’s the docker-compose.yml
file that I use.
1services:
2 docker-in-docker:
3 image: docker:dind
4 privileged: true
5 command: ["dockerd", "-H", "tcp://0.0.0.0:2375", "--tls=false"]
6
7 runner-register:
8 image: code.forgejo.org/forgejo/runner:3.3.0
9 links:
10 - docker-in-docker
11 environment:
12 DOCKER_HOST: tcp://docker-in-docker:2375
13 volumes:
14 - runner-data:/data
15 user: 0:0
16 command: >-
17 bash -ec '
18 if [ -f config.yml ]; then
19 exit 0
20 fi
21
22 forgejo-runner register \
23 --no-interactive \
24 --name skadi-runner \
25 --token $FORGEJO_TOKEN \
26 --instance $FORGEJO_HOST;
27
28 forgejo-runner generate-config > config.yml;
29 chown -R 1000:1000 /data;
30 '
31
32 runner-daemon:
33 image: code.forgejo.org/forgejo/runner:3.3.0
34 restart: unless-stopped
35 environment:
36 DOCKER_HOST: "tcp://docker-in-docker:2375"
37 env_file:
38 - '.env'
39 links:
40 - docker-in-docker
41 depends_on:
42 runner-register:
43 condition: service_completed_successfully
44 volumes:
45 - runner-data:/data
46 command: "forgejo-runner --config config.yml daemon"
47
48volumes:
49 runner-data:
50
This docker-compose.yml
file expects you to have a .env
file similar to the following:
1FORGEJO_HOST=https://codeberg.org
2FORGEJO_TOKEN=<token>
You’ll need to get your Forgejo Runner registration token using this guide here. This token will be the FORGEJO_TOKEN
in the .env
file.
Once you have these two things, starting your runner should be as simple as running docker-compose up -d
.
Now, let’s go through each service individually.
docker-in-docker
1 docker-in-docker:
2 image: docker:dind
3 privileged: true
4 command: ["dockerd", "-H", "tcp://0.0.0.0:2375", "--tls=false"]
This little bit is responsible for actually executing containers. Docker-in-docker creates a child container inside of itself rather than exposing the docker.sock
on our host machine to the Forgejo Runner container. In theory, this is more secure due to us not being forced to change the docker.sock
permissions to 666
. In practice…eh. You already own the machine, host the runner container, and choose what code is ran on it. Your Forgejo Runner can run without listening on any ports, so there’s no reason to expose it to public internet traffic either.
runner-register
1 runner-register:
2 image: code.forgejo.org/forgejo/runner:3.3.0
3 links:
4 - docker-in-docker
5 environment:
6 DOCKER_HOST: tcp://docker-in-docker:2375
7 volumes:
8 - runner-data:/data
9 user: 0:0
10 command: >-
11 bash -ec '
12 if [ -f config.yml ]; then
13 exit 0
14 fi
15
16 forgejo-runner register \
17 --no-interactive \
18 --name skadi-runner \
19 --token $FORGEJO_TOKEN \
20 --instance $FORGEJO_HOST;
21
22 forgejo-runner generate-config > config.yml;
23 chown -R 1000:1000 /data;
24 '
This service is a oneshot that connects to our Forgejo (or Codeberg) instance and registers the runner using the environment variables. Once it’s done that, it will generate a config file that our runner-daemon
can use.
The config file is saved to our runner-data
volume. If this file currently exists, then the runner-register
will skip the registration. Registering the same runner twice will create a duplicate runner on your account on your Forgejo Instance.
runner-daemon
1 runner-daemon:
2 image: code.forgejo.org/forgejo/runner:3.3.0
3 restart: unless-stopped
4 environment:
5 DOCKER_HOST: "tcp://docker-in-docker:2375"
6 env_file:
7 - '.env'
8 links:
9 - docker-in-docker
10 depends_on:
11 runner-register:
12 condition: service_completed_successfully
13 volumes:
14 - runner-data:/data
15 command: "forgejo-runner --config config.yml daemon"
This is the Forgejo Runner itself. Once it detects that runner-register
has successfully completed, it will start up and wait for any actions to be triggered.
Fin.
Overall, I’ve been quite happy with Codeberg. The service is (relatively) stable, quick, and feature-full enough that I don’t miss Github. They are experiencing growing pains, but Codeberg over-communicates about every incident they’ve had, and I much prefer that the alternative. I still self-host my own Forgejo instance for rough projects and my dotfiles, but my public repos are now all on Codeberg.
If you have any questions, feel free to message me on Mastodon!