Ein Gitea Actions Runner führt CI-Jobs nicht in Gitea selbst, sondern auf einem getrennten Docker-Host aus. Dieser Leitfaden richtet einen persistenten Runner mit Container-Jobs ein, verbindet ihn korrekt mit der Gitea-Instanz und prüft ihn mit einem ersten Workflow. Dadurch bleiben Code-Ausführung, Registrierung und Fehlersuche nachvollziehbar.

Stand: 13.10.2026

Auf einen Blick

  • Gitea Actions braucht einen separat registrierten Runner.
  • Container-Jobs sind laut Gitea die empfohlene Ausführungsart.
  • Die Instanz-URL muss aus Job-Containern erreichbar sein; localhost ist dafür falsch.
  • Das Verzeichnis /data bewahrt die Runner-Identität dauerhaft.
  • Ein Runner darf nur Repositories ausführen, denen du vertraust.

1. Die Ausgangslage und das Ziel

Teil 1 – Was der Gitea Actions Runner übernimmt

Gitea Actions speichert Workflows im Repository, führt sie aber nicht selbst aus. Stattdessen weist Gitea wartende Jobs einem Runner zu. Der Runner erstellt für ein Docker-Label einen separaten Job-Container. Deshalb kann der Gitea-Server schlank bleiben und der CI-Host lässt sich getrennt absichern oder bei Bedarf ersetzen.

Das Ziel ist ein dauerhaft registrierter Runner auf einem Docker-Host. Er nimmt nur Workflows mit dem Label ubuntu-latest an. Die Jobs laufen in dem von Gitea dokumentierten Runner-Image, nicht direkt auf dem Host. Das ist für private Repositories ein sinnvoller Ausgangspunkt; bei fremdem oder öffentlichem Code braucht die Vertrauensgrenze mehr Aufmerksamkeit.

Architektur eines Gitea Actions Runner mit Gitea, Runner, Docker-Daemon und Job-Container.
Der Runner vermittelt zwischen Gitea und dem Docker-Job-Container.

Die Gitea-Dokumentation zu Actions beschreibt den Runner als eigenständiges Programm. Sie warnt zugleich davor, einen nicht vertrauenswürdigen Runner für eigene Repositories zu verwenden oder vertrauenswürdige Runner fremden Repositories anzubieten. Das ist keine Formalität: Workflow-Code ist ausführbarer Code.

2. Voraussetzungen und Architektur

Gitea Actions Runner: Was vorher erreichbar sein muss

Du brauchst eine Gitea-Instanz mit aktivierten Actions, einen Docker-Host für den Runner und einen Registrierungstoken. Actions sind laut aktueller Runner-Dokumentation seit Gitea 1.21 standardmäßig aktiviert. Bei älteren Installationen aktivierst du die Funktion in der Gitea-Konfiguration.

Diese Einstellung gehört in die app.ini der Gitea-Instanz. Sie schaltet Workflows frei, stellt aber noch keinen Runner bereit.

[actions]
ENABLED=true

Danach muss Gitea die Änderung geladen haben. In der Weboberfläche erscheint anschließend der Bereich für Actions und Runner; ohne registrierten Runner bleiben Jobs allerdings in der Warteschlange.

Installiere Docker auf dem Runner-Host und prüfe zuerst, ob der Daemon erreichbar ist. Container-Jobs benötigen laut Gitea einen Docker-Daemon; Podman ist in dieser Konfiguration nicht unterstützt.

docker version

Die Ausgabe enthält Client- und Server-Informationen. Fehlt der Server-Teil oder erscheint ein Verbindungsfehler, löse das zuerst. Der Runner kann dann keine Job-Container erzeugen.

Die richtige Adresse für den Runner wählen

