Setting Up Self-Hosted Forgejo Runners
In an earlier post I mentioned that Codeberg actions was less 'plug and play' than GitHub actions, and it is. But it's not quite as difficult as I'd thought, and regarding setting up self-hosted runners for Codeberg actions went from 'not today' to 'in a few days' pretty quickly.
First, you need to turn on actions in a Codeberg repository, at https://codeberg.org/{username}/{repo}/settings/units
you'll need to select 'Actions' to show the tab in the repo and also configure runners.
Second, hosted runners are now a thing on Codeberg, but the resources are limited and I'd rather host my own, learn a bunch in the process, and let these be used by those who don't have another option, those who don't have the time to do what I've done here, and those who are otherwise doing better things for the world than publishing this blog.
Third, you can run self-hosted Forgejo Runners which are not publicly accessible/don't have a public/static IP address. This means it's much more convenient to do from a home lab scenario.
Fourth, I found a Proxmox helper script which enabled me to get up and running quickly (and subsequently enter a cycle of breaking things, figuring it out and starting again for a day), which gave me sufficient confidence to try and make it work. This is the debrief, and a place to add further notes as they arise.
Proxmox
I run all of my self-hosted services in Proxmox VE, an open-source virtualization platform with a web-based GUI which you can run for free. I use this to enable one machine to serve many purposes flexibly - and I run it from a desktop-format server sitting in my office, making good use of RAM purchased before /ai ruined the personal computing market.
Some of the information below is specific to using Proxmox, but beyond setting up a container to run things in, a lot of it will be transferrable (e.g. if you set this up in a VM, on another virtualization host, or set up the runner to run directly on a host like an old computer you have sitting around which you don't necessarily want to run anything else on).
You can create runners which accept workflows for different scopes (all repositories - at least of a Forgejo instance you run yourself, an organization, all repositories of a your user, or a specific repository), and this comes into play when the runner is registered with Codeberg (or an other Forgejo instance).
Proxmox Helper Scripts
One useful aspect of running services on Proxmox which makes some of it's technical features a little easier to handle is the Proxmox Community Helper Scripts page which provides pre-bakend solutions for getting common (and some less common) services up and running under Proxmox. It doesn't have everything, and it's worth checking what the setup scripts are actually doing, but generally this is a good way to try setting something up, and judge whether you want to put effort into doing it properly/maintaining it.
As of March 2026, there's a Proxmox Helper Script for Forgejo Runner, but it's noted as being 'under development. I have successfully used this to set up a runner, but will also go through the detail of setting it up in a LXC template below (this uses Debian but it could easily be adapted to similar distributions).
Setting Up a Forgejo Runner in a Debian LXC
In Proxmox, I typically use LXC's (Linux containers) to run services in. These provide a simple layer of isolation, but enable flexible usage of resources on my server as i) the kernel is shared and the templates are small, ii) resources allocated to the LXC aren't reserved specifically but instead provide more of a cap on usage (as opposed to fully fledged virtual machines).
Creating a Debian LXC
In Proxmox, creating an LXC from a template involves:
-
Selecting your Proxmox node and clicking 'Create CT' is a blue button on the top right, or you can right-click on your node and select 'Create CT'
-
Selecting a hostname (what your runner will be called, locally and in Codeberg), and adding a password (containers are by-default password protected in Proxmox)
-
Selecting your template (different templates can be stored on different storage volumes).
If the options you see here don't cover Debian (or whatever else you want to use), you'll need to browse to that storage volume in your Proxmox node, select 'CT Templates' from the menu, and browse 'Templates' for what you're after (this is a Proxmox-maintained list of templates). If you're after something not in this list, you can also manually upload your own from an external source.
-
Configuring template resources and options
For this exercise, I used the debian-13 template, with 4 cores and 8GB RAM. Make sure you configure the network to use DHCP/SLAAC unless you're happy to configure static IPs within your local network for this container. Don't run the container after creating it, instead modify the options to turn on nesting and keyctl (will
be needed to run the Docker engine).
After you have an LXC, you'll want to open a console, and we'll get started.
Initial Setup
The first step in this is to quickly update your container, and add some basic dependencies.
apt update
apt upgrade
apt install curl jq gpg ca-certificates
Note I don't use
sudohere as I'm running asrootfor the majority of important tasks within the container, but on your device or in your container you might need it.
Install Docker
The next step in the workflow is to install the Docker engine. We'll follow the Docker instructions on doing
this in a Debian-based system using apt
but I've added the (March 2026) commands for doing this below.
The first step is to remove any existing Docker components:
apt remove $(dpkg --get-selections docker.io docker-compose docker-doc podman-docker containerd runc | cut -f1)
After that we add Docker's GPG key:
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
And from here modify the apt sources to add the Docker repository:
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
Now we can install the Docker packages:
apt update
apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
And check the status of Docker with (Ctrl+C /:q to exit):
systemctl status dockerForgejo Runner Binary
We'll use a binary installation for the Forgejo Runner in our container; I've added the (March 2026) commands for this below.
The first part of this is just selecting the right binary for your system:
export ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
export RUNNER_VERSION=$(curl -X 'GET' https://data.forgejo.org/api/v1/repos/forgejo/runner/releases/latest | jq .name -r | cut -c 2-)
export FORGEJO_URL="https://code.forgejo.org/forgejo/runner/releases/download/v${RUNNER_VERSION}/forgejo-runner-${RUNNER_VERSION}-linux-${ARCH}"
We can then pull the runner binary from this URL, make it executable, and verify the GPG key:
wget -O forgejo-runner ${FORGEJO_URL} || curl -o forgejo-runner ${FORGEJO_URL}
chmod +x forgejo-runner
wget -O forgejo-runner.asc ${FORGEJO_URL}.asc || curl -o forgejo-runner.asc ${FORGEJO_URL}.asc
gpg --keyserver hkps://keys.openpgp.org --recv EB114F5E6C0DC2BCDD183550A4B61A2DC5923710
gpg --verify forgejo-runner.asc forgejo-runner && echo "✓ Verified" || echo "✗ Failed"
From here, we copy the binary into /usr/local/bin (we'll use this later).
cp forgejo-runner /usr/local/bin/forgejo-runner
The Forgejo Runner installation guide also suggests creating a user for the runner;
as we're using Docker they'll need to be added to the docker group too:
useradd --create-home runner
usermod -aG docker runnerRegister the Runner
Now we have a runner, we need to register it with Codeberg
so we can use it in Codeberg Actions. The Forgejo Runner installation instructions
suggest doing this with the runner user we created above, so we'll switch to
that user and switch into their home directory.
su runner
cd ~
At this point, if you want to create a configuration file, it's a convenient time.
You can create it with the forgejo-runner binary and edit it with nano:
forgejo-runner generate-config > config.yml
nano config.yml # edit labels and anything else you want to, maybe docker automount?
The two things I edited here (as of this post) were the runner.labels (I'll use
"rust-latest:docker://rust:latest" for Zola, and I'm sure I can find uses for the others)
and the container.docker_host:
labels: [
"ubuntu-latest:docker://node:20-bookworm",
"ubuntu-22.04:docker://node:20-bookworm",
"python-latest:docker://python:latest",
"rust-latest:docker://rust:latest"
]
...
docker_host: "automount"
At this point, we also need to get a registration token from Codeberg (or
other Forgejo instance) in order to connect to it. Make sure you've
added the 'Actions' unit (at at https://codeberg.org/{username}/{repo}/settings/units,
if you don't have it), and after that you should be able to
find the 'Create new runner' button at https://codeberg.org/{username}/{repo}/settings/actions/runners
which will create a new runner specifically scoped for this repository.
Clicking this button will give you a registration token to copy, which
you'll need below - alongside the instance URL (i.e. https://codeberg.org
for me).
Then we can register the runner using this configuration, this will give you some prompts to reply to:
forgejo-runner register --config config.yml
For my setup, the prompt replies to this were (kept the hostname, already had the labels in config):
> https://codeberg.org
> {registration token}
>
>
Almost there!
Start the Runner, and Keep it Running
You can can run the daemon with your specific configuration as follows:
forgejo-runner daemon --config config.yml
However, to have this start automatically with your container, you can add it as a
systemd service.
You'll need do do this as root (or otherwise give runner sudo access).
Here we change back to root, and open a new file to edit with nano;
the Forgejo Runner installation instructions point to adding/copy-pasting in
this file but
note that I needed to edit line 7 which refers to the configuration (replace /home/runner/runner-config.yml
with /home/runner/config.yml which we created above):
su root
nano /etc/systemd/system/forgejo-runner.service
My file, after the edit to keep it in line with the filenaming above:
[Unit]
Description=Forgejo Runner
Documentation=https://forgejo.org/docs/latest/admin/actions/
After=docker.service
[Service]
ExecStart=/usr/local/bin/forgejo-runner daemon -c /home/runner/config.yml # I needed to edit this line
ExecReload=/bin/kill -s HUP $MAINPID
# This user and working directory must already exist
User=runner
WorkingDirectory=/home/runner
Restart=on-failure
TimeoutSec=0
RestartSec=10
[Install]
WantedBy=multi-user.target
From here we can reload the daemon, start the service to check it's working, and from there enable the systemd service for the Forgejo Runner.
systemctl daemon-reload
systemctl start forgejo-runner.service
systemctl enable forgejo-runner.service