Skip to main content
Featured image for post: Setup Forgejo Runner for Codeberg

Setup Forgejo Runner for Codeberg

3 min539 words

This stack runs a Forgejo Actions runner (data.forgejo.org/forgejo/runner:12) together with a docker:dind sidecar, so job containers get their own isolated Docker daemon (DOCKER_HOST=tcp://docker-in-docker:2375).

How it’s wired up

  • compose.yml — two services:
  • docker-in-docker: privileged docker:dind, exposes the Docker API on tcp://0.0.0.0:2375 (no TLS, only reachable from inside the compose network).
  • runner: the Forgejo runner daemon, runs as uid/gid 1001:1001, mounts ./data to /data, and starts with forgejo-runner daemon --config runner-config.yml.
  • data/runner-config.yml — the runner’s config file. Registration is done declaratively in this file under server.connections, not via the older forgejo-runner register interactive command. Each entry under connections is one Forgejo/Codeberg instance (or scope) this runner logs into.
  • data/ is git-ignored (see .gitignore) because it holds the registration token — never commit it.

Setup the Runner Service

To setup the runner you can use this compose.yml file:

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

  runner:
    image: 'data.forgejo.org/forgejo/runner:12'
    links:
      - docker-in-docker
    depends_on:
    docker-in-docker:
      condition: service_started
    container_name: 'runner'
    environment:
      DOCKER_HOST: tcp://docker-in-docker:2375
    # User without root privileges, but with access to `./data`.
    user: 1001:1001
    volumes:
      - ./data:/data
    restart: 'unless-stopped'
    command: 'forgejo-runner daemon --config runner-config.yml'

Registering the runner with Codeberg

1. Enable Actions on the repo (or org)

Actions are off by default on Codeberg. For a single repository:

  1. Go to the repo → Settings → Units/Overview.
  2. Enable Actions.

(For an organization-wide runner, this isn’t needed per-repo — see scope note below.)

2. Get a registration token from Codeberg

Runners can be registered at four levels, and a repo also picks up runners registered at any broader level that owns it:

ScopeWhere
Instance-wide (admin only)/admin/actions/runners
Organization/org/<org>/settings/actions/runners
User/user/settings/actions/runners
Single repository/<owner>/<repo>/settings/actions/runners

Pick the narrowest scope that covers what you need. Steps:

  1. Open the relevant Actions → Runners page.
  2. Click Create new runner.
  3. Give it a name/description.
  4. Codeberg shows a UUID and a Token (shown once — copy both immediately).

3. Add the connection to data/runner-config.yml

Under server.connections, add or edit an entry:

server:
  connections:
  codeberg: # arbitrary name for this connection
    url: https://codeberg.org/
    uuid: <uuid-from-step-2>
    token: <token-from-step-2>
Note

4. Restart the runner

docker compose up -d --force-recreate runner

On startup the runner reads server.connections, registers/authenticates each one, and starts polling for jobs. Registration state per connection is tracked internally — you don’t run a separate register step.

5. Verify

  • Check logs: docker compose logs -f runner — look for a successful connection message per instance.
  • On Codeberg, the runner should show as online on the same Actions → Runners page used to create it.
  • Push a commit with a workflow under .forgejo/workflows/ in the target repo and confirm a job gets picked up.

Labels / job containers

runner.labels in runner-config.yml maps a label used in a workflow’s runs-on: to how the job container is built, e.g.:

labels:
  - "ubuntu-latest:docker://node:lts-bookworm"
  - "ubuntu-22.04:docker://node:22-bookworm"

Since the runner talks to the docker-in-docker sidecar (via DOCKER_HOST), job containers are spun up there rather than on the host Docker daemon.

Rotating/revoking access

If a token in data/runner-config.yml is ever exposed, revoke it immediately from the same Actions → Runners page on Codeberg (delete the runner entry) and generate a fresh one, then repeat step 3.