Updates and Notes:

  • 2025-01-09: With the latest versions of the Docker Runner, this setup does not work any more. I wrote about that here

I am using Forgejo as a local Github alternative where I can host my stuff. Until now, I used that in combination with Drone for doing CI/CD tasks. I was never really happy with Drone, but just kept it because I was not motivated to replace it.

Some time ago, Gitea introduced an own CI/CD alternative called Gitea Runners. This functionality is also available for Forgejo, so I decided to switch to this one.

My primary setup

I wanted a quite bare metal installation: Have a system that has as few modifications as possible. For me, thas means: Have a Debian Host with Docker installed on it, and that’s all.

Most guides that follow this setup then use ‘Docker in Docker’ to run tasks. I wanted that differently: I wanted the runner to use the Docker installation directly on the machine. Realizing that this may be a security threat to somebody, I see it the following way: My Git Server is running in my homelab and inaccessible from outside (given that only I and a handful of other people have VPN access). The same holds true for the server that will run the Forgejo Runner. So the security threat is (currently) not too important to me.

So, the infrastructure is simple: Install a Debian machine, install Docker on it. Everything else will run inside Docker.

Setup the runner

After a bit of fiddling around, I have got the following setup, in a docker-compose.yaml file.

Please note some adaptions for later versions here.

services:
    forgejo-runner:
        image: code.forgejo.org/forgejo/runner:3.5.1
        container_name: runner
        environment:
            DOCKER_HOST: unix:///var/run/docker.sock
        user: 0:0
        volumes:
            - ./data:/data
        restart: unless-stopped

        command: forgejo-runner -c /data/config.yml daemon

Differences from the official docs:

  • There is no Docker-in-Docker container. Obviously.
  • The user for the runner is root (user: 0:0). This is required, as otherwise the docker.sock cannot be accessed.
  • Obviously, docker.sock is mapped into the container. Also, the DOCKER_HOST variable is updated accordingly.
  • I fixed the startup command: In the official docs, there is a sleep 5 setting before starting the Forgejo runner. This is for the DinD container to start; I don’t need that in my scenario and hence removed it.

Configuration

  • First, we need to create the mapped directory: mkdir ~/data

  • Next, let the Forgejo Runner generate it’s default configuration with the following command:

    docker compose run --rm forgejo-runner 'forgejo-runner' 'generate-config' > data/config.yaml
    

    Note the quotes - these are not there arbitrary. The container knows the forego-runner executable and this way, we can also pass the sub-command generate-config. If we don’t add the quotes here, Docker searchs for the executable 'forgejo-runner generate-config', which fails.

    This command creates the default configuration. For storing it in a file, the part > data/config.yaml is again executed on the host system - and the configuration file is stored in ~/data/config.yaml.

  • Now open the configuration file. I did the following changes:

    • I played around a bit with fetch_interval and report_interval. This is probably not required and was just out of interest.
    • Define some labels. I defined the following labels:
      • ubuntu-22.04:docker://node:20-bullseye. This is defined as a good approach in the docs.
      • debian-12:docker://registry.hub.docker.com/library/debian:12. This is a test of me. Be aware - with that, it is quite hard to use any predefined actions!
    • Please note the updates for newer versions I described here.

The labels are the actually important part. I will still have to find out what good labels are, and how to actually use them. I will probably keep you posted.

Note that the labels are also not completely static. You can edit them in the config file and restart the runner, then also new labels will be accessible.

Register the runner

This one is surprisingly easy.

  • Go to your forgejo instance (I am using forgejo.tech-tales.blog here) and login.
    • Go to Settings - Actions - Runners. Here, find the “Create new runner” button. When clicking that, you will be presented a registration token. Note that down (or copy it to clipboard), you will need it in a second.
  • Now go back to your Forgejo Runner server. Execute the following command:
    docker compose run --rm -it forgejo-runner 'forgejo-runner' 'register'
    
    • You will be asked for the domain of your Forgejo instance (https://forgejo.tech-tales.blog for me), the token (which you just copied) and a name for the runner (I chose forgejo-docker-runner-0, but this is mostly up to you). Enter all that and you are essentially done!
  • Back on the command line of your Forgejo Runner Host, a new file was created: ~/data/.runner. Check that this one exists - it contains the token that the runner will use to authenticate against your Forgejo instance. Note that this is a different token than your registration token!

Startup the runner!

We are done! Run docker compose up -d and check the logs with docker compose logs -f. If everything went smoothly, you should see three lines about the runner starting up successfully.

Finally, also head over to Forgejo again. In the Settings - Actions - Runners section, you should see your runner now, and you should see that the status is Idle.

You can now start using workflows in your repo. Don’t forget to setup Actions for the repo in the repo Settings under “Repository units - Overview”, and then choose runs-on: ubuntu-22.04 for a workflow!