Zum Hauptinhalt springen

CI-Runner

fremforge stellt gehostete Runner bereit, die in jedem Nutzerplan enthalten sind, und unterstützt eigene Runner (BYO) für Teams, die On-Premises-Betrieb, eigene Hardware oder eine bestimmte Platzierung im Netzwerk brauchen. Beide Runner-Typen führen Forgejo-Actions-Workflows mit derselben YAML-Syntax wie GitHub Actions aus.

Gehostete Runner

Gehostete Runner sind virtuelle Maschinen pro Commit auf T Cloud Public in der Region eu-de (Biere/Magdeburg). Eine VM wird aus einem eigenen Debian-Image mit vorinstalliertem Docker bereitgestellt, führt die Jobs eines einzigen Commits eines Repositorys aus und wird gelöscht, sobald die Jobs dieses Commits erledigt sind. Zwischen Commits bleibt nichts erhalten.

Verfügbare Labels

LabelBetriebssystem / ArchitekturHinweise
fremforgeDebian 13 („trixie“, glibc), x86_64Standard-Label für gehostete Runner. Nutze es für fast jeden Workflow. Was vorinstalliert ist, steht unter Inhalt des Runner-Images.
linux/amd64Debian 13 („trixie“, glibc), x86_64Synonym für fremforge. Zielt auf dasselbe Image.
dind-privilegedDebian 13, x86_64, KVM-VerschachtelungZusätzlich zu fremforge angeben (runs-on: [fremforge, dind-privileged]) für Workflows, die verschachtelte KVM-Gäste brauchen – kind-Cluster, testcontainers-Funktionen, die Namespaces anlegen müssen. Läuft auf einem eigenen VM-Flavor mit aktivierter verschachtelter Virtualisierung. Dieselbe Isolation mit einer VM pro Commit.

So verweist du in deinem Workflow auf ein Label:

jobs:
  build:
    runs-on: fremforge

Container-Builds funktionieren ab Werk auf zwei Wegen – die vollständige Aufteilung findest du unten unter Inhalt des Runner-Images § Container-Builds:

  • Natives Docker (Standard) – docker build, docker compose und docker pull funktionieren einfach. Der Docker-Daemon läuft nativ auf der VM (kein Docker-in-Docker-Sidecar), daher verhält sich docker genauso wie auf einem von GitHub gehosteten Runner. Passt für 90 % der Workflows.
  • kaniko – ohne Daemon, schnellerer Kaltstart, besserer Layer-Cache gegen entfernte Registrys. Nutze es, wenn du keine BuildKit-spezifischen Funktionen brauchst.
jobs:
  build-image:
    runs-on: fremforge
    steps:
      - uses: actions/checkout@v4
      - run: docker build -t registry.example.com/$GITHUB_REPOSITORY:$GITHUB_SHA .
      - run: docker push registry.example.com/$GITHUB_REPOSITORY:$GITHUB_SHA 2>&1 | stdbuf -oL tr '\r' '\n' | grep -vE '^(Waiting|)$'

Gehostete ARM64-Runner und weitere Betriebssystemvarianten (Ubuntu, Fedora, …) stehen auf der Roadmap (siehe Changelog) – fremforge zielt heute nur auf x86_64-Linux. Für ARM64- oder Nicht-Linux-Workflows sind bis dahin eigene Runner (siehe unten) der Weg.

Isolationsmodell

Jobs laufen auf dedizierten virtuellen Maschinen mit eigenem Kernel. Die Plattform beobachtet die Warteschlange von Forgejo Actions und startet für einen wartenden Job eine frische VM. Ein Kaltstart dauert etwa 40 Sekunden bis zum ersten Schritt des Jobs; es gibt keinen Pool vorgestarteter VMs.

Ein Commit, eine Vertrauensdomäne. Eine VM führt immer nur Jobs des Commits und Repositorys aus, für die sie gestartet wurde. Ist ein Job fertig, wartet die VM bis zu 60 Sekunden auf einen weiteren Job desselben Commits, in einem beliebigen Workflow, und führt ihn ohne neuen Boot aus (Start in etwa 5 Sekunden). Danach wird sie ausgeschaltet und gelöscht. Keine VM führt Jobs aus zwei Commits, zwei Repositorys oder zwei Organisationen aus; behandle daher alle Jobs eines Commits als eine Vertrauensdomäne. Zugangsdaten, die die Plattform kennt (Docker- und npm-Logins, die üblichen Credential-Dateien), werden zwischen Jobs zurückgesetzt, Tool-Caches und gezogene Images bleiben aber auf der VM. Das vollständige Design beschreibt das Modell der gehosteten Runner.

