← Alle Beiträge

CI/CD-Pipelines mit GitHub Actions — Ein praktischer Leitfaden

Matthias Bruns · · 5 Min. Lesezeit
ci-cd github-actions devops engineering

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:

  1. Dependencies aggressiv cachen — Build-Zeiten halbieren
  2. Wiederverwendbare Workflows nutzen — keine 10 Kopien der gleichen Pipeline pflegen
  3. Actions auf SHAs pinnen — Supply-Chain-Angriffe verhindern
  4. Minimale Berechtigungen setzen — Blast Radius begrenzen
  5. 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.

Lesebarkeit

Schriftgröße