5 Workflow-Anatomie

Ein GitHub Actions Workflow ist mehr als nur eine YAML-Datei mit Konfigurationsanweisungen. Es ist ein lebendiges Konstrukt, das auf Ereignisse reagiert, Aufgaben orchestriert und dabei eine klare, hierarchische Struktur befolgt. Bevor wir uns in die Syntax-Details stürzen, lohnt sich ein Blick auf das große Ganze – das mentale Modell, das uns hilft, Workflows nicht nur zu schreiben, sondern zu verstehen.

Stellen wir uns einen Workflow wie eine Fabrikproduktion vor: Ein Auslöser (etwa eine Bestellung) startet die Produktion. Die Produktionslinie besteht aus mehreren Stationen (Jobs), die nacheinander oder parallel arbeiten. Jede Station führt konkrete Handgriffe (Steps) aus, für die sie teils spezialisierte Werkzeuge (Actions) einsetzt. Diese Analogie mag vereinfacht sein, trägt aber das grundlegende Prinzip: Workflows sind strukturierte Automatisierungsketten mit klar definierten Ebenen.

5.1 Die vier Ebenen der Hierarchie

GitHub Actions folgt einem strikten, hierarchischen Aufbau. Diese Hierarchie ist nicht willkürlich gewählt, sondern spiegelt die logische Aufteilung von Automatisierungsaufgaben wider. Schauen wir uns die einzelnen Ebenen von oben nach unten an.

Der Workflow bildet die oberste Ebene. Er ist die Datei im .github/workflows-Verzeichnis und definiert, wann die Automatisierung startet (on:), welche Jobs ausgeführt werden und wie diese zusammenhängen. Ein Repository kann beliebig viele Workflows enthalten – einen für CI-Tests, einen für Deployments, einen für Release-Automatisierung. Jeder Workflow ist eine eigenständige Einheit mit eigenem Lebenszyklus.

Jobs sind logische Arbeitseinheiten innerhalb eines Workflows. Jeder Job läuft in einer isolierten Umgebung auf einem Runner (einer virtuellen Maschine oder einem Container). Diese Isolation ist wichtig: Jobs teilen sich standardmäßig keine Dateien oder Umgebungsvariablen. Ein typischer CI/CD-Workflow könnte drei Jobs haben: build, test und deploy. Jobs können sequenziell oder parallel ausgeführt werden – eine Entscheidung, die wir über Abhängigkeiten steuern.

Steps sind die konkreten Anweisungen innerhalb eines Jobs. Sie werden sequenziell abgearbeitet, einer nach dem anderen. Ein Step kann entweder ein Shell-Kommando ausführen (run:) oder eine vorgefertigte Action aufrufen (uses:). Steps teilen sich den Workspace und können auf Dateien zugreifen, die vorherige Steps erstellt haben. Hier findet die eigentliche Arbeit statt: Code auschecken, Dependencies installieren, Tests ausführen, Builds erstellen.

Actions sind die unterste Ebene – wiederverwendbare Bausteine, die komplexe Aufgaben kapseln. Eine Action ist selbst ein kleines Programm, das in einem Step aufgerufen wird. Sie kann in TypeScript geschrieben sein, als Docker-Container vorliegen oder selbst wieder aus mehreren Steps bestehen (Composite Actions). Actions sind das Geheimnis der Produktivität in GitHub Actions: Statt jedes Mal Docker-Login-Logik neu zu implementieren, rufen wir einfach docker/login-action@v3 auf.

Die Hierarchie lässt sich tabellarisch so zusammenfassen:

Ebene Scope Isolation Typische Anzahl Ausführung
Workflow Repository Pro Datei 1–10 pro Repo Event-gesteuert
Job Workflow Eigener Runner 1–5 pro Workflow Parallel/Sequenziell
Step Job Shared Workspace 5–20 pro Job Sequenziell
Action Step In-Process 0–mehrere pro Step Per Step-Aufruf

5.2 Der Auslösemechanismus: Events und Trigger

Ein Workflow ohne Trigger ist wie ein Programm ohne main()-Funktion – er existiert, wird aber nie ausgeführt. Der on:-Schlüssel in der Workflow-Datei definiert, welche Ereignisse den Workflow starten. Diese Events können aus verschiedenen Quellen stammen und haben unterschiedliche Charakteristiken.