Jobs für gehostete Runner zuschneiden

Jede frische VM kostet etwa 40 Sekunden Boot vor deinem ersten Schritt und oft die 60-Sekunden-Wartezeit nach deinem letzten. Ein Job mit 15 Sekunden Arbeit belegt eine VM also etwa zwei Minuten lang. Daher:

  • Gib einem Job nur dann eine eigene VM, wenn seine Arbeit etwa 2 Minuten oder länger dauert. Kürzere Prüfungen (Lint, eine kleine Testsuite, ein Guard-Skript) sind als zusätzliche Schritte in einem bestehenden Job günstiger und schneller.
  • Parallelisiere lange, unabhängige Arbeit (Test-Shards, Image-Builds, End-to-End-Suiten) über Jobs hinweg. Genau dafür ist die Nebenläufigkeit da.
  • Parallelisiere kurze Arbeit innerhalb eines Jobs statt über Jobs hinweg, zum Beispiel mehrere tofu plan-Läufe als Hintergrundprozesse eines Schritts. Sie warten meist auf APIs, nicht auf die CPU.
  • Ketten nutzen VMs automatisch wieder. Ein needs:-Job, der bereit wird, während die VM seines Vorgängers noch wartet (innerhalb von 60 Sekunden), läuft ohne Boot auf dieser VM. Ein breiter Fan-out von Jobs, die alle gleichzeitig starten, kann nichts wiederverwenden: Jeder braucht eine eigene VM.
  • Halte einen Lauf innerhalb deiner Nebenläufigkeit. Eine Matrix, die breiter ist als der garantierte Anteil deiner Organisation, läuft in Wellen, und die letzte Welle wartet, bis ein ganzer Job fertig ist. Schneide Shards so zu, dass ein Lauf in eine Welle passt.

Arbeitsspeicher

Eine VM für gehostete Runner hat 4 GiB Arbeitsspeicher und keinen Swap (2 vCPU). Eine größere Größe für gehostete Runner gibt es derzeit nicht.

Ein Job, dem der Speicher ausgeht, scheitert innerhalb von etwa einer Minute. Fällt der verfügbare Speicher unter etwa 4 % (rund 130 MiB), beendet der Runner den größten Prozess des Jobs und bevorzugt dabei Build-Werkzeuge wie node, tsc, java und vitest-Worker. Er sendet zuerst SIGTERM und SIGKILL, wenn der Speicher weiter sinkt. Der Schritt scheitert mit Exit-Code 143 oder 137, und das Schrittprotokoll nennt den Grund:

earlyoom: killed node (pid 4242) because the runner VM ran out of memory. ...

Der Runner selbst ist nie der beendete Prozess, daher werden Ergebnis und Protokolle des Jobs weiterhin gemeldet. Vor dieser Änderung konnte ein Job, der der VM entwachsen war, unbemerkt bis zu seinem timeout-minutes hängen bleiben.

Siehst du diese Zeile, senke den Spitzenverbrauch des Jobs:

  • Verteile die Arbeit auf mehrere Jobs. Ein großer Build neben seinen Integrationstests oder ein docker build neben Test-Containern addiert die beiden Spitzen auf einer VM.
  • Begrenze den Speicher des Werkzeugs. Zum Beispiel NODE_OPTIONS=--max-old-space-size=2048 für Node, --checkers 1 und GOMEMLIMIT für TypeScript 7 (tsc in Go geschrieben), -Xmx für Java, weniger Test-Worker (--maxWorkers / --pool-options).
  • Streamen statt laden. Für große Image-Scans siehe Große Images mit Trivy scannen.

Passt ein Job nicht in 4 GiB, wende dich an support@frem.sh oder führe ihn auf einem passend dimensionierten eigenen Runner aus.

Enthaltene Minuten und Nebenläufigkeit

