Gitea Actions & Runners
Diese Seite beschreibt Gitea Actions auf dem aktuellen Stand (Stand: Gitea 1.27, Runner v3.1.0 vom 06.2026):
- die Grundlagen (Runner-Architektur, Label-System, Image-Flavours)
- wie man eigene Job-Images für das Team baut (Ubuntu 26.04 + OracleLinux 9, Java 25 + Kotlin + Maven + gitflow)
- wie Alles ohne externe Quellen läuft: Actions-Mirror auf eigener Gitea, Job-Images aus eigener Registry
- wie die Ansible-Rolle
gitea_runnerdas pro Runner bzw. pro Tag einbindet - wie man automatisch patcht: Renovate + wöchentlicher Build + Cache-Prune
Alle Beispiele nutzen die eigene Registry gitea.rwcloud.ch und den eigenen Runner.
Gitea Actions – Überblick
Gitea Actions ist der GitHub-Actions-kompatible CI/CD-Arm von Gitea (seit Gitea 1.19). Die wichtigsten Unterschiede zu GitHub:
| Thema | Gitea | GitHub |
|---|---|---|
workflow_dispatch Inputs |
✅ seit 1.23, alle Typen | ✅ |
jobs.<id>.environment |
⚠️ wird ignoriert | ✅ |
complex runs-on (Matrix) |
⚠️ nur runs-on: xyz oder [xyz] |
✅ |
permissions: (Job-Token) |
✅ mit Gitea-scopes (code, releases, wiki, projects) |
✅ |
| Go-Actions | ✅ native | ❌ |
| Absolute Action-URLs | ✅ uses: https://gitea.com/owner/repo@tag |
✅ (rel. owner/repo@tag) |
| Problem Matchers | ⚠️ ignoriert | ✅ |
always() Expression |
✅ einzige unterstützte | ✅ alle |
| Pre/Post-Schritte im UI | ⚠️ keine eigene Sektion | ✅ |
schedule: @daily/@weekly |
✅ zusätzlich zu Cron | ❌ |
Aktivieren
In app.ini der Gitea-Instanz (falls noch nicht aktiviert – seit 1.21 standardmäßig an):
1 2 | |
Im Repository unter Settings → Actions ist zusätzlich Enable Repository Actions zu aktivieren.
Runner-Architektur & Label-System
Der Runner (getrennt vom Gitea-Server betrieben) empfängt Jobs und führt sie aus. Wichtig zu verstehen ist die Trennung:
1 2 3 4 5 6 7 8 9 10 | |
Der Runner startet für jeden Job einen Container aus einem Docker-Image. Welches Image verwendet wird, entscheidet das Label-System:
1 | |
| Schema | Bedeutung | Beispiel |
|---|---|---|
docker://<image> |
Job läuft in Container aus <image> |
java-25:docker://gitea.rwcloud.ch/homelab/runner-java-25-ubuntu-26.04:latest |
host |
Job-Schritte laufen direkt auf Host | shell:host |
| (leer) | Standard host |
ubuntu-latest → läuft auf Host mit dort installierten Tools |
Wichtig: Wenn du
runs-on: ubuntu-latestin deinem Workflow schreibst UND der Runner nur das Labelubuntu-latest(ohnedocker://) kennt, läuft der Job auf dem Host des Runners – nicht in einem Container! Das ist häufiger Grund für "warum ist Java nicht da, obwohl ich's doch installiert habe?".
Wenn kein Label passt, fällt der Runner auf docker.gitea.com/runner-images:ubuntu-latest zurück (default). Genau dorthin will man also nicht gelangen.
Runner-Image-Flavours
Das offizielle gitea/runner-Image gibt's in drei Varianten:
| Tag | Basis | Docker-daemon | Prozess-Monitoring | Nutzt |
|---|---|---|---|---|
latest (basic) |
alpine | extern (Host-Socket bind-mount required) | tini |
Root, --privileged nicht nötig |
latest-dind |
docker:dind | eingebaut | s6 |
Root, --privileged nötig |
latest-dind-rootless |
docker:dind-rootless | eingebaut (rootless) | s6 |
UID 1000, rootless-Limits |
Für dieses Setup: basic (Status-Quo). Der Runner bindet /var/run/docker.sock vom Host. Vorteile:
- keine Privilegien, einfacher
- der Docker-daemon mit allen Caches läuft auf dem Host (perfekt für docker image prune via post_task_script)
- Job-Images werden per docker://image aus labels gezogen, können also deine eigene Registry sein
Eigene Job-Images: Konzept
Anstatt jedes Mal uses: actions/setup-java@v3 (der von github.com lädt) zu nutzen, bauen wir ein Job-Image, in dem alle Tools schon installiert sind. Das ist:
- schneller – kein Setup-Schritt pro Job
- offline-fähig –
DEFAULT_ACTIONS_URL=self+ eigenes Image = kein github.com - kontrolliert – exakt die Versionen, die das Team braucht
Was muss in das Image?
Für ein Java 25/26 + Kotlin + Vaadin Flow + Maven + gitflow Setup:
| Komponente | Warum |
|---|---|
git |
Version Control (immer) |
curl, wget, jq, zsh |
Basis-Tools |
ca-certificates, unzip |
TLS & de-archivers |
| JDK 25 (Temurin) | Vaadin Flow, mvn, Kotlin |
| Kotlin 2.1.x (kotlinc) | Kotlin-Compiler (falls nicht via Maven nur) |
| Maven | Build & Dependency-Management für Vaadin |
| gitflow-avh | avh-Wrapper um git flow fürs Team |
docker client (optional) |
falls Job docker build braucht |
make |
einfache Build-Schritte |
openssh-client |
Deploy-Ziele |
uv (optional) |
Python-Tooling, falls im Workflow |
Hinweis zu Vaadin / Vaadin Flow: Das eigentliche Vaadin ist nur ein Maven-Plugin (
com.vaadin:vaadin-maven-plugin), keine eigene Binary. Maven + JDK genügt. Für Vaadin 24+ braucht man Node.js, das Plugin lädt das aber selbst (vianpm/pnpm).
Dockerfile – Ubuntu 26.04 (Beispiel)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | |
Dockerfile – OracleLinux 9 (Beispiel)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | |
Achtung: Oracle Linux 9 hat
nodejsimAppStream-Repo ab der 9.3. Wennnodejsnicht gefunden wird:dnf config-manager --enable "ubi-9-kmods-rpms ubi-9-crs-rpms ol9_codeready_builder"oder die Version explizitdnf install nodejs:22.
Naming & Push
1 2 3 | |
latestwird wöchentlich neu gebaut (siehe Auto-Patch)- Versionstags (z. B.
25.0.4) sind immutabil – gut für Rollbacks
Build-Workflow (Beispiel)
.gitea/workflows/runner-java-build.yml im docker-builds-Repo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | |
Beachte: -
runs-on: java-25-buildermuss ein Label sein, dessendocker://-Image docker-CLI enthält (basic-Runner hat kein). -actions/checkout@v4wird hier über eine absolute URL angepumpt, damit er von deiner Gitea kommt, nicht von github.com (dafür muss die Action dort gemirrorrt sein –DEFAULT_ACTIONS_URL=selfist optional, aber sinnvoll).
Actions ohne github.com
Gitea lädt uses: actions/checkout@v4 per Default von github.com. Für ein volles internes Setup brauchst du:
1. Actions auf eigene Gitea spiegeln
Einmaliges Setup via Gitea Mirror-Repositories:
1 2 3 4 5 6 7 8 9 | |
Gitea zieht bei jedem push/pull automatisch die neue Version (sofern Enable mirror sync aktiviert).
2. Gitea-Server-Setting
In app.ini:
1 2 3 4 | |
3. Optional: absolute uses:-URLs
Wenn du für eine Action ganz sicherstellen willst, dass sie lokal kommt (unabhängig vom DEFAULT_ACTIONS_URL):
1 | |
4. Registry-Login ohne docker/login-action
docker/login-action@v3 ist eine JS-Action (kommt von github.com). Alternativen, die komplett intern sind:
a) Direkter run:-Step (einfachste, empfohlene Variante):
1 2 3 4 | |
Voraussetzung: das runs-on-Label zeigt auf ein Job-Image, das docker-CLI + /var/run/docker.sock bind-mounted hat (bei basic-Runner ist der Socket bereits verfügbar; docker-CLI muss im Job-Image sein).
b) container.env im Workflow (secrets, die nur beim Login gebraucht werden):
1 2 3 4 5 6 7 8 | |
c) runner.hooks.job_started (wenn es bei jedem Job passieren soll):
Wichtig: Hooks laufen wo die Steps laufen – im Job-Container bei docker://-Labels, auf dem Host bei host-Labels. Ein docker login braucht docker-CLI im Job-Image:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Kein
docker loginohne docker-CLI im Job-Image. Wenn's dein Job-Image nicht hat, verwende (a) oder (b) mit einem Builder-Image, dasdocker-CLI enthält.
Auto-Patching: der Mechanismus
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Cache-Prune via post_task_script
Runner unterstützt ein optionales Skript, das nach jedem Job-Cleanup läuft – perfekt für docker image prune:
Im Ansible-Inventory (group_vars):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Die Rolle schreibt das Skript nach /etc/gitea-runner/<name>/post-task.sh, mountet es read-only in den Runner-Container unter /usr/local/bin/gitea-post-task.sh und setzt in der config.yaml automatisch:
1 2 3 4 | |
Wichtig: Während
post_task_scriptläuft, meldet sich der Runner nicht bei Gitea (Heartbeat pausiert). Das Skript braucht docker-CLI in der Runner-Umgebung – basic-Flavour hat keins, also dafürgitea/runner:latest-dindoder ein Custom-Image mit Docker-CLI wählen. Ein2>/dev/null || trueam Ende jeder Zeile hält das Skript robust, falls der Daemon nicht erreichbar ist.
Renovate-Trigger im docker-builds-Repo
In renovate.json (ergänzend zum Bestehenden):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
→ Renovate erstellt PRs mit JAVA_VERSION = 26 oder KOTLIN_VERSION = 2.2.0. Merge → Workflow-Triggert → neuer Build → latest aktualisiert.
Ansible-Rolle gitea_runner – Nutzung
Die Rolle legt pro Runner config.yaml, das Volume und den Container an. Die Labels kommen aus gitea_runners:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
Workflow dann:
1 2 3 4 5 6 7 8 9 10 11 | |
Häufige Stolpersteine
| Problem | Ursache | Fix |
|---|---|---|
| Job läuft auf Host, nicht in Image | Label ohne docker:// |
java-25:docker://… |
actions/checkout lädt von github.com |
DEFAULT_ACTIONS_URL fehlt |
[actions] DEFAULT_ACTIONS_URL=self + Mirror |
| Java im Job fehlt | setup-java nicht im Image |
Temurin im Dockerfile |
| Cache hängt an alten Images fest | Local Daemon-Cache + latest |
post_task_script_body → docker image prune |
| Runner nimmt neue Labels nicht an | .runner-Datei hat alte |
Labels in config.yaml + config re-write bei Start, oder .runner neu register |
uses:-Action gibt 404 |
Mirror fehlt / DEFAULT_ACTIONS_URL=github |
Mirror anlegen oder absolute URL |
docker build im Job nicht möglich |
Basic-Runner ohne Daemon | /var/run/docker.sock bind-montiert, oder dind-Flavour |
Weiterführende Quellen
- Gitea Actions – Runner
- Design of Gitea Actions (Netzwerk-Architektur)
- Compared to GitHub Actions
- Gitea Runner (Runner-Doku)
- Gitea: Creating Go Actions
- Gitea Runner 3.0.0 Release Notes
Checklist beim Onboard neuer Workflows
- [ ]
gitea_runners(Ansible) enthält Label mitdocker://<dein-image> - [ ] Das Image ist in
gitea.rwcloud.chgebaut (Build-Workflow + Registry) - [ ] Workflows nutzen
runs-on: <label>(nacktes Wort =runs-on-Match) - [ ] Keine
uses: actions/*-Actions, die nicht ingitea.rwcloud.chgemirrorrt sind - [ ]
app.inihatDEFAULT_ACTIONS_URL = self - [ ]
post_task_script_bodyim Ansible-Runner gesetzt (optional, empfohlen) - [ ] Renovate-Repo für das Image aktiviert