Die URL in GITEA_INSTANCE_URL verwenden sowohl der Runner als auch die später gestarteten Job-Container. Daher darf sie nicht auf 127.0.0.1 oder localhost zeigen, wenn Gitea und Runner getrennt laufen. Die Quickstart-Dokumentation empfiehlt in diesem Fall typischerweise die LAN-Adresse oder einen aus dem Docker-Netz erreichbaren DNS-Namen.

Prüfe diese Erreichbarkeit vom Runner-Host, bevor du registrierst. Ersetze die Adresse durch deine eigene Gitea-URL.

curl -I https://gitea.example.net/

Erwartet wird eine HTTP-Antwort von Gitea. Wichtig ist nicht ein bestimmter Statuscode der Startseite, sondern dass DNS, TLS und die Route von diesem Host aus funktionieren.

VarianteGeeignet wennWichtige Folge
Docker-LabelWorkflows sollen isoliert laufenDer Host-Daemon erstellt Job-Container.
Host-LabelEin gezielt vorbereiteter, vertrauenswürdiger Host nötig istJobs laufen ohne Container-Isolation auf dem Host.
Docker-in-DockerEin eigener Daemon pro Runner erforderlich istMehr Einrichtung; die Dokumentation nennt --privileged als Voraussetzung.

Für diesen Leitfaden bleibt es beim Docker-Label. Die Runner-Dokumentation empfiehlt Docker für isolierte Jobs; Host-Modus ist dagegen nicht zwischen Jobs isoliert.

3. Token und persistente Daten vorbereiten

Teil 2 – Den passenden Registrierungstoken holen

Erzeuge den Token in Gitea auf der kleinsten sinnvollen Ebene: Instanz, Organisation oder Repository. Laut Dokumentation zur Registrierung kann ein Token mehrere Runner registrieren und bleibt gültig, bis er zurückgesetzt wird. Für einen einzelnen Projekt-Runner ist deshalb ein Repository-Token enger begrenzt als ein Instanz-Token.

Lege den Token nicht in ein Git-Repository und nicht direkt in eine Shell-Historie. Die Runner-Dokumentation nennt eine Datei als Alternative zur Kommandozeile. Lege sie mit restriktiven Rechten an und ersetze den Platzhalter durch den Token aus Gitea.

install -m 600 /dev/null ./runner-token
printf '%s' '<DEIN_REGISTRIERUNGSTOKEN>' > ./runner-token

Danach darf nur der Besitzer die Datei lesen. Für produktive Umgebungen ist ein Secret-Mechanismus des jeweiligen Betriebsmodells besser als eine im Projektordner liegende Datei; der Runner unterstützt dafür auch GITEA_RUNNER_REGISTRATION_TOKEN_FILE.

Erstelle außerdem ein benanntes Docker-Volume. Es enthält später .runner, also die Identität und API-Zugangsdaten dieses einen Runners. Gitea weist ausdrücklich darauf hin, diese Datei weder von Hand zu bearbeiten noch auf einen zweiten Rechner zu kopieren.

docker volume create gitea-runner-data

Docker gibt den Namen des Volumes zurück. Fehlt diese Persistenz, registriert ein neu erstellter Container sich erneut und hinterlässt einen alten Runner-Eintrag in Gitea. Wurde der Token inzwischen zurückgesetzt, scheitert der Neuaufbau sogar.

4. Gitea Actions Runner mit Docker starten

Der sichere Standard: Docker-Job-Container

Der offizielle Container kombiniert Registrierung und Daemon: Beim ersten Start registriert der Entrypoint, wenn noch keine Registrierungsdatei vorhanden ist. Danach startet er den Daemon. Das Volume auf /data macht genau diesen ersten Start dauerhaft nachvollziehbar.

Der folgende Befehl orientiert sich an der offiziellen Docker-Installation. Der Docker-Socket stellt dem Runner den Host-Daemon für Job-Container bereit. Ersetze URL und Namen; die Token-Datei bleibt auf dem Host und wird nur lesbar eingebunden.