DetailWert
Enthaltene Minuten1.000 Runner-Minuten pro Nutzer und Monat, gebündelt für die Organisation
Nebenläufigkeit2 gleichzeitige Jobs pro Nutzer garantiert (mindestens 10, höchstens 60); bis zu 60, solange die Plattform freie Kapazität hat. Testphase: 2.
Laufzeitgrenze pro JobStandard 60 Min. (über --timeout); absolute Obergrenze von 6 h, auf der VM durchgesetzt. timeout-minutes: auf Job-Ebene in deiner Workflow-YAML begrenzt auf kürzere Werte; timeout-minutes: auf Schritt-Ebene wird bereits beachtet.
Minutenpreis innerhalb des PoolsKeiner, im Pauschalplan zu 30 € pro Nutzer enthalten
Wenn der Pool aufgebraucht istNeue Jobs werden mit einem eindeutigen Commit-Status-Check fremforge/runner-minutes-cap abgelehnt; laufende Jobs laufen normal zu Ende. Mehrverbrauchsabrechnung ist optional, siehe unten.
Zurücksetzen der MinutenAm 1. jedes Monats; ungenutzte Minuten verfallen

Jobs über dem Limit deiner Organisation warten in der Warteschlange, statt zu scheitern. Wende dich an support@frem.sh, wenn du einen höheren garantierten Anteil brauchst.

Mehrverbrauchsabrechnung (optional): Standardmäßig werden neue Jobs eingereiht, wenn der Pool aufgebraucht ist. Owner der Organisation können die verbrauchsabhängige Abrechnung unter Organisationsverwaltung → Abrechnung → Runner-Minuten-Verbrauch einschalten. Ist sie aktiv, werden Minuten über dem Pool mit 0,01 € pro Minute auf der nächsten Rechnung berechnet. Auf derselben Seite lässt sich der Mehrverbrauch jederzeit wieder abschalten.

Nutzungs-Dashboard

Den Verbrauch an Runner-Minuten verfolgst du unter Organisationsverwaltung → Abrechnung → Runner-Minuten-Verbrauch.

Das Nutzungs-Dashboard zeigt:

AnsichtWas sie zeigt
MonatsübersichtGesamte in diesem Abrechnungszeitraum verbrauchte Minuten im Vergleich zum enthaltenen Pool (1.000 Min. × Anzahl Nutzer)
Aufschlüsselung nach RepositoryVon jedem Repository verbrauchte Minuten, nach Verbrauch sortierbar
Aufschlüsselung nach WorkflowVon jeder Workflow-Datei innerhalb eines Repositorys verbrauchte Minuten
ZeitverlaufTägliches Verbrauchsdiagramm für den aktuellen und den vorherigen Abrechnungszeitraum

Minuten werden vom Start bis zum Ende des Jobs gezählt und auf die nächste volle Sekunde aufgerundet. Wartezeit in der Warteschlange (auf einen Runner) wird nicht gezählt.

Der Monatszeitraum wird am 1. jedes Monats (UTC) zurückgesetzt. Ungenutzte Minuten verfallen.

Minuten eigener Runner werden im selben Dashboard getrennt erfasst und nicht auf den gehosteten Pool angerechnet.

Vom Kunden einstellbare Schwellen für Verbrauchswarnungen gibt es noch nicht – fremforge sendet standardmäßig eine Warn-E-Mail, wenn die Organisation 80 % des Monatspools erreicht. Wende dich an support@frem.sh, um die Schwelle anzupassen.

Inhalt des Runner-Images

Das Image der gehosteten Runner wird wöchentlich neu gebaut (Mo. 06:00 UTC), aus Upstream-Werkzeugversionen, die Renovate verfolgt. Jeder Download zur Build-Zeit ist per sha256 gepinnt (bei Abweichung scheitert der Build). Zur Build-Zeit läuft ein Trivy-CVE-Gate – ein Image mit Befunden der Stufe HIGH oder CRITICAL wird erst ausgeliefert, wenn es gepatcht ist.

Bevor ein neu gebautes Image auf der Runner-Flotte live geht, wird es erneut gegen die Listen bekannt ausgenutzter Schwachstellen (ENISA EUVD und CISA KEV) geprüft. Ein Image mit einer bekannt ausgenutzten CVE, für die ein Fix verfügbar ist, wird nicht ausgerollt, unabhängig vom Schweregrad, es sei denn, eine geprüfte Ausnahme von höchstens 30 Tagen deckt diese CVE ab. Die Runner nutzen das vorherige Image weiter, bis ein gepatchtes gebaut ist.