Repository-Events sind die häufigste Trigger-Art. Sie entstehen durch Aktionen im Repository selbst: ein Push auf einen Branch, das Öffnen eines Pull Requests, das Erstellen eines Issues. GitHub unterscheidet dabei zwischen dem Event-Typ (z.B. push) und optionalen Filtern (z.B. nur für main-Branch oder bestimmte Dateipfade). Ein typisches Beispiel:

on:
  push:
    branches: [ main, develop ]
    paths:
      - 'src/**'
      - 'tests/**'

Dieser Workflow startet nur bei Pushes auf main oder develop und nur, wenn sich Dateien in src/ oder tests/ geändert haben. Diese Granularität verhindert unnötige Workflow-Läufe und spart Runner-Zeit.

Zeitbasierte Trigger folgen dem Cron-Format und starten Workflows nach Zeitplan. Sie eignen sich für regelmäßige Wartungsaufgaben, Nightly Builds oder Reports. Die Syntax 0 2 * * * bedeutet: jeden Tag um 2 Uhr morgens UTC. Ein wichtiger Hinweis aus der Praxis: Scheduled Workflows laufen nur auf dem Default-Branch. Wer in einem Feature-Branch mit Cron-Triggern experimentiert, wird enttäuscht – erst nach dem Merge werden sie aktiv.

Manuelle Trigger über workflow_dispatch ermöglichen es, Workflows on-demand aus der GitHub-UI oder via API zu starten. Sie können sogar Eingabeparameter definieren, etwa eine Zielumgebung für ein Deployment. Das macht Workflows zu flexiblen Tools, die sowohl automatisiert als auch manuell einsetzbar sind.

External Triggers über repository_dispatch erlauben es, Workflows von außerhalb GitHubs anzustoßen – etwa von einer CI/CD-Pipeline eines anderen Systems oder einem Monitoring-Tool. Das Event trägt einen benutzerdefinierten event_type, über den verschiedene Workflows auf unterschiedliche externe Signale reagieren können.

5.3 Der Workflow-Lebenszyklus

Wenn ein Event eintritt, durchläuft GitHub einen präzisen Ablauf, um zu entscheiden, welche Workflows gestartet werden:

Sobald ein Event eintritt – sei es ein Push, ein PR-Comment oder ein Cron-Tick – notiert GitHub den zugehörigen Commit-SHA und Git-Ref. Das ist entscheidend: Der Workflow läuft mit genau der Version der Workflow-Datei, die in diesem Commit existiert. Änderungen an der Workflow-Datei in späteren Commits wirken sich nicht rückwirkend auf bereits gestartete Läufe aus.

GitHub durchsucht nun das .github/workflows/-Verzeichnis im Repository und prüft alle YAML-Dateien. Für jeden Workflow wird geprüft: Passt der on:-Trigger zum eingetretenen Event? Sind eventuelle Filter (Branches, Paths, Types) erfüllt? Wenn ja, wird ein Workflow-Lauf in die Queue gestellt.

Ein wichtiges Detail: Bei manchen Events – etwa workflow_dispatch oder repository_dispatch – muss die Workflow-Datei auf dem Default-Branch existieren, selbst wenn das Event auf einem anderen Branch ausgelöst wird. Das schützt vor Missbrauch durch beliebige Feature-Branches.

Jeder Workflow-Lauf ist eine eigenständige Instanz. Mehrere Läufe desselben Workflows können gleichzeitig aktiv sein, etwa wenn zwei Entwickler parallel pushen. Die Umgebungsvariablen GITHUB_SHA und GITHUB_REF halten in jedem Lauf fest, auf welchen Commit und Branch er sich bezieht. Das ermöglicht es, im Workflow selbst auf den Kontext zuzugreifen, etwa um einen Build mit der Commit-Message zu taggen.

5.4 Jobs: Parallelität und Abhängigkeiten

Jobs innerhalb eines Workflows verhalten sich standardmäßig wie nebenläufige Prozesse – sie starten gleichzeitig, sobald der Workflow ausgelöst wird. Diese Parallelität ist gewollt: Während der test-Job Unit-Tests ausführt, kann der lint-Job gleichzeitig die Code-Qualität prüfen. Zeit ist wertvoll, und GitHub Actions nutzt sie effizient.