docker run -d --name gitea-runner --restart always \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v gitea-runner-data:/data \
  -v "$(pwd)/runner-token:/run/secrets/runner-token:ro" \
  -e GITEA_INSTANCE_URL=https://gitea.example.net/ \
  -e GITEA_RUNNER_REGISTRATION_TOKEN_FILE=/run/secrets/runner-token \
  -e GITEA_RUNNER_NAME=homelab-docker-01 \
  -e GITEA_RUNNER_LABELS=ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest \
  gitea/runner:3

Mit docker ps muss der Container nun als laufend erscheinen. Der Token wird laut offizieller Docker-Dokumentation nach der Registrierung vor dem Start des Daemons entfernt; dennoch schützt der nur-lesbare Mount davor, ihn unnötig zu verändern.

Der Socket-Mount ist eine bewusste Entscheidung. Laut Docker-Installationsdokumentation teilen Jobs dann den Host-Daemon und können dessen andere Container sowie Images sehen. Ein Job mit Socket-Zugriff kann außerdem den wiederverwendbaren Registrierungstoken in der Container-Inspektion erreichen. Nutze diesen Aufbau daher nur für vertrauenswürdige Repositories und einen dedizierten CI-Host.

Warum das Label Teil der Architektur ist

Das Label verbindet runs-on eines Workflows mit einer Ausführungsart. Das offizielle Format lautet name:docker://image. Der Name ist, was der Workflow anfordert; das Image bestimmt den Container, in dem der Job startet.

ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest

Damit darf ein Workflow runs-on: ubuntu-latest verwenden. Die Label-Dokumentation empfiehlt, Images nach Tag oder Digest zu pinnen, wenn reproduzierbare Jobs wichtiger sind als automatische Image-Updates.

Ablauf vom Push bis zum erfolgreichen Gitea-Workflow-Job.
Der Testworkflow überprüft den Weg vom Push bis zur Ausgabe im Job-Container.

5. Den ersten Workflow anlegen

Teil 3 – Ein minimaler, überprüfbarer Job

Lege im Repository die Datei .gitea/workflows/verify.yml an. Gitea Actions ist weitgehend zu GitHub Actions kompatibel; der Workflow bleibt trotzdem bewusst klein. So prüfst du zuerst Registrierung, Label, Image-Pull und Log-Ausgabe, statt einen komplexen Build zu debuggen.

name: verify-runner
on: [push]
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - name: Show environment
        run: |
          uname -a
          pwd

Nach einem Push erscheint ein Lauf im Actions-Bereich des Repositories. Der Job wechselt von der Warteschlange auf den Runner und endet erfolgreich, wenn die beiden Befehle im Log stehen. Bleibt er wartend, passt meist das Label nicht oder der Runner ist offline.

Verwende für Aktionen aus fremden Quellen bewusst deren Herkunft. Laut Gitea lädt actions/checkout@v4 standardmäßig von GitHub. Eigene Aktionen referenzierst du mit self:owner/repo@ref, wenn DEFAULT_ACTIONS_URL entsprechend konfiguriert ist. Damit ist sichtbar, ob ein Workflow Code von außen bezieht.

uses: self:owner/repo@ref

Die Referenz ist erst sinnvoll, wenn das eigene Action-Repository existiert. Andernfalls bleibt der erste Test bei Shell-Befehlen und vermeidet eine zusätzliche Download-Quelle.

6. Verifikation: So prüfst du, dass alles läuft

Prüfe zuerst den Containerzustand und danach seine Protokollausgabe. Diese Reihenfolge trennt Docker-Probleme von Registrierung und Workflow-Zuordnung.

docker ps --filter name=gitea-runner
docker logs gitea-runner

Erwartet wird ein laufender Container ohne fortlaufende Registrierungsfehler. In Gitea muss derselbe Runner im gewählten Bereich als online sichtbar sein. Die Datei .runner liegt im persistenten /data-Volume und ist das entscheidende Zeichen, dass die Erstregistrierung nicht bei jedem Start wiederholt wird.