Zweischichtiges Modell, beide Schichten in der EU:

  • Systemschicht (~300 MB) – Debian-Basis + Systemwerkzeuge in jeweils einer Version (Node 24 LTS, Build-Essentials, Scanner, IaC- und Cloud-CLIs, Browser, Datenbank-Clients). Teil der Boot-Disk der VM; kein Download pro Job.
  • hostedtoolcache-Schicht (~7 GB) – Sprach-Toolchains in mehreren Versionen (Node, Go, Python, .NET, Java, Ruby) + Android SDK + Miniconda3 + R (seit 06.06.2026). Auf derselben Boot-Disk unter /opt/hostedtoolcache eingebrannt. setup-*-Actions lösen von hier ohne Download pro Job auf.

Wie wir mit insgesamt ~7 GB volle Abdeckung liefern, während GitHubs ubuntu-latest ~50 GB groß ist: siehe die Größengeschichte unten.

Vorinstalliert in der Systemschicht:

KategorieWas
BasisDebian 13 („trixie“) slim (glibc – nötig für Ubuntu-kompatible Binärdateien aus setup-*-Actions)
Shell + GNU coreutilsbash, coreutils, util-linux, findutils, grep, sed, gawk, diffutils, tzdata, Locale
Netzwerk + Kryptoca-certificates, curl, wget, openssl, openssh-client, gnupg
Versionsverwaltunggit, git-lfs (systemweit initialisiert), tea (Forgejo-CLI)
Archivetar, gzip, xz, bzip2, unzip, zip, zstd
Build-Essentialsbuild-essential (gcc + make), clang, lld, ninja, cmake, pkg-config, autoconf, automake, libtool
JS-Laufzeit (System)Node 24 LTS + npm + über corepack verwaltetes pnpm & yarn
Weitere Sprachen (System)Rust (rustup stable), PowerShell 7, PHP 8.3 + Composer
JVM-BuildMaven 3.9, Gradle 9.8 (Java-SDKs kommen aus dem hostedtoolcache)
IaCOpenTofu, Bicep, Pulumi, Ansible (über pipx), AWS SAM CLI
Cloud-CLIsAWS CLI v2, Azure CLI, Google Cloud CLI (gcloud + gsutil + bq)
ContainerDocker-CLI + nativer Docker-Daemon (kein DinD-Sidecar – siehe Container-Builds), kaniko, skopeo, kind
K8skubectl, helm, kustomize
Sicherheit + Lintgitleaks, trivy, osv-scanner, cosign, syft, opengrep, scorecard, checkov, cfn-lint, actionlint, hadolint, shellcheck, shfmt, yamllint
Datenformatejq, yq
Datenbank-Clientspostgresql-client, default-mysql-client, sqlite3
Browser + E2EChromium + chromedriver, Firefox ESR + geckodriver, Selenium Server
Inittini als PID 1 (saubere Signalbehandlung + Aufräumen von Zombie-Prozessen)
Executorforgejo-runner (Upstream-act_runner, gepinnt) führt die Schritte von Forgejo Actions nativ auf der VM aus

Vorinstalliert im hostedtoolcache (von setup-*-Actions aufgelöst, kein Download pro Job):

WerkzeugVersionen / Inhalt
Node.js26.x, 24.x, 22.x (LTS)
Go1.27.x, 1.26.x
Python3.14, 3.13, 3.12
.NET SDK10.0, 8.0
Java (Temurin)25, 21, 17
Ruby4.0.x, 3.4.x, 3.3.x
Android SDKcmdline-tools + Plattform 34 + build-tools 34.0.0 + platform-tools; ANDROID_HOME=/usr/local/lib/android/sdk; weitere Plattformen/NDK pro Job über setup-android@v3
Miniconda3conda + Python 3.11; CONDA=/opt/hostedtoolcache/Miniconda3/latest/x64; Umgebungen werden pro Job über setup-miniconda@v3 angelegt
R (CRAN)r-base + r-base-dev 4.3.x; aufgelöst von r-lib/actions/setup-r@v2; CRAN-Pakete werden pro Job installiert

Die maßgebliche Versionsliste mit exakt aufgelösten Versionen, Image-Digest, Zusammenfassung des Trivy-Scans und CycloneDX-SBOM wird mit jedem wöchentlichen Neubau veröffentlicht – den aktuellen Werkzeugstand findest du in der Referenz zum Runner-Image.

Container-Builds: natives Docker oder kaniko

Zwei Wege, wähle nach Workload:

Natives Docker (Standard) – die VM wird mit installiertem und laufendem Docker-Daemon auf dem Host ausgeliefert. Deine docker-CLI spricht ihn über den üblichen lokalen Socket an – keine Umleitung über DOCKER_HOST, kein Sidecar. Funktioniert für docker build, docker compose, docker pull, mehrstufige Builds und BuildKit-Funktionen. Direkter Ersatz für docker auf einem von GitHub gehosteten Runner.

- run: docker build -t myapp:${{ github.sha }} .
- run: docker push myapp:${{ github.sha }} 2>&1 | stdbuf -oL tr '\r' '\n' | grep -vE '^(Waiting|)$'

Große Images mit Trivy scannen

trivy image <ref> (über den Daemon) hält die Layer-Metadaten des gesamten Images im Speicher, während es das Dateisystem durchläuft – Images mit mehreren GB können weit über das Speicherlimit der Runner-VM hinausschießen und mitten im Scan einen OOM-Kill auslösen. Das verlässliche Muster für jede Image-Größe ist, das Image über docker save zu streamen:

- run: docker build -t myapp:${{ github.sha }} .
- run: |
    docker save myapp:${{ github.sha }} | trivy image \
      --severity HIGH,CRITICAL --ignore-unfixed \
      --scanners vuln --pkg-types os \
      --offline-scan --no-progress --quiet \
      --input -

Warum das funktioniert: docker save liest die Layer von der Platte und streamt das Tarball Layer für Layer in Trivys stdin. Trivy liest aus jedem Layer die Metadaten der Betriebssystempakete (~einige Dutzend KB pro Layer) und verwirft sie vor dem nächsten. Der gemeinsame RSS-Spitzenwert bleibt unabhängig von der Gesamtgröße des Images bei etwa 500 MB, sodass der Scan bequem in das Speicherbudget der Runner-VM passt. GOGC=10 vor trivy (z. B. GOGC=10 trivy ...) verkleinert den Heap weiter, falls du einmal nahe an die Grenze kommst.

Der Filter tr '\r' '\n' bei docker push wird empfohlen: Docker schreibt den Fortschritt des Layer-Uploads mit Wagenrücklauf-Überschreibungen, die das Schrittprotokoll von Forgejo Actions als eine einzige, ständig wachsende Zeile behandelt und stillschweigend abschneidet. Ohne den Filter zeigt ein Push, der mitten im Upload hängt oder scheitert, im Protokoll nur <layer>: Preparing ohne Fehlerdetails – bis der Schritt in den Timeout läuft, ist der eigentliche Fehler überschrieben. Die Weiterleitung durch tr macht aus jedem Fortschrittsschritt eine eigene Zeile, die vollständig erfasst wird, und grep -vE '^(Waiting|)$' entfernt leere bzw. untätige Füllzeilen. Der Durchsatz beim Layer-Push bleibt unverändert.

Kunden-Registrys (ghcr.io, Docker Hub, quay.io, deine eigene) sind über den Runner-Egress erreichbar; der Proxy hat einen Timeout von 30 Minuten pro Anfrage, sodass PUTs von Layern mit mehreren GB sauber durchlaufen. Budget für die Image-Größe: 50 GB pro Build (die Grenze der Scratch-Disk der VM) – eröffne ein Ticket, wenn du mehr brauchst.

kaniko – OCI-Builder ohne Daemon und ohne Root. Schnellerer Kaltstart als der vollständige Docker-Daemon, besseres Layer-Cache-Verhalten gegen entfernte Registrys. Nutze es, wenn der Build keine BuildKit-spezifischen Funktionen braucht.

- run: |
    /usr/local/bin/kaniko \
      --context=$GITHUB_WORKSPACE \
      --dockerfile=Dockerfile \
      --destination=registry.example.com/$GITHUB_REPOSITORY:$GITHUB_SHA

Für kind-/testcontainers-Workloads, die verschachtelte KVM-Gäste brauchen, füge das Label dind-privileged zu runs-on hinzu. fremforge leitet diese auf einen VM-Flavor mit aktivierter verschachtelter Virtualisierung.

Größe im Vergleich zu GitHub-gehosteten Runnern

