Zum Inhalt springen
Serverküche
Suche

Die Suche wird geladen … (nur in der veröffentlichten Seite verfügbar).

Anwendungen Schwierigkeit: Profi

Forgejo Actions: eigener CI/CD-Runner mit Docker

Einen Forgejo-Actions-Runner mit Docker-in-Docker aufsetzen und registrieren: eigene CI/CD-Pipelines auf dem selbstgehosteten Git-Server – Schritt für Schritt.

· 8 Min. Lesezeit ·Dauer: ca. 60 Minuten
Inhaltsverzeichnis

Dein Forgejo-Git-Server läuft – aber Code liegt nur da herum, solange ihn niemand testet und ausrollt. Forgejo Actions bringt CI/CD direkt in deine Git-Plattform: Bei jedem Push laufen automatisch Tests, Builds oder Deployments. Die Arbeit erledigt ein Runner, den du selbst betreibst. Dieses Tutorial setzt einen solchen Runner mit Docker-in-Docker auf und lässt eine erste Pipeline grün durchlaufen.

Was bauen wir?

Am Ende hat dein Forgejo einen registrierten, aktiven Runner, der Workflows aus dem Verzeichnis .forgejo/workflows/ ausführt. Wir setzen dabei auf Docker-in-Docker (DinD): Der Runner startet jeden CI-Job in einem eigenen, wegwerfbaren Container, sauber isoliert vom Host. Konkret läuft am Ende:

  • der Forgejo-Runner (code.forgejo.org/forgejo/runner:13.0.0), der bei Forgejo nach Jobs fragt,
  • ein Docker-in-Docker-Sidecar (docker:29-dind), in dem die Jobs isoliert laufen,
  • ein Beispiel-Repository mit einem Workflow, der bei jedem Push actions/checkout ausführt und eine kleine Aktion startet.

Forgejo Actions ist weitgehend kompatibel zu GitHub Actions – dieselbe Workflow-Syntax, viele Marketplace-Actions funktionieren unverändert. Du kannst also bestehendes Wissen direkt weiternutzen, nur eben auf deinem eigenen Server. Getestet mit Forgejo 16.0.1 und Runner v13.0.0 auf Debian 13 / Docker 29.

Voraussetzungen

  • Ein laufender Forgejo-Server hinter einem Reverse Proxy, erreichbar unter einer öffentlichen HTTPS-Domain (DEINE_DOMAIN). Die öffentliche URL ist wichtig – dazu unten mehr.
  • Docker auf demselben Server (der Runner und sein DinD-Sidecar laufen als Container).
  • Admin-Zugang zu Forgejo, um den Registrierungstoken zu erzeugen.

Actions ist seit Forgejo 1.21 standardmäßig aktiviert. Falls du es in deiner Forgejo-Compose explizit gesetzt hast (empfohlen), steht dort:

YAML
    environment:
      FORGEJO__actions__ENABLED: "true"

CI-Jobs sind ressourcenhungriger als der reine Git-Server – Builds brauchen CPU und RAM. Für den Runner-Betrieb neben Forgejo empfehlen wir daher etwas mehr Reserve; wie viel dein konkretes Setup braucht, schätzt der Server-Rechner.

🍳 Empfehlung Anzeige

VPS 2000 G12

8 vCore · 16 GB RAM · 512 GB NVMe

ab 19,24 €/Monat

Forgejo plus Runner und Build-Jobs profitieren vom größeren Tarif.

Zu netcup →

💶 5 € Gutschein für netcup-Neukunden:36nc17844976032 (nur Neukunden, keine Domains)

Schritt für Schritt

Schritt 1: Registrierungstoken holen

Der Runner muss sich einmalig bei Forgejo anmelden. Dafür brauchst du einen Registrierungstoken. Am einfachsten holst du ihn über die Weboberfläche: Melde dich als Administrator an und geh auf Administrator-Einstellungen → Actions → Runner. Dort siehst du alle Runner und oben rechts den Knopf Registrierungstoken anzeigen.