Manchmal brauchen wir aber Kontrolle über die Reihenfolge. Ein Deployment sollte erst starten, wenn Build und Tests erfolgreich waren. Dafür gibt es das needs:-Keyword, mit dem wir Abhängigkeiten zwischen Jobs deklarieren:

jobs:
  build:
    runs-on: ubuntu-latest
    steps: [...]
  
  test:
    needs: build
    runs-on: ubuntu-latest
    steps: [...]
  
  deploy:
    needs: [build, test]
    runs-on: ubuntu-latest
    steps: [...]

Hier entsteht eine Pipeline: build läuft zuerst, dann test, und erst wenn beide erfolgreich waren, startet deploy. Schlägt build fehl, werden test und deploy gar nicht erst gestartet – das spart Ressourcen und gibt schnelles Feedback.

Jobs laufen auf Runnern, die GitHub bereitstellt oder die wir selbst hosten. Die Wahl des Runners über runs-on: hat praktische Konsequenzen: Ein Linux-Runner startet in Sekunden, ein macOS-Runner kann länger brauchen. Windows-Systeme haben andere vorinstallierte Tools als Linux. Matrix-Strategien, die wir später betrachten werden, nutzen diese Flexibilität, um Code auf mehreren Plattformen gleichzeitig zu testen.

5.5 Steps und Actions: Wo die Arbeit geschieht

Steps sind die kleinsten Ausführungseinheiten und das Werkzeug, mit dem wir konkrete Aufgaben umsetzen. Ein Step kann entweder Shell-Befehle ausführen oder eine Action aufrufen – oder beides kombinieren. Die Entscheidung, wann wir ein run: und wann ein uses: einsetzen, folgt pragmatischen Überlegungen.

Ein run:-Step ist direkt und transparent. Wir sehen im Workflow-File, was passiert:

- name: Install dependencies
  run: npm ci

Für einfache Kommandos ist das perfekt. Wird die Logik komplexer – etwa ein mehrzeiliges Setup-Script oder eine Interaktion mit externen APIs – kann der Workflow schnell unübersichtlich werden. Hier kommen Actions ins Spiel.

Eine Action ist eine gekapseltes Stück Funktionalität, das wir per uses: einbinden. Der Klassiker ist actions/checkout@v4, der den Repository-Code in den Workspace kopiert. Der Step wird dadurch zur Deklaration:

- name: Checkout code
  uses: actions/checkout@v4
  with:
    fetch-depth: 0

Das with:-Mapping übergibt Parameter an die Action. Was im Hintergrund passiert – Git-Operationen, Token-Handling, Submodule – ist abstrahiert. Wir arbeiten auf einer höheren Ebene.

Actions stammen aus drei Quellen: dem offiziellen actions/-Namespace von GitHub, dem Community-Marketplace oder aus unserem eigenen Repository. Die Versionierung über Tags oder SHAs stellt sicher, dass Workflows reproduzierbar bleiben. @v4 referenziert eine Major-Version, @v4.1.2 eine exakte Version, @abc123def einen konkreten Commit.

Der gemeinsame Workspace aller Steps eines Jobs ist das verbindende Element. Der erste Step checkt Code aus, der zweite baut ihn, der dritte führt Tests aus – jeder arbeitet im selben Verzeichnis. Diese Kontinuität macht Jobs zu kohärenten Einheiten, während die Isolation zwischen Jobs saubere Trennungen erzwingt.

5.6 Das Zusammenspiel verstehen

Die Workflow-Anatomie ist keine abstrakte Theorie, sondern ein Werkzeug zum Denken. Wenn wir einen neuen Workflow planen, fragen wir uns:

Diese Fragen strukturieren unsere Arbeit. Ein einfacher CI-Workflow könnte aus einem Job mit fünf Steps bestehen. Ein komplexes CD-System hat vielleicht fünf Jobs mit je zehn Steps und nutzt dutzende Actions. Die Hierarchie skaliert mit der Komplexität.

Ein letzter Punkt, der oft übersehen wird: Workflows sind versioniert wie Code. Jeder Lauf verwendet die Workflow-Definition, die zum Zeitpunkt des Triggers im Repository lag. Das bedeutet auch: Experimente mit Workflows können wir in Feature-Branches durchführen, ohne Produktions-Workflows zu beeinflussen. Erst mit dem Merge gehen Änderungen live. Diese Git-basierte Versionierung macht Workflows wartbar, nachvollziehbar und kollaborativ – wie allen Code, den wir schreiben.