GitHubs ubuntu-latest ist ~50 GB groß, weil es Folgendes einbrennt:

  • Mehrere Versionen pro Sprache – 3 aktuelle Reihen für Node, Python, Java und Ruby; 2 für Go und .NET (~3 GB insgesamt). Die Tabelle oben ist die maßgebliche Auswahl.
  • Android SDK + NDK (~10 GB bei GitHub-gehosteten Runnern) – wir brennen das Kern-SDK ein (Plattform 34 + build-tools + platform-tools, ~3 GB); weitere API-Level, NDK und Emulatoren werden pro Job über setup-android@v3 installiert (ohne erneuten Download dessen, was eingebrannt ist).
  • Vorab gezogene Docker-Images für Service-Container (postgres, mysql, mongo, redis, mssql, ~5 GB) – nicht eingebrannt; ein docker pull holt sie von Docker Hub oder über den Abhängigkeits-Proxy deiner Organisation (siehe Paket-Caches).
  • Microsoft-Edge-Browser – nicht eingebrannt (Chromium ist dieselbe Engine für Headless-Tests in CI).
  • Mono / Julia / Swift / Haskell / Lua / Crystal / Nim / Elixir – nicht eingebrannt (Nischensprachen, pro Job über setup-* oder apt installieren).
  • Mehrere GCC-Versionen, alte bzw. Legacy-Toolchains für die Kompatibilität – wir liefern einen aktuellen Satz.

Werkzeuge, die der typische Workflow nutzt, fehlen uns nicht – jede gängige Sprache, jede große Cloud-CLI, jeder gängige Scanner, beide großen Browser, Docker (nativ, Root über das Label dind-privileged), Android (Kern-SDK) und Data Science (Miniconda3 + R). iOS / Xcode ist mit Linux nicht kompatibel und wird gesondert verfolgt; Nischensprachen (Julia/Swift/Haskell/Mono) werden pro Job installiert. Sag uns Bescheid, wenn dein Team etwas braucht, das wir nicht einbrennen, und wir prüfen es für den nächsten wöchentlichen Neubau des Images.

Alle Details pro Werkzeug findest du unter docs.frem.sh/build/actions/runner-image/.

Eigene Runner (BYO)

Registriere eigene Runner für eine beliebige fremforge-Organisation (On-Premises-Server, Cloud-VMs, Mac minis, Raspberry Pis). Minuten eigener Runner werden nicht auf den gehosteten Pool angerechnet.

Registrierung

  1. Gehe in den Forgejo-Einstellungen der Organisation zu Einstellungen → Actions → Runner → Neuen Runner erstellen (frem.sh/org/<org>/settings/actions/runners).
  2. Kopiere das Registrierungs-Token.
  3. Lade die Binärdatei des Forgejo-Runners herunter. Die aktuelle stabile Version wird unter code.forgejo.org/forgejo/runner/releases veröffentlicht; pinne bei der Installation auf ein aktuelles Tag:
# Linux x86_64 — replace <version> with the tag from the releases page (e.g. v6.4.0)
curl -sSL https://code.forgejo.org/forgejo/runner/releases/download/<version>/forgejo-runner-<version>-linux-amd64 \
  -o forgejo-runner && chmod +x forgejo-runner

# macOS arm64
curl -sSL https://code.forgejo.org/forgejo/runner/releases/download/<version>/forgejo-runner-<version>-darwin-arm64 \
  -o forgejo-runner && chmod +x forgejo-runner

fremforge folgt den aktuellen Forgejo-Releases; Runner mit älteren Binärdateien funktionieren weiter, sollten aber innerhalb eines Release-Zyklus aktualisiert werden, um CVE-Patches zu erhalten.

  1. Registriere den Runner:
./forgejo-runner register \
  --instance https://frem.sh \
  --token <registration-token> \
  --name my-runner \
  --labels linux,self-hosted,my-custom-label
  1. Starte den Runner:
./forgejo-runner daemon

Unterstützte Plattformen

PlattformArchitekturen
Linuxx86_64, ARM64, ARMv7
macOSx86_64, Apple Silicon (ARM64)
Windowsx86_64

Eigene Labels

Die bei der Registrierung vergebenen Labels sind das, worauf du in runs-on: verweist:

jobs:
  deploy:
    runs-on: [self-hosted, my-custom-label]

Mehrere Labels in einer Liste bedeuten „finde einen Runner, der ALLE diese Labels hat“. Das Label self-hosted ist Konvention. Gib es mit an, damit ein BYO-Job nicht versehentlich auf einem gehosteten Runner landet.

