CI/CD-Pipelines mit GitHub Actions — Ein praktischer Leitfaden
Die meisten CI/CD-Pipelines sind Flickwerk
Es fängt mit einer einzigen YAML-Datei an, die npm test ausführt. Sechs Monate später: 400 Zeilen Shell-Skripte, hardcodierte Secrets und Steps, die niemand anzufassen wagt. Build-Zeiten schleichen von 2 auf 20 Minuten. Flaky Tests bekommen || true angehängt. Workflow-Änderungen reviewt keiner.
Dieser Leitfaden zeigt, wie Pipelines schnell, sicher und wartbar bleiben — auch wenn das Projekt wächst.
Fang mit dem Dependency-Cache an
Die größte Zeitverschwendung in den meisten Pipelines: bei jedem Run die gleichen Dependencies herunterladen. GitHub Actions hat eingebautes Caching, und die Setup-Actions für Node.js, Go und Python unterstützen es nativ.
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
Für Go-Projekte sowohl den Modul- als auch den Build-Cache cachen:
- uses: actions/setup-go@v5
with:
go-version: '1.23'
cache: true
Allein das kann Build-Zeiten um 30–60 % senken. Die GitHub Actions Cache-Dokumentation beschreibt die Details, aber das Prinzip ist simpel: Lade nie herunter, was Du schon hast.
Für Docker-Builds verhindert Layer-Caching über docker/build-push-action mit cache-from und cache-to das Neubauen unveränderter Layer:
- uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/my-org/my-app:latest
cache-from: type=gha
cache-to: type=gha,mode=max
Das type=gha-Backend speichert Layer-Caches direkt in GitHubs Cache-Infrastruktur — keine externe Registry nötig.
Matrix-Builds für Cross-Platform-Sicherheit
Nur auf einem OS mit einer Runtime-Version zu testen ist eine Wette, dass der Rest egal ist. Matrix-Builds testen Kombinationen automatisch:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
node-version: ['20', '22']
fail-fast: false
Das fail-fast: false-Flag ist wichtig — ohne es bricht eine fehlschlagende Kombination alle anderen ab und versteckt zusätzliche Fehler.
Für Library-Autoren sind Matrix-Builds über Versionen hinweg Pflicht. Für Anwendungsteams fängt das Testen gegen die nächste Minor-Version Deprecations ab, bevor sie Production erreichen. Die GitHub Actions Matrix-Dokumentation beschreibt fortgeschrittene Kombinationen und Ausschlüsse.
Wiederverwendbare Workflows eliminieren Copy-Paste
Bei 10 Repositories mit nahezu identischen CI-Pipelines bedeutet ein Linting-Update 10 PRs. Wiederverwendbare Workflows lösen das:
# .github/workflows/ci-shared.yml (im .github-Repo der Org)
on:
workflow_call:
inputs:
node-version:
type: string
default: '22'
jobs:
lint-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: npm
- run: npm ci
- run: npm run lint
- run: npm test
Consuming-Repos rufen es mit einer Zeile auf:
jobs:
ci:
uses: my-org/.github/.github/workflows/ci-shared.yml@main
with:
node-version: '22'
Eine Änderung, ein PR, alle Repos aktualisiert. Pinne die Referenz auf einen Tag oder SHA für Stabilität — @main ist bequem, bedeutet aber, dass jeder Push zum Shared-Repo sofort alle Consumer betrifft.
Security-Hardening, das wirklich zählt
Action-Versionen auf SHA pinnen
# Schlecht: veränderbarer Tag
- uses: actions/checkout@v4
# Gut: unveränderlicher SHA
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
Tags können verschoben werden. SHA-Referenzen nicht. Der GitHub Security Hardening Guide empfiehlt das, und Tools wie pin-github-action automatisieren die Umstellung.
Minimale Berechtigungen
Standard-GITHUB_TOKEN-Berechtigungen sind zu weit gefasst. Einschränken:
permissions:
contents: read
pull-requests: write
Setze permissions: {} auf Workflow-Ebene und gewähre nur, was jeder Job braucht. Die Permissions-Dokumentation listet jeden Scope auf.
User-Input nicht in der Shell vertrauen
PR-Titel, Branch-Namen und Commit-Messages sind nutzergesteuert. Sie in Shell-Befehle zu interpolieren ermöglicht Injection:
# Gefährlich: PR-Titel könnte $(malicious-command) enthalten
- run: echo "PR: ${{ github.event.pull_request.title }}"
# Sicher: Umgebungsvariable verwenden
- run: echo "PR: $PR_TITLE"
env:
PR_TITLE: ${{ github.event.pull_request.title }}
Umgebungsvariablen werden als Daten übergeben, nicht als Code interpretiert. Das ist der #1 GitHub Actions Sicherheitsfehler und trivial vermeidbar.
Deployment-Patterns
Environment Protection Rules
Für Production-Deployments GitHub Environments mit Required Reviewers und Wait-Timern nutzen:
jobs:
deploy-production:
runs-on: ubuntu-latest
environment:
name: production
url: https://my-app.example.com
steps:
- uses: actions/checkout@v4
- run: ./deploy.sh
Die Environment-Konfiguration in den Repository-Settings steuert, wer approven darf, wie lange gewartet wird und welche Branches deployen können. Infrastructure-Level Gating — kein Custom-Approval-Bot nötig.
Rollback-Strategie
Jeder Deploy-Workflow sollte einen Rollback-Pfad haben. Der einfachste Ansatz: das vorherige erfolgreiche Deployment erneut ausführen.
on:
workflow_dispatch:
inputs:
ref:
description: 'Git-Ref zum Deployen (Tag, SHA oder Branch)'
required: true
default: 'main'
workflow_dispatch mit explizitem Ref-Input ermöglicht das Deployen jeder früheren Version per Hand. Kombiniert mit getaggten Releases ergibt das One-Click-Rollback ohne Production-Server anzufassen.
Pipeline-Gesundheit überwachen
Langsame Pipelines untergraben das Vertrauen. Diese Metriken tracken:
- P50 und P95 Build-Zeit — wenn P95 das 3-fache von P50 ist, gibt es Flaky- oder Resource-Contention-Probleme
- Fehlerrate — über 5 % braucht Untersuchung; über 15 % heißt, Entwickler ignorieren CI
- Time to Green nach Failure — misst, wie schnell das Team auf broken Builds reagiert
GitHubs Workflow-Run-API liefert Timing-Daten. Ein wöchentlicher Slack-Digest dieser Zahlen hält Pipeline-Gesundheit sichtbar — ohne Dashboards, die keiner anschaut.
Das 80/20 von CI/CD
Der meiste Wert kommt aus fünf Dingen:
- Dependencies aggressiv cachen — Build-Zeiten halbieren
- Wiederverwendbare Workflows nutzen — keine 10 Kopien der gleichen Pipeline pflegen
- Actions auf SHAs pinnen — Supply-Chain-Angriffe verhindern
- Minimale Berechtigungen setzen — Blast Radius begrenzen
- Production-Deploys mit Environments gaten — Approval erzwingen ohne Custom-Tooling
Alles andere — Matrix-Builds, Concurrency Groups, Self-Hosted Runner, Composite Actions — ist Optimierung auf einem soliden Fundament. Erst die Basics richtig machen.