Die Runner-Verwaltung in den Administrator-Einstellungen von Forgejo mit dem registrierten Runner.
Site-Administration → Actions → Runner: hier holst du den Token und siehst später den Runner-Status.

Alternativ per Kommandozeile direkt im Forgejo-Container:

Terminal
docker exec -u git forgejo forgejo actions generate-runner-token

Das gibt einen langen Token aus – kopiere ihn, du brauchst ihn gleich einmal.

Tipp

Der hier gezeigte Runner wird global (für die ganze Instanz) registriert. Du kannst Runner auch nur an eine Organisation oder ein einzelnes Repository binden – dann holst du den Token in den jeweiligen Einstellungen unter Actions → Runner. Für den Anfang ist ein globaler Runner am praktischsten.

Schritt 2: Die Runner-Compose schreiben

Leg einen eigenen Ordner an und wechsle hinein:

Terminal
mkdir -p /opt/forgejo-runner && cd /opt/forgejo-runner

Erstelle die compose.yaml:

YAML
services:
  docker:
    image: docker:29-dind
    container_name: fjr-docker
    privileged: true
    restart: unless-stopped
    environment:
      DOCKER_TLS_CERTDIR: /certs
    volumes:
      - dind_certs:/certs
      - runner_data:/data

  runner:
    image: code.forgejo.org/forgejo/runner:13.0.0
    container_name: fjr-runner
    restart: unless-stopped
    depends_on: [docker]
    environment:
      DOCKER_HOST: tcp://docker:2376
      DOCKER_CERT_PATH: /certs/client
      DOCKER_TLS_VERIFY: "1"
    volumes:
      - dind_certs:/certs:ro
      - runner_data:/data
    working_dir: /data
    command: forgejo-runner daemon

volumes:
  dind_certs:
  runner_data:

Die wichtigsten Punkte:

  • Der DinD-Dienst heißt bewusst docker. Sein automatisch erzeugtes TLS-Zertifikat ist auf genau diesen Namen ausgestellt – heißt der Dienst anders, scheitert der Runner mit „certificate is valid for docker, not …" (siehe „Wenn es nicht funktioniert").
  • privileged: true braucht DinD, um seine eigene Docker-Engine zu betreiben. Das ist der Preis der Isolation; halte den Runner-Server entsprechend abgesichert.
  • Der Runner spricht den DinD über DOCKER_HOST: tcp://docker:2376 mit TLS an; die Client-Zertifikate teilt er sich über das Volume dind_certs.
  • Die Registrierung landet als .runner-Datei im Volume runner_data und übersteht so Neustarts und Updates.

Warum Docker-in-Docker?

Die Alternative wäre, dem Runner den Docker-Socket des Hosts (/var/run/docker.sock) hineinzureichen. Das ist einfacher, gibt den CI-Jobs aber faktisch Root auf dem Host – ein manipulierter Workflow könnte den ganzen Server übernehmen. DinD kapselt die Jobs in einer eigenen Docker-Instanz und ist die deutlich sicherere Wahl.

Schritt 3: DinD starten und den Runner registrieren

Starte zuerst nur den DinD-Sidecar, damit er seine Zertifikate erzeugt:

Terminal
docker compose up -d docker

Jetzt registrierst du den Runner einmalig. Ersetze DEIN_TOKEN durch den Token aus Schritt 1:

Terminal
docker compose run --rm runner forgejo-runner register \
  --no-interactive \
  --instance https://DEINE_DOMAIN \
  --token DEIN_TOKEN \
  --name mein-runner \
  --labels "docker:docker://node:24-bookworm"

