Forgejo Actions - Runners container setup

Hi there!

Today, I want to share my experience enabling Actions on my self-hosted Forgejo instance at home. My goal was to create a GitHub Actions-like environment locally so I could run CI pipelines even when offline.

As I mentioned in my second post , one of the main reasons I run a homelab is to stay independent from Big Tech as much as possible. I strongly believe in the original vision of a decentralized internet. Unfortunately, the modern web has become increasingly centralized, controlled by a handful of companies, and often locked behind subscription services. To maintain as much digital independence as possible, hosting the services you care about is key.

Now, back to the technical part. In this post, I’ll show you how to enable Forgejo Actions in a resource-constrained environment using Docker-in-Docker, without relying on virtual machines.


Preparation steps

First, create the directory structure that the runner will use. According to the Forgejo documentation, the runner container runs under a non-root user (uid=1000 gid=1000 groups=1000), so we need to create directories and apply the correct permissions.

BASH
mkdir -p <your desired path>/forgejo-runner/data/.cache
cd  <your desired path>/forgejo-runner/
chown -R 1001:1001 data
chmod 775 data/.cache
chmod g+s data/.cache
Click to expand and view more

Next, define a Docker Compose file that creates the runner container using Docker-in-Docker (DinD):

YAML
version: '3.8'
    
services:
  forgejo-docker-in-docker:
    image: docker:dind
    container_name: 'forgejo-docker-in-docker'
    privileged: true
    command: ['dockerd', '-H', 'tcp://0.0.0.0:2375', '--tls=false']
    restart: 'unless-stopped'

  forgejo-runner:
    image: 'data.forgejo.org/forgejo/runner:11'
    links:
      - forgejo-docker-in-docker
    depends_on:
      forgejo-docker-in-docker:
        condition: service_started
    container_name: 'forgejo-runner'
    environment:
      DOCKER_HOST: tcp://forgejo-docker-in-docker:2375
    # User without root privileges, but with access to `./data`.
    user: 1001:1001
    volumes:
      - ./<your desired path>/forgejo-runner/data/:/data
    restart: 'unless-stopped'
    command: '/bin/sh -c "while : ; do sleep 1 ; done ;"'
Click to expand and view more

For now, the runner’s command simply keeps the container running. Start the containers with:

BASH
docker compose up -d
Click to expand and view more

Once both containers are running, proceed with the following steps:

  1. Register the runner in Forgejo.
  2. Stop the Docker Compose setup to update the runner’s command.
  3. Restart the containers.

Register the runner

To register the runner in your Forgejo instance, open a shell inside the runner container and run:

BASH
docker exec -it forgejo-runner forgejo-runner register
Click to expand and view more

Follow the registration prompts:

Example:

BASH
homeserver@homeserver:~$ docker exec -it forgejo-runner forgejo-runner register
INFO Registering runner, arch=amd64, os=linux, version=v11.1.2. 
WARN Runner in user-mode.                         
INFO Enter the Forgejo instance URL (for example, https://next.forgejo.org/): 
https://git.example.com
INFO Enter the runner token:                      
AAAAVVVXXZZZFDGFDGFfgdfgdfhgsr5464675678
INFO Enter the runner name (if set empty, use hostname: 895af1b0c8c8): 
unraid-runner
INFO Enter the runner labels, leave blank to use the default labels (comma-separated, for example, ubuntu-20.04:docker://node:20-bookworm,ubuntu-18.04:docker://node:20-bookworm): 

INFO Registering runner, name=unraid-runner, instance=https://git.example.com, labels=[docker:docker://data.forgejo.org/oci/node:20-bullseye]. 
DEBU Successfully pinged the Forgejo instance server 
INFO Runner registered successfully.  
Click to expand and view more

After registration, you should see the runner listed in the Forgejo web UI:

Registered runners page

At this point, the runner is registered but still offline. Let’s bring it online.


Update the runner command in the Compose file

As mentioned earlier, we need to update the runner container’s command. Edit your Docker Compose file and replace the last line:

YAML
command: '/bin/sh -c "while : ; do sleep 1 ; done ;"'
Click to expand and view more

with:

YAML
command: '/bin/sh -c "sleep 5; forgejo-runner daemon"'
Click to expand and view more

The short delay ensures the Docker-in-Docker service is ready before the runner daemon starts. Then restart the containers:

BASH
docker compose down
docker compose up -d
Click to expand and view more

The runner status should now show as Idle and appear green:

Runner up and idle


Test the setup

Now let’s verify the setup with a simple workflow. In one of your repositories, create a workflows directory:

BASH
cd <my git repo>
mkdir -p .forgejo/workflows/
touch .forgejo/workflows/demo-pipeline.yaml
Click to expand and view more

Paste the following into .forgejo/workflows/demo-pipeline.yaml:

YAML
on: [push]
jobs:
  demo-pipeline:
    runs-on: docker # this is the label we have on the runner by default
    steps:
      - uses: https://code.forgejo.org/actions/checkout@v4
      - run: echo Hello World! This is a test
Click to expand and view more

Commit and push the changes. You should see the Action start running in the Forgejo web UI. After a short while, it should complete successfully:

Demo CI Pipeline

Great work!

You now have a self-hosted Forgejo instance capable of running Actions and CI/CD pipelines. From here, you could expand your setup with security scans (for example, using Trivy) or automate dependency updates with tools like Renovate (like dependabot does in GitHub), but those topics may be covered in future.

Enjoy your new self-hosted CI environment. See you!

Start searching

Enter keywords to search articles

↑↓
ESC
⌘K Shortcut