Self-Hosting a Forgejo Runner for Codeberg Actions | Hammer Blog
Why I Run My Own Codeberg Runner<br>My Codeberg review is still in the pipeline but I wanted to get this out now.<br>For everyone who doesn’t know Codeberg: it’s a GitHub alternative run by the non-profit organization Codeberg e. V. out of Berlin. It’s based on the Gitea fork Forgejo. Just like GitHub, Forgejo has an Actions feature. Codeberg offers runners to use, but since they’re a non-profit and their resources are limited, the only right thing to do is to set up your own Forgejo runner and connect it to Codeberg.<br>In this quick guide I’ll show you how to set up the Forgejo runner with Docker and connect it to Codeberg. I set it up on my VPS, which acts as my development server. But since the runner connects to Codeberg and not the other way around, it’s absolutely possible to deploy the runner on a local machine and only fire it up when you need it.<br>Setting Up the Docker Compose File<br>The official Forgejo documentation lets you run the runners on bare metal or in Docker. But either way you need Docker or Podman, since the runners run in containers. Since I’m team Docker, I chose to run it in Docker. For security reasons the runners shouldn’t get access to the host Docker daemon and instead be given “Docker in Docker”.<br>The Docker Compose file is pretty straightforward. Two containers. The DinD and the Forgejo runner itself:<br>version: '3.8'
services:<br>docker-in-docker:<br>image: docker:dind<br>container_name: 'docker_dind'<br>privileged: 'true'<br>command: ['dockerd', '-H', 'tcp://0.0.0.0:2375', '--tls=false']<br>restart: 'unless-stopped'
runner:<br>image: 'data.forgejo.org/forgejo/runner:12'<br>links:<br>- docker-in-docker<br>depends_on:<br>docker-in-docker:<br>condition: service_started<br>container_name: 'runner'<br>environment:<br>DOCKER_HOST: tcp://docker-in-docker:2375<br># User without root privileges, but with access to `./data`.<br>user: 1001:1001<br>volumes:<br>- ./data:/data<br>restart: 'unless-stopped'<br>command: 'forgejo-runner daemon --config runner-config.yml'
We are not quiet ready to start it. You first need to create the data folder and its permissions, and of course the config file. Choose a directory where you want to place the docker-compose.yml. I like to put my Compose files in the /opt directory, but your home folder is also fine. Then create the data and cache folders and give them non-root permissions:<br>cd /opt/forgejo-runner<br>mkdir -p data/.cache<br>chown -R 1001:1001 data<br>chmod 775 data/.cache<br>chmod g+s data/.cache
Now it’s time to create the default Forgejo runner config. Using Docker, this can be done with this command:<br>sudo sh -c 'docker run --rm data.forgejo.org/forgejo/runner:12 forgejo-runner generate-config > data/runner-config.yml'
Now we’re almost ready to fire up the runner.<br>Getting Your Runner Token from Codeberg<br>Now we need to get our token for the runner from Codeberg. You can set up a runner either for your account, for your organization, or just a single repository. Go to the respective settings -> Actions -> Runners. The Codeberg documentation is outdated here. It tells you to copy the registration token. But that route is deprecated. Instead, hit “Create new runner”, enter the name for your runner and an optional description.<br>Codeberg create new runner dialog<br>You’re then presented with the UUID and Token. These values need to be added to the runner config so the runner can connect to Codeberg. They’re only shown once, so write them down or you’ll have to reset them later.<br>Codeberg runner config block<br>Here, Codeberg hands you the correct config so you don’t have to bother writing it yourself. Which, given my own YAML track record, I’m grateful for.<br>Editing the Config and Starting the Runner<br>Head back to the folder where you created the runner-config.yml and open it with the text editor of your choice. The default config is fine for most users. It’s very well documented. Just scroll through it and you’ll understand it. The only optional change I made is the capacity setting. Default is one job per runner. I set it to 4. And then there are two changes you have to make: add the server config and set the runner labels.<br>In line 63 you’ll find the config for the labels:<br># The labels of a runner are used to determine which jobs the runner can run and how to run them.<br># Like: ["macos-arm64:host", "ubuntu-latest:docker://node:20-bookworm", "ubuntu-22.04:docker://node:20-bookworm"]<br># If it's empty when registering, it will ask for inputting labels.<br># If it's empty when executing the `daemon`, it will use labels in the `.runner` file.<br>labels: ['ubuntu-latest:docker://node:20-bookworm', 'ubuntu-22.04:docker://node:20-bookworm', 'ubuntu-24.04:docker://node:22-bookworm']
These labels tell Codeberg what actions the runner can handle. Think of it as a matchmaking system between your workflows and this runner. Each label follows a strict three-part...