Bei Erfolg endet die Ausgabe mit Runner registered successfully. (eine Warnung, dass register „deprecated" sei, kannst du ignorieren – es funktioniert).

Unbedingt die öffentliche URL verwenden

Registriere den Runner mit deiner öffentlichen Adresse (https://DEINE_DOMAIN) – nicht mit einer internen wie http://forgejo:3000. Grund: Die CI-Jobs laufen im DinD in eigenen Containern mit eigenem Netzwerk und können interne Docker-Namen nicht auflösen. Beim Auschecken müssen sie den Git-Server aber erreichen. Mit der öffentlichen URL klappt das von überall – mit einem internen Namen scheitert jeder Job beim checkout.

Das Label docker:docker://node:24-bookworm bedeutet: Jobs mit runs-on: docker werden in einem node:24-bookworm-Container ausgeführt (bringt Node.js und die üblichen Build-Tools mit). Node 24 ist die aktuell aktive LTS-Linie – Node 20 ist seit April 2026 aus dem Support.

Schritt 4: Den Runner starten und Status prüfen

Jetzt startest du den ganzen Stack:

Terminal
docker compose up -d

Prüfe, dass beide Container laufen:

Terminal
docker compose ps

Wirf einen Blick ins Runner-Log – hier siehst du, ob die Anmeldung geklappt hat:

Terminal
docker compose logs runner

Du solltest eine Zeile wie declared successfully und [poller] launched sehen – der Runner fragt Forgejo jetzt aktiv nach Jobs. In der Weboberfläche unter Administrator-Einstellungen → Actions → Runner taucht mein-runner mit einem grünen Statuspunkt und dem Label docker auf (siehe Screenshot oben). Steht er auf Inaktiv mit grünem Punkt, ist alles gut: Er ist verbunden und wartet nur auf Arbeit.

Schritt 5: Den ersten Workflow anlegen

Workflows liegen im Repository unter .forgejo/workflows/. Lege in einem beliebigen Repo die Datei .forgejo/workflows/ci.yml an:

YAML
name: CI
on: [push]
jobs:
  test:
    runs-on: docker
    steps:
      - uses: actions/checkout@v7
      - run: echo "Commit $GITHUB_SHA wird getestet"
      - run: node --version

Zerlegt:

  • on: [push] – der Workflow startet bei jedem Push.
  • runs-on: docker – wählt unseren Runner über das Label docker aus.
  • actions/checkout@v7 – checkt den Code aus (dieselbe Action wie bei GitHub; Forgejo lädt sie automatisch aus seinem Action-Register).
  • Die beiden run-Schritte geben den Commit und die Node-Version aus – ein minimales, aber echtes Beispiel, das du später durch deine echten Build-/Test-Befehle ersetzt.

Committe und pushe die Datei. Der Push löst den Workflow sofort aus.

Schritt 6: Den Lauf ansehen

Öffne im Repository den Reiter Actions. Dort erscheint dein Lauf – nach wenigen Sekunden mit einem grünen Haken:

Der Actions-Reiter eines Repositories mit einem erfolgreich durchgelaufenen CI-Workflow.
Der Actions-Reiter: der Workflow-Lauf ist grün.

Ein Klick auf den Lauf öffnet die Job-Ansicht mit den einzelnen Schritten und ihren Logs. Hier siehst du, wie actions/checkout das Repository klont und die Befehle nacheinander laufen:

Die Detailansicht eines Forgejo-Actions-Jobs mit aufgeklappten Schritt-Logs.
Job-Detailansicht: alle Schritte grün, mit vollständigen Logs.

Der node --version-Schritt gibt bei uns v24.20.0 aus – der Beweis, dass der Job wirklich im node:24-bookworm-Container gelaufen ist. Damit steht deine CI/CD: Ab jetzt kannst du in den run-Schritten testen, bauen und deployen, was du brauchst.

Wenn es nicht funktioniert

Der Runner startet neu und meldet „cannot ping the docker daemon … certificate is valid for docker, …, not fjr-docker". Der DinD-Dienst heißt anders als docker, aber sein TLS-Zertifikat ist auf docker ausgestellt. Nenne den DinD-Service exakt docker (wie oben) und sprich ihn über DOCKER_HOST: tcp://docker:2376 an – dann passt der Name zum Zertifikat.

Der Job startet, scheitert aber beim actions/checkout mit einem Verbindungsfehler. Der Runner wurde mit einer internen Instanz-URL (http://forgejo:3000) registriert. Die Job-Container im DinD können diesen Namen nicht auflösen. Neu registrieren mit der öffentlichen URL https://DEINE_DOMAIN (.runner-Datei im Volume vorher löschen oder das Volume neu anlegen).

Der Runner erscheint gar nicht in der Übersicht / die Registrierung schlägt fehl. Falscher oder bereits verbrauchter Token, oder der Runner erreicht Forgejo nicht. Frischen Token holen (Schritt 1) und prüfen, dass der Runner-Container https://DEINE_DOMAIN erreicht (docker compose run --rm runner wget -qO- https://DEINE_DOMAIN/api/healthz).

Ein Job bleibt ewig „wartend" (pending). Kein Runner hat ein passendes Label. Der Workflow nutzt runs-on: docker, der Runner muss also das Label docker tragen. Labels beim Registrieren prüfen; in der Runner-Übersicht werden die Labels je Runner angezeigt.

actions/checkout findet die Action nicht. Forgejo lädt Actions aus einem konfigurierten Register (standardmäßig data.forgejo.org). Ist der Server komplett vom Internet abgeschnitten, schlägt das fehl. Ausgehenden HTTPS-Zugriff erlauben oder Actions in einem internen Register spiegeln.

Wartung & Backups

Updates. Runner und DinD aktualisierst du wie jeden Compose-Stack:

Terminal
cd /opt/forgejo-runner
docker compose pull && docker compose up -d

Halte den Runner grob auf Augenhöhe mit deiner Forgejo-Version – eine stark veraltete Runner-Version kann mit neuen Forgejo-Features Probleme bekommen. Pinne wie oben eine konkrete Version statt latest, damit Updates bewusst passieren.

Umstieg von Runner v12 auf v13

Runner v13 bringt bewusste Brüche mit: Die Workflow-Befehle set-output, set-env und add-path sind ersatzlos entfernt – schreib stattdessen in die Dateien $FORGEJO_OUTPUT, $FORGEJO_ENV und $FORGEJO_PATH. Außerdem lassen fehlerhafte Ausdrücke einen Job jetzt hart scheitern (statt nur zu warnen), und in der Runner-Konfiguration heißt container.network_mode nur noch container.network. Ein frisch aufgesetzter Runner wie hier ist davon nicht betroffen; wer bestehende Workflows mitnimmt, liest vorher die Release-Notes zu v13.

Backups. Sicherungswürdig ist vor allem die .runner-Datei im Volume runner_data – sie enthält die Registrierung. Geht sie verloren, registriert sich der Runner beim nächsten Start als neuer Runner (der alte bleibt als „offline" in der Übersicht stehen und kann dort gelöscht werden). Ein Totalverlust ist kein Drama: Du holst einen neuen Token und registrierst neu. Die DinD-Daten (dind_certs, Job-Caches) sind flüchtig und müssen nicht gesichert werden.

Aufräumen. Die CI-Jobs erzeugen im DinD mit der Zeit ungenutzte Images und Layer. Räum sie gelegentlich auf, damit die Platte nicht vollläuft:

Terminal
docker compose exec docker docker system prune -af

Sicherheit. Der DinD läuft privileged – behandle den Runner-Host wie ein sicherheitskritisches System: nur nötige Ports offen, keine anderen sensiblen Dienste daneben, und CI nur für Repositories, deren Workflows du kontrollierst. Wer Workflows aus fremden Forks zulässt, sollte sich vorher intensiv mit deren Risiken beschäftigen.

Zuletzt aktualisiert: 28.08.2026

Feedback per E-Mail: feedback@serverkueche.de

Das könnte dir auch schmecken