Der Spickzettel zur ersten Hälfte des Seminars GitHub Actions in der Praxis: Wie eine Workflow-Datei aufgebaut ist, welcher Auslöser wann greift, wie Werte zwischen Schritten und Jobs wandern, wann ein Cache und wann ein Artefakt richtig ist und wie sich Schrittfolgen wiederverwenden lassen. Alles, was danach beim Ausrollen, Absichern und Betreiben passiert, steht auf dem zweiten Blatt: GitHub Actions: Betrieb und Lieferkette.
Maßgeblich für Schlüsselnamen und Versionsdetails ist die GitHub-Actions-Dokumentation; dieses Blatt trifft eine Auswahl — die Entscheidungsraster, die einen Versionssprung überleben.
Continuous Integration, Delivery und Deployment
| Stufe | Bauen und Prüfen | Ausrollen |
|---|---|---|
| Continuous Integration | automatisch | von Hand |
| Continuous Delivery | automatisch | auf Knopfdruck |
| Continuous Deployment | automatisch | automatisch |
Der Sprung zu Continuous Deployment ist keine technische Frage, sondern eine der Testabdeckung. Wer Continuous Delivery verspricht, das Ausrollen aber weiterhin aus einem Handbuch bedient, steht auf der ersten Stufe.
Modul: Continuous Integration und Delivery verstehen
Schlüssel eines Schritts
| Schlüssel | Bedeutung | Beispiel |
|---|---|---|
| uses | fertige Action einbinden | actions/checkout@v6 |
| run | Kommando in der Shell | npm test |
| with | Eingaben an die Action | node-version: 22 |
| name | Anzeigename im Log | Tests ausführen |
| id | Referenz für Outputs | build |
uses und run schließen sich in einem Schritt gegenseitig aus. Jeder Schritt
startet in einer eigenen Shell — ein cd wirkt nicht im nächsten Schritt.
jobs:
pruefen:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v7
with:
node-version: 22
- run: npm ci
- run: npm test
npm ci statt npm install — es installiert exakt die Stände aus dem Lockfile
und schlägt bei Abweichung fehl.
Modul: Den ersten Workflow erstellen
Auslöser und Filter
| Ereignis | Startet bei | Typisch für |
|---|---|---|
| push | Commits auf Branch oder Tag | Bauen, Release |
| pull_request | Eröffnen und Aktualisieren | Prüfungen vor dem Merge |
| workflow_dispatch | Klick oder API-Aufruf | Deployment, Wartung |
| schedule | Zeitplan in Cron-Syntax | Nachtläufe, Aufräumen |
| release | Veröffentlichter Release | Artefakte publizieren |
Zeitgesteuerte Läufe starten nicht auf die Minute genau, rechnen in UTC und
pausieren in inaktiven Repositories. workflow_dispatch wird erst sichtbar,
wenn die Datei auf dem Standardbranch liegt.
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
paths-ignore:
- 'docs/**'
- '**.md'
paths und paths-ignore schließen sich gegenseitig aus. branches greift
nicht bei Tags und tags nicht bei Branches — ein Tag-Push löst sonst nichts
aus. Wer Pfade filtert, muss den Required Status Check mitdenken: ausgelassen
ist nicht grün.
Eingaben eines manuellen Laufs nehmen die Typen string, boolean, number,
choice und environment an — choice statt Freitext erspart die Prüfung im
Workflow.
Modul: Ereignisse, Filter und Bedingungen
Bedingungen und Läufe aus Forks
jobs:
deploy:
if: github.ref == 'refs/heads/main'
steps:
- run: ./deploy.sh
- name: Bericht sichern
if: always()
run: ./bericht.sh
In if steht der Ausdruck ohne geschweifte Klammern. Eine als Zeichenkette
geschriebene Bedingung ist immer wahr. failure() und cancelled() sind
verschiedene Fälle, always() deckt beide ab.
Bei pull_request aus einem Fork bekommt der Lauf nur Leserechte und keine
Secrets. pull_request_target läuft dagegen im Kontext des Zielrepositories —
mit Secrets und Schreibrechten. Wer darin den Fork-Code auscheckt und ausführt,
gibt das Repository preis. Sichere Reihenfolge: prüfen ohne Secrets, danach in
einem getrennten Lauf veröffentlichen.
Modul: Ereignisse, Filter und Bedingungen
Kontexte und Outputs
| Kontext | Enthält | Beispiel |
|---|---|---|
| github | Lauf und Ereignis | github.ref, github.sha |
| env | gesetzte Variablen | env.REGISTRY |
| vars | Konfigurationswerte | vars.NODE_VERSION |
| secrets | Geheimnisse | secrets.GITHUB_TOKEN |
| needs | Outputs vorheriger Jobs | needs.build.outputs.version |
| matrix | Werte der aktuellen Kombination | matrix.node |
Kontexte sind nicht überall verfügbar — auf Workflow-Ebene stehen nur github,
inputs und vars bereit.
steps:
- id: version
run: |
V=$(node -p "require('./package.json').version")
echo "wert=$V" >> "$GITHUB_OUTPUT"
outputs:
version: ${{ steps.version.outputs.wert }}
Outputs sind auf 1 MB je Job begrenzt; alles Größere gehört in ein Artefakt.
Der Folgejob sieht sie nur, wenn er den Vorgänger unter needs führt. Die drei
Wege im Vergleich: Outputs für einzelne Werte wie Version, Digest oder
Image-Tag; Artefakte für Dateien, die ein späterer Job unverändert braucht;
Caches für nichts davon — sie dürfen jederzeit fehlen. Werte aus GITHUB_ENV
gelten nur innerhalb desselben Jobs.
echo "::warning file=src/plan.ts,line=42::Fehlender Fall"
echo "::add-mask::$TOKEN"
echo "### Testlauf Beetkarte" >> $GITHUB_STEP_SUMMARY
Annotationen mit file und line erscheinen direkt an der betroffenen Zeile im
Pull Request. add-mask muss vor der ersten Ausgabe stehen, sonst schützt es zu
spät.
Modul: Daten zwischen Schritten und Jobs austauschen
Matrix und Nebenläufigkeit
| Schlüssel | Wirkung | Wann sinnvoll |
|---|---|---|
| include | ergänzt Kombinationen oder Werte | Sonderfall mit Extra-Flag |
| exclude | entfernt Kombinationen | unnötige Paarung sparen |
| fail-fast | bricht alle Jobs beim ersten Fehler ab | schnelles Signal |
| max-parallel | begrenzt gleichzeitige Jobs | knappe Runner-Kapazität |
fail-fast steht standardmäßig auf true — für eine vollständige
Fehlerübersicht auf false setzen. Die Matrix wächst schneller als gedacht:
drei Listen zu je vier Werten ergeben 64 Jobs.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
concurrency:
group: deploy-produktion
queue: max
Zwei Muster, zwei Absichten: Prüfläufe am Branch dürfen abgebrochen werden, nur
der letzte Stand zählt. Deployments dürfen es nicht — sonst bleibt eine Umgebung
halbfertig; dort wartet queue: max (bis zu 100 Läufe). Der Gruppenschlüssel
entscheidet über die Wirkung: zu grob blockiert, zu fein schützt nicht. Enthält
er den Lauf selbst, ist er immer eindeutig und die Gruppe wirkt nie.
timeout-minutes je Job verhindert, dass ein hängender Lauf die Gruppe
blockiert.
Innerhalb eines Jobs steuern fünf Schlüssel parallele Schritte: background
startet einen Schritt und läuft weiter, wait wartet auf benannte
Hintergrundschritte, wait-all auf alle vorherigen, cancel beendet einen
Hintergrundschritt, parallel führt eine Gruppe gemeinsam aus. Die Ausgabe
eines Hintergrundschritts erscheint erst, wenn er abgeschlossen ist — und
background wartet nicht auf Bereitschaft, nur auf den Start.
Modul: Jobs parallelisieren und Build-Zeiten optimieren
Cache oder Artefakt
| Cache | Artefakt | |
|---|---|---|
| Zweck | Laufzeit sparen | Ergebnis weitergeben |
| Verlässlichkeit | darf fehlen | muss da sein |
| Sichtbarkeit | Branch und Elternbranch | im ganzen Lauf, danach Download |
| Aufbewahrung | 7 Tage ohne Zugriff | konfigurierbar, Standard 90 Tage |
| Grenze | 10 GB je Repository | Speicherkontingent |
Ist das Kontingent erschöpft, löscht GitHub die am längsten nicht genutzten Caches zuerst.
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
restore-keys: npm-
Der Key trägt den Lockfile-Hash, sonst bleiben alte Abhängigkeiten ewig liegen;
in einer Matrix gehört zusätzlich runner.os hinein. Gecacht wird das
Verzeichnis des Paketmanagers, nicht node_modules — plattformabhängige Pakete
brechen sonst.
- uses: actions/upload-artifact@v4
with:
name: beetkarte-bundle
path: dist/
retention-days: 7
- uses: actions/download-artifact@v4
with:
name: beetkarte-bundle
Als Artefakt lohnen das gebaute Bundle (damit Test und Deployment dieselbe Datei verwenden), Testberichte und Coverage-Daten sowie Protokolle fehlgeschlagener Oberflächentests — nicht aber Zwischenstände, die schneller neu gebaut als übertragen sind.
Modul: Abhängigkeiten, Caches und Artefakte
Testumgebung in der Pipeline
| Service Container | Testcontainers | |
|---|---|---|
| Definiert in | Workflow-Datei | Testcode |
| Lokal gleich | nein, dort anders | ja, identisch |
| Lebensdauer | ganzer Job | je Testlauf |
| Version steuert | Workflow-Autor | Entwicklerteam |
Testcontainers hält Test und Umgebung zusammen — der Workflow muss dann nur Docker bereitstellen.
services:
db:
image: postgres:18
env:
POSTGRES_PASSWORD: test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
Ohne Health-Check startet der Test womöglich, bevor die Datenbank Verbindungen annimmt.
Modul: Tests und Qualitätsprüfungen automatisieren
Reusable Workflow oder Composite Action
| Reusable Workflow | Composite Action |
|---|---|
| Ersetzt einen oder mehrere Jobs | Ersetzt mehrere Schritte |
| Liegt in .github/workflows | Liegt in beliebigem Verzeichnis |
| Eigene Runner je Job | Läuft auf dem Runner des Aufrufers |
| Kann Environments und Matrix nutzen | Kein eigener Runner, keine Matrix |
jobs:
ci:
uses: gartenwerk/cicd/.github/workflows/ci.yml@v2
with:
node-version: '22'
secrets: inherit
Der Aufruf steht bei den Jobs, nicht bei den Schritten. secrets: inherit
gilt nur innerhalb derselben Organisation, sonst wird jedes Secret einzeln
übergeben. Bis zu zehn Ebenen lassen sich verketten, Schleifen nicht.
Berechtigungen lassen sich in der Kette senken, aber niemals anheben, und der
Schlüssel environment steht in workflow_call nicht zur Verfügung. Inputs
nehmen dort string, number und boolean an.
runs:
using: composite
steps:
- uses: actions/setup-node@v7
with:
node-version: ${{ inputs.node-version }}
cache: npm
- run: npm ci
shell: bash
Ein run-Schritt ohne shell lässt die Action gar nicht erst laden. Outputs
einer Composite Action brauchen zusätzlich ein value mit dem Verweis auf den
Schritt. Und in beiden Fällen gilt: Der Aufruf zeigt auf einen Tag, nicht auf
main — sonst bricht eine fremde Änderung alle Repositories gleichzeitig.
Modul: Workflows und Actions wiederverwenden
Bauarten einer eigenen Action
| Bauart | Geeignet für | Preis |
|---|---|---|
| Skript im Workflow | einmalige Logik | keine Wiederverwendung |
| Composite | Schrittfolgen ohne eigene Logik | nur was der Runner kennt |
| JavaScript | Logik, API-Aufrufe, Fehlerfälle | Build und Abhängigkeiten |
| Container | fremde Werkzeuge, feste Umgebung | nur Linux, langsamer Start |
Die Reihenfolge ist auch die Empfehlung — die einfachste Bauart, die trägt,
gewinnt. Inputs erreichen den Code als Umgebungsvariablen mit dem Präfix INPUT
und dem Namen in Großbuchstaben.
runs:
using: composite
steps:
- shell: bash
run: |
test -f "$INPUT_MANIFEST" || {
echo "::error::Manifest fehlt"; exit 1; }
Ohne exit 1 gilt der Schritt als erfolgreich — auch wenn die Fehlermeldung im
Log steht.
Modul: Eigene Actions entwickeln
Typische Fallen
- Datei am falschen Ort.
.github/workflowohne s wird nie ausgeführt; ein Tabulator in der Einrückung lässt den Lauf gar nicht erst starten. - Checkout vergessen. Ohne
actions/checkoutfindet npm keinepackage.json. - Doppelte Läufe.
pushundpull_requestauf demselben Branch lassen jeden Commit zweimal laufen. - Muster mit zwei Sternen.
docs/**trifft nichtdocsselbst. - Output ohne id. Ein Schritt ohne
idliefert keinen adressierbaren Wert; mehrzeilige Werte brauchen einen Begrenzer. - Wert aus dem Log zurücklesen. Werte gehören über
GITHUB_OUTPUTweiter, nicht über einechound die Log-Auswertung. - Secret im Job Summary. Was dort landet, ist dauerhaft sichtbar — vor jeder Ausgabe prüfen, ob ein Geheimnis enthalten sein könnte.
- Cache als Voraussetzung. Ein Lauf, der ohne Cache-Treffer scheitert, ist kaputt; der Cache darf jederzeit fehlen.
- Artefakt mit
.env. Damit ist jede Zugangsinformation herunterladbar; in einer Matrix kollidieren zudem gleiche Artefaktnamen. - Required Check zeigt ins Leere. Ein umbenannter Job oder eine anders gebildete Matrix lässt jeden Pull Request hängen.
- Neu bauen statt weiterreichen. Baut der Testjob selbst neu, prüft er etwas anderes als das, was ausgeliefert wird.
- Der Linter mit Beruhigungsflag. Ein Flag, das Fehler zu Warnungen macht,
oder ein
continue-on-errorentschärft die Prüfung dauerhaft.