Prüfe danach explizit den Workflow. Die Dokumentation erklärt, dass ein nicht passendes runs-on zwar auf das Standard-Image zurückfallen kann; dennoch solltest du das gewünschte Label in der Runner-Ansicht kontrollieren, statt dich darauf zu verlassen.

git add .gitea/workflows/verify.yml
git commit -m "ci: verify Gitea runner"
git push

Erwartet wird ein erfolgreicher Lauf mit den Ausgaben von uname -a und pwd. Kontrolliere außerdem, dass der Runner nicht den Host-Modus meldet. Ein erfolgreicher Host-Job wäre funktional, aber nicht die hier gewählte Isolation.

7. Betriebskonzept: Grenzen bewusst ziehen

Teil 4 – Repository, Organisation oder Instanz?

Die Ebene des Registrierungstokens bestimmt, welche Arbeitsmenge der Runner grundsätzlich erreichen kann. Ein Repository-Runner ist passend, wenn ein einzelnes Projekt gebaut oder geprüft werden soll. Ein Organisations-Runner spart dagegen Pflegeaufwand, wenn mehrere Repositories dieselben Build-Umgebungen verwenden. Ein Instanz-Runner ist am weitesten gefasst und sollte deshalb nur auf einer privaten Instanz mit klarer Verwaltung eingesetzt werden.

Diese Entscheidung ist auch eine Sicherheitsentscheidung. Die Gitea-Dokumentation unterscheidet ausdrücklich zwischen dem Vertrauen in einen Runner und dem Vertrauen in die Repositories, die ihn nutzen dürfen. Ein gemeinsamer Runner ist nicht automatisch falsch. Allerdings sollte seine Berechtigung dem engsten gemeinsamen Vertrauensbereich entsprechen. So bleibt nachvollziehbar, warum ein bestimmter Job diesen Host verwenden durfte.

Container-Isolation richtig einordnen

Der Docker-Modus trennt einzelne Job-Container voneinander. Das ist deutlich besser als ein Host-Label, bei dem Workflow-Befehle unmittelbar auf dem Runner-System laufen. Dennoch ist die Trennung nicht gleichbedeutend mit einer harten Sicherheitsgrenze, sobald der Docker-Socket in den Runner gemountet ist. Der Job kann dann über denselben Daemon mit Docker-Ressourcen arbeiten.

Deshalb sollte der CI-Host keine zusätzlichen, sensiblen Arbeitslasten tragen. Trenne insbesondere den Runner von einem Host, auf dem produktive Datenbanken, private Registry-Zugangsdaten oder andere dauerhafte Container laufen. Diese Empfehlung folgt direkt aus dem dokumentierten Trade-off des Socket-Mounts: Job-Container teilen den Daemon und können dessen übrige Container und Images sehen.

Wenn Workflows aus einem größeren, nicht vollständig vertrauenswürdigen Kreis kommen, sind ephemere Runner die bessere Richtung. Ein solcher Runner akzeptiert laut Gitea genau einen Job und beendet sich dann. Seine Zugangsdaten werden nach der Job-Zuteilung widerrufen. Der Gewinn entsteht aber erst vollständig, wenn jede Instanz wirklich frisch bereitgestellt wird, etwa als neuer Container oder als neue VM.

Images und Labels planbar halten

Ein Label ist mehr als ein Anzeigename. Es ist ein Vertrag zwischen Workflow und Infrastruktur. Schreibe deshalb nicht einfach ubuntu-latest in jedes Repository, wenn verschiedene Runner unterschiedliche Werkzeuge liefern. Nutze bei mehreren Plattformen eindeutige Namen, zum Beispiel für Linux-Architekturen oder spezielle Toolchains. Die Label-Dokumentation rät gerade bei Host-Labels zu klaren Namen, damit ein vermeintlich generischer Workflow nicht unbemerkt direkt auf einem Host ausgeführt wird.