Verwende keine reservierten Labels. Die Labels fremforge und linux/amd64 führen zu unserem Pool gehosteter Runner (die veralteten Aliase fremforge-cci und cci sind abgekündigt und führen ebenso dorthin). Wähle für deine eigenen Runner eindeutige Labels (z. B. self-hosted, on-prem, my-team-runner), damit Workflows sie unmissverständlich ansprechen können.

Netzwerkanforderungen an den Runner-Host

Die Runner-Binärdatei öffnet Long-Poll-HTTPS-Verbindungen zu https://frem.sh und klont Repositorys über HTTPS. Aus Sicht des Hosts sind diese Verbindungen nötig:

ZielPortZweck
frem.sh443Connect-RPC-Runner-Protokoll (/api/actions/runner.v1.RunnerService/*), Laufzeit-APIs von Actions, git clone über HTTPS
*.frem.sh443Reserviert für Subdomains von Mandanten, Downloads von Anhängen und Rohdateien
Action-Mirror (transitiv)443Nutzt ein Workflow uses: actions/<name>@<ref>, holt der Runner die Action von frem.sh/<owner>/<repo> – derselbe Host wie oben
Ziele deiner WorkflowsunterschiedlichWelche Ziele deine Workflows auch ansteuern (Deploy-Ziele, Paket-Registrys usw.)

Eingehende Verbindungen sind nicht nötig – der Runner baut alle Verbindungen ausgehend auf. Ein Runner hinter NAT oder einer Firewall funktioniert problemlos, solange er frem.sh:443 erreicht.

Verantwortung für Sicherheit und Isolation

Bei gehosteten Runnern sorgt fremforge für Isolation auf Kernel-Ebene zwischen Commits und zwischen Kunden (jeder Commit läuft in einer frischen, kurzlebigen ECS-VM; Jobs desselben Commits können sie sich teilen). Bei eigenen Runnern liegt die Isolation in deiner Verantwortung. Ein selbst gehosteter Runner, der Workflows aus mehreren Repositorys verarbeitet, teilt Zustand zwischen Jobs, sofern du nicht jeden Runner auf ein einzelnes Repository oder eine Organisation beschränkst.

Führst du Workflows mehrerer Teams oder Vertrauensdomänen aus, behandle deinen eigenen Runner so, wie selbst gehostete Runner bei GitHub Actions behandelt werden:

  • Ein Runner pro Repository (oder pro Vertrauensgrenze), wenn Secrets sensibel sind.
  • Registriere keinen eigenen Runner für ein öffentliches Repository – jeder mit Merge-Rechten könnte eine Workflow-Änderung einbringen, die beliebigen Code auf deinem Runner-Host ausführt.
  • Behandle den Runner-Host wie Produktion: Härte das Betriebssystem, halte die Runner-Binärdatei gepatcht und leite seinen Egress durch deine eigenen SSRF- bzw. Egress-Kontrollen.

Der SSRF-Outbound-Proxy von fremforge gilt NICHT für eigene Runner – nur für gehostete Runner. Die Netzwerkrichtlinie deines Runners ist das, was du auf dem Host einrichtest.

Egress und Netzwerk

RegelDetail
Ausgehendes HTTP/HTTPSErlaubt (Ports 80 und 443)
Ausgehend, andere PortsStandardmäßig blockiert
Eingehende VerbindungenBlockiert, Runner nehmen keine eingehenden Verbindungen an
Metadaten-Endpunkt von T Cloud Public (169.254.x.x)Vom SSRF-Outbound-Proxy blockiert
Private Bereiche nach RFC 1918Vom SSRF-Outbound-Proxy blockiert

Egress-Allowlist auf Organisationsebene: Um Runner-Jobs ausgehenden Zugriff auf weitere CIDRs oder Ports zu erlauben, wende dich an support@frem.sh. Egress-Regeln werden auf Plattformebene konfiguriert.

Der SSRF-Proxy ist eine Untergrenze der Plattform. Er lässt sich nicht abschalten. Er verhindert, dass Runner-Jobs die Instanz-Metadaten von T Cloud Public oder interne Infrastruktur erreichen.

Secrets in Jobs

Secrets werden als Umgebungsvariablen injiziert und in Job-Protokollen maskiert. Jede Protokollzeile, die einen Secret-Wert enthält, wird durch *** ersetzt.

Syntax für den Verweis:

steps:
  - name: Deploy
    env:
      API_KEY: ${{ secrets.MY_API_KEY }}
    run: ./deploy.sh

Geltungsbereiche von Secrets

GeltungsbereichWo festlegenVerfügbar für
Organisations-SecretsOrganisationsverwaltung → SecretsAlle Repositorys der Organisation
Repository-SecretsRepository → Einstellungen → Actions → GeheimnisseNur dieses Repository
Umgebungs-SecretsOrganisationsverwaltung → Umgebungen → <env> → Umgebungs-SecretsJobs, die auf diese benannte Umgebung verweisen

Für Umgebungs-Secrets muss der Job eine Umgebung deklarieren:

jobs:
  deploy:
    runs-on: fremforge
    environment: production
    steps:
      - run: echo ${{ secrets.PROD_API_KEY }}

Für Zugangsdaten von Cloud-Anbietern ist die OIDC-Token-Föderation langlebigen Secrets vorzuziehen. Kein Secret zu speichern, kein Secret zu rotieren.

Fehlerbehebung

Ein Schritt scheitert mit earlyoom: killed <process> ... ran out of memory.

Der Job hat mehr als die 4 GiB Arbeitsspeicher der Runner-VM gebraucht, daher hat der Runner seinen größten Prozess beendet, statt die VM hängen zu lassen. Wie du die Spitze des Jobs senkst, steht unter Arbeitsspeicher.

Der Job hängt in der Warteschlange.

Die häufigste Ursache ist die Nebenläufigkeitsgrenze pro Mandant (2 gleichzeitige Jobs pro Nutzer garantiert, mindestens 10 und höchstens 60; siehe Enthaltene Minuten und Nebenläufigkeit). Die aktuelle Zahl aktiver Jobs siehst du unter Organisationsverwaltung → Abrechnung → Runner-Minuten-Verbrauch. Bist du an der Grenze, warte, bis ein laufender Job fertig ist, oder wende dich für eine Enterprise-on-Demand-Erhöhung an support@frem.sh.

Der docker-Befehl funktioniert, aber eine privilegierte Operation scheitert (z. B. Anlegen eines kind-Clusters mit KVM-Verschachtelung).

Die Standard-VM für runs-on: fremforge betreibt Docker nativ mit den Standardvorgaben für Benutzer ohne Root – docker build / docker compose / docker pull funktionieren ab Werk, Operationen mit zusätzlichen Capabilities (KVM-Verschachtelung, direkter /dev-Zugriff für einen verschachtelten Gast, bestimmte Netzwerkmodi für kind-Cluster) jedoch nicht. Füge das Label dind-privileged zu runs-on hinzu (runs-on: [fremforge, dind-privileged]) – fremforge leitet dann auf einen VM-Flavor mit aktivierter verschachtelter Virtualisierung. Dieselbe Isolation mit einer VM pro Commit, dasselbe Image, nur erweiterte Capabilities.

Für reines Kopieren/Umtaggen von Images zwischen Registrys (ohne Build) funktioniert das vorinstallierte skopeo ganz ohne Docker-Daemon.

Das Secret ist in den Protokollen maskiert, aber der Schritt verhält sich, als wäre es leer.

Das Secret existiert zur Laufzeit (die Maskierung bestätigt das), aber der Wert ist im richtigen Geltungsbereich nicht gesetzt. Prüfe:

  1. Ist das Secret auf Organisations-, Repository- oder Umgebungsebene gesetzt?
  2. Wenn es ein Umgebungs-Secret ist: Deklariert der Job environment: <name> passend zur Umgebung des Secrets?
  3. Ist der Name des Secrets exakt richtig geschrieben (Groß-/Kleinschreibung beachten)?

Die Secrets-Oberfläche im jeweiligen Geltungsbereich zeigt den Namen des Secrets und den Zeitpunkt der letzten Änderung. Prüfe damit, ob das Secret existiert, bevor du dir den Workflow ansiehst.

Querverweise

  • OIDC-Token-Föderation, schlüssellose Cloud-Authentifizierung aus CI-Jobs
  • Secrets, Secrets für Organisation, Repository und Umgebung verwalten
  • Marketplace-Kompatibilität, welche Actions aus dem GitHub Marketplace auf fremforge-Runnern funktionieren
  • KI-Haltung, welche KI-Werkzeuge in der Runner-Umgebung vorhanden sind und welche nicht