Auch das Job-Image braucht eine bewusste Strategie. Ein gleitender Tag ist bequem, kann aber den Inhalt zwischen zwei Läufen ändern. Ein konkreter Tag oder Digest macht die Umgebung nachvollziehbar. Starte mit dem offiziellen Image, wenn der Workflow nur Standardwerkzeuge benötigt. Braucht er wiederholt Compiler, Linter oder Clients, ist ein eigenes, gepflegtes Image sinnvoller als deren Installation bei jedem Lauf. Das verkürzt nicht nur Schritte, sondern macht Abhängigkeiten im Image sichtbar.

Konfiguration und Neustarts

Der Runner liest seine Konfiguration laut Dokumentation nur beim Start. Eine geänderte Konfigurationsdatei allein verändert also keinen laufenden Daemon. Plane deshalb jede Änderung von Labels, Kapazität oder Netzwerk wie ein kleines Deployment: Arbeitslasten prüfen, Konfiguration ändern, Runner neu starten und mit einem Testworkflow verifizieren.

Bei gleichzeitigen Jobs lohnt sich eine besonders vorsichtige Planung. Die Konfiguration beschreibt, dass bei leerem container.network pro Docker-Job ein Netzwerk angelegt wird. Eine hohe Parallelität kann dadurch die Adresspools des Docker-Daemons erschöpfen. Erhöhe Kapazität daher nicht blind. Beginne mit einem Job, beobachte die tatsächliche Warteschlange und dokumentiere, welche Netzwerke und Ressourcen der Workflow benötigt.

Behalte außerdem die Registrierungsdatei je Runner getrennt. Zwei Prozesse mit derselben Datei sind für Gitea nicht unterscheidbar und können nach der Runner-Dokumentation ihre Jobs gegenseitig abbrechen. Jeder Runner-Container erhält deshalb sein eigenes /data-Volume. Ein kopiertes Volume ist kein Backup- oder Skalierungsmechanismus für einen zweiten Runner.

Updates ohne Überraschung durchführen

Ein Update beginnt nicht mit dem Löschen des Containers. Prüfe zuerst, ob in Gitea noch Jobs laufen oder warten. Anschließend halte fest, welchen Image-Tag der Runner verwendet, damit ein Rückweg möglich bleibt. Ziehe das gewünschte Image, erstelle den Container mit denselben persistenten Daten neu und prüfe danach die Online-Anzeige in Gitea. Das Volume sorgt dabei dafür, dass die vorhandene Runner-Identität erhalten bleibt.

Danach ist der kleine Verifikationsworkflow wichtiger als ein bloßer Containerstatus. Ein gestarteter Prozess beweist weder die Verbindung zur Instanz noch den Image-Pull eines Jobs. Erst ein erfolgreicher Lauf bestätigt das Zusammenspiel von Token, URL, Label, Docker-Daemon und Workflow. Dokumentiere die verwendete Runner-Version sowie das Datum der Prüfung neben deiner Infrastruktur-Konfiguration.

8. Die wichtigsten Stolperfallen

Der Job bleibt dauerhaft in der Warteschlange

Symptom: Der Workflow startet nicht, obwohl die YAML gültig ist. Ursache: Der Runner ist offline oder runs-on passt nicht zu seinem Label. Lösung: Prüfe Runner-Status in Gitea, docker logs und das Label ubuntu-latest:docker://….

Der Runner registriert sich bei jedem Neustart neu

Symptom: In Gitea sammeln sich alte Runner-Einträge. Ursache: /data ist nicht persistent. Lösung: Binde ein eigenes Volume auf /data ein; es enthält die Registrierungsdatei .runner.

Jobs erreichen Gitea nicht

Symptom: Registrierung oder Checkout scheitern mit Verbindungsfehler. Ursache: Die Instanz-URL zeigt auf localhost oder eine Adresse, die aus Containern nicht erreichbar ist. Lösung: Verwende die LAN-Adresse oder einen erreichbaren DNS-Namen und prüfe ihn vom Runner-Host.

Ein fremder Workflow bekommt zu viele Rechte

Symptom: Unbekannter Code kann Docker-Ressourcen sehen oder steuern. Ursache: Der gemountete Docker-Socket teilt den Host-Daemon. Lösung: Betreibe den Runner nur für vertrauenswürdige Repositories; für breitere Vertrauensgrenzen empfiehlt die Dokumentation frische, ephemere Container oder VMs pro Job.

Änderungen an Labels wirken nicht

Symptom: Der Runner nimmt weiter die alten Jobs an. Ursache: Labels haben eine dokumentierte Priorität: Startparameter beziehungsweise Umgebungsvariable vor Konfiguration vor Registrierungsdatei. Lösung: Ändere die höchste tatsächlich verwendete Quelle und starte den Runner neu.

8. Betrieb, Updates und Grenzen

Der Tag 3 folgt laut Docker-Dokumentation der aktuellen 3.x-Serie; ein exakter Versions-Tag erleichtert reproduzierbare Updates. Plane Updates als kontrollierten Neustart: Prüfe erst laufende Jobs, ziehe danach das gewünschte Image und kontrolliere Registrierung sowie Testworkflow erneut. Die Runner-Konfiguration wird nur beim Start gelesen.

Für besonders sensible oder fremde Workloads bietet Gitea ephemere Runner. Sie akzeptieren genau einen Job und beenden sich danach; nach der Zuteilung werden ihre Zugangsdaten widerrufen. Das löst aber nicht automatisch jede Isolation: Nur ein frischer Container oder eine frische VM pro Runner schafft die beabsichtigte Trennung.

Fazit

Ein Gitea Actions Runner mit Docker macht CI für eine eigene Gitea-Instanz greifbar, solange Netzwerk, Label und persistente Registrierung zusammenpassen. Der minimale Workflow beweist den gesamten Weg vom Push bis zum Job-Container. Entscheidend bleibt die Vertrauensgrenze: Der Docker-Socket ist für eigene, kontrollierte Repositories praktisch, aber kein geeigneter Ausführungsweg für beliebigen fremden Code. Mit einem persistenten /data-Volume und einer erreichbaren Instanz-URL bleibt der Betrieb nachvollziehbar.

Quellen

Weiterführend: Wer Docker zunächst solide aufbauen will, findet die Docker-Compose-Grundlagen. Für sichere Images hilft der Leitfaden zu Dockerfile Best Practices. Verlässliche Container-Zustände deckt Docker Healthcheck einrichten ab, während Restic Docker Backup die Sicherung persistenter Daten erklärt.

Häufige Fragen

Was ist ein Gitea Actions Runner?

Er ist ein separates Programm, das Workflows aus Gitea abholt und deren Jobs ausführt. Gitea selbst plant die Jobs, der Runner stellt die Ausführungsumgebung bereit.

Braucht Gitea Actions Docker?

Nicht zwingend: Ein Runner kann Jobs auch im Host-Modus ausführen. Gitea empfiehlt für die bessere Isolation und Verwaltung jedoch Docker-Container für Jobs.

Warum darf die Gitea-URL nicht localhost sein?

Der Runner und seine Job-Container müssen die Adresse erreichen. localhost zeigt in einem getrennten Container auf diesen Container und nicht auf die Gitea-Instanz.

Wo finde ich den Registrierungstoken für den Runner?

Gitea stellt Tokens auf Instanz-, Organisations- und Repository-Ebene bereit. Wähle die kleinste Ebene, auf der der Runner tatsächlich arbeiten soll.

Ist ein Docker-Socket für einen Gitea Runner sicher?

Er ist für vertrauenswürdige eigene Repositories praktikabel, gibt Job-Code aber weitreichenden Zugriff auf den Docker-Daemon. Für fremden Code sollte ein stärker isoliertes, ephemeres Modell verwendet werden.