Design wechseln

GitHub Actions CI-Pipeline in der Praxis: Build und Test automatisieren

Das Handy vibriert. Eine Nachricht vom Kollegen: „Produktion ist down – dein gestern gemergter Code.“

Kurz der Schock. Lokal liefen die Tests doch? In den Logs zeigte sich später: lokal Node 20, Testumgebung Node 18 – ein API-Verhalten wich ab. In dem Moment ärgert man sich – hätte vor dem Merge eine CI-Pipeline automatisch getestet, wäre das nicht passiert.

Manuelles Testen vergisst man leicht. Wer nicht vergisst, wurde meist schon einmal erwischt. GitHub Actions löst genau das: nach jedem Push automatisch bauen, testen, deployen – die Maschine übernimmt.

Dieser Artikel führt Sie von null zu einer vollständigen CI-Pipeline: kopierfähiges Workflow-Template, Matrix für parallele Mehrfachversionen (oft über die Hälfte Laufzeit gespart) und Praxis aus echten Projekten. Los geht’s.

Kapitel 1: GitHub Actions schnell starten

Was ist GitHub Actions?

Kurz gesagt: GitHub Actions ist die eingebaute Automatisierungsplattform von GitHub. Sie pushen lokal – in der Cloud laufen Test, Build und Deployment.

Früher brauchte man Jenkins-Server mit Wartung und Upgrades. Bei GitHub Actions entfallen Server und Installation: eine YAML-Datei im Repo genügt. Monatlich 2.000 kostenlose Minuten (öffentliche Repos unbegrenzt) – für persönliche Projekte und kleine Teams meist ausreichend.

Gegenüber Jenkins oder Travis CI: tiefe GitHub-Integration, Build-Status direkt im PR, einfache Konfiguration ohne Groovy, tausende Actions im Marketplace. Nachteile: Vendor-Lock-in bei GitHub; sehr komplexe Enterprise-Pipelines sind Jenkins teils flexibler. Für die meisten Projekte reicht GitHub Actions.

Kernbegriffe im Überblick

Anfangs verwirren ein paar Begriffe – in einfachen Worten:

Workflow: Eine YAML-Datei mit dem gesamten Automatisierungsablauf – z. B. „bei jedem Push auf main Tests ausführen“. Liegt unter .github/workflows/.

Job: Eine Gruppe von Schritten im Workflow. Jobs laufen parallel oder mit Abhängigkeiten – zuerst „Test“, danach „Deploy“.

Step: Eine einzelne Aktion im Job, nacheinander – npm test oder actions/checkout@v4.

Runner: Die VM, die den Job ausführt – ubuntu-latest, windows-latest, macos-latest. Eigene Server sind möglich; meist reichen die offiziellen Runner.

Analogie: Workflow = Drehbuch, Job = Szene, Step = Bewegung, Runner = Schauspieler.

Ihr erster CI-Workflow

Nicht zu lange planen – erst laufen lassen. Unter .github/workflows/ci.yml folgenden Inhalt einfügen:

name: CI Pipeline  # Name in der GitHub Actions-Oberfläche

on:
  push:
    branches: [main]  # Trigger bei Push auf main
  pull_request:
    branches: [main]  # Trigger bei PR gegen main

permissions:
  contents: read  # Minimalprinzip: nur Lesezugriff

jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 15

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Build
        run: npm run build

Was passiert hier?

on definiert Trigger: Push auf main oder PR gegen main. permissions mit contents: read – Minimalprinzip, kein versehentliches Überschreiben des Repos. Der Job build auf Ubuntu: Checkout, Node, Dependencies, Test, Build.

Committen, pushen, Actions-Tab öffnen – der grüne Kreis zeigt den laufenden Workflow. Nach wenigen Minuten grüne Häkchen? Ihre erste CI-Pipeline läuft.

Rotes Kreuz? Logs Schritt für Schritt lesen. In den meisten Fällen scheitern Dependency-Install oder Tests selbst – nicht die CI-Konfiguration.

Kapitel 2: Kernkonfiguration der CI-Pipeline

Der Workflow aus Kapitel 1 läuft – für den Alltag fehlen noch Feinheiten. Vier Säulen: Trigger, Berechtigungen, Umgebungsvariablen, Cache. Richtig gesetzt wird die Pipeline sicherer und schneller.

Trigger: wann läuft was?

Am häufigsten: push und pull_request.

on:
  push:
    branches: [main, dev]
    paths:
      - 'src/**'
      - 'package.json'
  pull_request:
    branches: [main]

paths ist praktisch: Änderungen nur in docs/ lösen keine CI aus – spart Minuten und Kosten.

Weitere Optionen:

schedule: Cron, z. B. nächtlicher Build:

on:
  schedule:
    - cron: '0 0 * * *'  # täglich 00:00 UTC (08:00 Peking)

Ein Projekt nutzt das für tägliches npm outdated und E-Mail bei veralteten Paketen.

workflow_dispatch: manueller Start ohne Push – Button „Run workflow“ in der Actions-UI.

on:
  workflow_dispatch:

Berechtigungen: Sicherheit zuerst

Standardmäßig erhält der Workflow ein GITHUB_TOKEN mit Schreibrechten – praktisch, aber riskant: missbrauchte Workflows könnten das Repo verändern.

2021 gab es einen Vorfall: über einen gefälschten PR lief bösartiger Code in der CI. Seitdem gilt: explizit minimale Rechte.

permissions:
  contents: read
  pull-requests: write  # nur wenn PR-Kommentare o. Ä. nötig

Für reine CI (Test + Build) reicht contents: read. Release oder PR-Kommentare? Gezielt ergänzen.

Tipp: in den Repo-Einstellungen Default auf „Read repository contents“ – Workflows starten read-only, Schreiben nur wo deklariert.

Umgebungsvariablen: Ebenen

Drei Ebenen: Workflow, Job, Step – tiefer überschreibt höher.

env:
  NODE_ENV: production
  CI: true

jobs:
  build:
    env:
      BUILD_TARGET: web

    steps:
      - name: Run custom script
        env:
          MY_VAR: hello
        run: echo $MY_VAR

Beispiel: NODE_ENV für alle Jobs auf Workflow-Ebene; BUILD_TARGET nur im build-Job; temporäre Werte im Step.

Sensible Daten nie im Klartext in YAML. Secrets unter Repository Settings anlegen und referenzieren:

steps:
  - name: Deploy to server
    env:
      SSH_KEY: ${{ secrets.SSH_KEY }}
    run: |
      echo "$SSH_KEY" > private.key
      ssh -i private.key user@server 'deploy.sh'

Dependency-Cache: Builds beschleunigen

Hunderte npm-Pakete? Jede CI-Installation von null dauert – in einem Projekt 3 Minuten Install, 1 Minute Test: 75 % der Zeit nur für Dependencies.

setup-node bringt eingebauten Cache:

- uses: actions/setup-node@v4
  with:
    node-version: 20
    cache: 'npm'

Erster Lauf: normale Installation, Cache wird geschrieben. Unveränderte package-lock.json: Wiederherstellung aus Cache – oft von 3 Minuten auf ~10 Sekunden.

Für pnpm/yarn: cache: 'pnpm' bzw. cache: 'yarn'.

Feiner steuerbar mit actions/cache:

- name: Cache dependencies
  uses: actions/cache@v4
  with:
    path: ~/.npm
    key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
    restore-keys: |
      npm-${{ runner.os }}-

key eindeutig – hier Hash der Lock-Datei. restore-keys als Fallback bei Teil-Treffer.

Hohe Trefferquote: ein Projekt von 4 auf 1,5 Minuten – bei 20 Läufen pro Tag summiert sich das.

Kapitel 3: Matrix-Strategie – parallel testen

Eine der stärksten Funktionen und Schwerpunkt dieses Artikels: ein Job wird zu vielen parallelen Jobs – verschiedene Versionen, verschiedene OS. Ein Push, viele Builds gleichzeitig; Gesamtzeit oft deutlich unter serieller Ausführung.

Was ist eine Matrix?

Sie müssen Node 16, 18 und 20 testen. Klassisch: drei Jobs oder eine Version nach der anderen – redundant oder langsam.

Die Matrix ist eine Tabelle: Achsen = Versionen und OS, jede Zelle ein Job. GitHub Actions erzeugt alle Kombinationen parallel.

strategy:
  matrix:
    node: [16, 18, 20]
    os: [ubuntu-latest, windows-latest]

Sechs Jobs – Gesamtzeit ≈ langsamster Job, nicht Summe aller.

Versions-Matrix: mehrere Node-Versionen

Ein Bug: lokal Node 20, Nutzer meldet Fehler unter Node 18 – API-Verhalten unterschiedlich. Frühere Mehrfachversionen-Tests hätten das verhindert.

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        node-version: [16, 18, 20, 22]

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      - run: npm ci
      - run: npm test
  • matrix.node-version: Liste der Versionen
  • ${{ matrix.node-version }} im Step
  • fail-fast: false: ein Fehler stoppt nicht die anderen – sinnvoll für Kompatibilitätstests

include und exclude für feine Steuerung:

strategy:
  matrix:
    node-version: [16, 18, 20]
    os: [ubuntu-latest, windows-latest]
    exclude:
      - node-version: 16
        os: windows-latest
    include:
      - node-version: 20
        os: macos-latest

OS-Matrix: plattformübergreifend

Für CLI-Tools oder plattformabhängigen Code:

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [18, 20]

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      - run: npm ci
      - run: npm test

Plattformunterschiede: Pfade (\ vs /), Tool-Verhalten. Kosten: Linux 2.000 Min/Monat gratis; Windows Faktor 2, macOS Faktor 10. macOS nur wenn nötig; optional separates Workflow mit workflow_dispatch; öffentliche Repos unbegrenzt.

Performance-Tipps

Parallele Jobs sind begrenzt – max-parallel steuern:

strategy:
  max-parallel: 4
  matrix:
    node-version: [16, 18, 20, 22]

Bei vielen Kombinationen schont das das Freikontingent.

Cache pro Matrix-Job – setup-node mit cache reicht meist.

Lint braucht selten jede Node-Version:

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npm run lint

  test:
    needs: lint
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [16, 18, 20]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      - run: npm ci
      - run: npm test

Lint einmal, Test in drei Versionen.

Messung: drei Versionen seriell ~12 Minuten, Matrix parallel ~4 Minuten – grob 8 Minuten gespart pro Durchlauf.

Kapitel 4: Praxis und Fehlersuche

Konfiguration steht – hier eine Checkliste zu Sicherheit, Performance und typischen Fehlern.

Sicherheit

PraxisErklärungBeispiel
permissions explizitNicht auf Defaults verlassenpermissions: { contents: read }
Secrets für SecretsKeine Keys im Klartext${{ secrets.API_KEY }}
Branches einschränkenCI nicht auf jedem Branchbranches: [main]
Actions per SHA pinnenTags sind veränderbaractions/checkout@b4ffde65f46336ab88eb53be808477a39b6bc2b1
timeout setzenHänger verbrauchen Minutentimeout-minutes: 15

Action-Tags wie @v4 sind bequem, aber theoretisch umdeutbar. Produktion: SHA-Pin – Upgrade-Aufwand, mehr Sicherheit.

Performance

TippEffektKonfiguration
Dependency-Cacheoft >50 % bei Installcache: 'npm'
npm cischneller, deterministischrun: npm ci
timeout-minuteskein Minuten-Fressertimeout-minutes: 15
Matrix parallelGesamtzeit oft −60 %+strategy.matrix
concurrencynur letzter Build pro Branchconcurrency.group: ${{ github.ref }}

concurrency: fünf schnelle Pushes → ohne Config fünf Builds; mit cancel-in-progress: true laufen nur die letzten.

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

Häufige Fehler

MeldungUrsacheLösung
Permission deniedzu wenig Rechtepermissions prüfen
Cache not foundKey passt nichtkey, package-lock.json
npm ERR! networkTimeout RegistryTimeout erhöhen oder Mirror
Out of memoryNode-Heap zu kleinNODE_OPTIONS=--max_old_space_size=4096
EACCES permission deniedDateirechtechmod +x script.sh
Error: Cannot find moduleInstall unvollständignpm ci-Log prüfen

Netzwerk: langsames npm – Mirror in .npmrc:

- name: Configure npm registry
  run: echo "registry=https://registry.npmmirror.com" > .npmrc

Speicher:

env:
  NODE_OPTIONS: --max_old_space_size=4096

Cache: erster Lauf ohne Cache normal; package-lock.json für npm ci; cache zum Paketmanager passend.

Fazit

Kernpunkte: eine YAML-Datei für CI; minimale permissions; Umgebungsvariablen in Ebenen; Cache halbiert Install oft; Matrix macht Mehrfachversionen einfach.

Template aus Kapitel 1 kopieren, Node-Version und Befehle anpassen – CI läuft. Erst starten, dann verfeinern. Matrix mit zwei Node-Versionen zeigt den Effekt paralleler grüner Häkchen.

Probleme mit GitHub Actions? Kommentieren Sie – häufige Fälle fließen in die Tabelle von Kapitel 4 ein.

GitHub Actions CI-Pipeline einrichten

Von null bis zur vollständigen CI-Pipeline mit automatischem Build und Test

⏱️ Estimated time: 30 min

  1. 1

    Step 1: Workflow-Verzeichnis anlegen

    Im Projektroot das Verzeichnis `.github/workflows/` erstellen – dort liegen alle Workflow-Dateien.
  2. 2

    Step 2: Basis-CI-Konfiguration schreiben

    Datei `ci.yml` anlegen: Trigger (push/PR), Berechtigungen (Minimalprinzip), Job-Schritte (checkout, setup-node, install, test, build).
  3. 3

    Step 3: Dependency-Cache aktivieren

    Beim Schritt `setup-node` den Parameter `cache: 'npm'` setzen – npm-Abhängigkeiten werden automatisch gecacht.
  4. 4

    Step 4: Matrix für Mehrfachversionen konfigurieren

    `strategy.matrix` ergänzen und Node-Versionen angeben (z. B. [16, 18, 20]) für parallele Tests.
  5. 5

    Step 5: Committen und Build-Ergebnis prüfen

    Konfiguration committen, nach GitHub pushen und auf der Actions-Seite Status und Logs kontrollieren.

FAQ

Wie hoch ist das monatliche Freikontingent bei GitHub Actions?
Private Repositories: 2.000 Minuten pro Monat (Linux Runner), öffentliche Repositories unbegrenzt. Windows Runner verbrauchen das 2-Fache von Linux, macOS das 10-Fache.
Auf welchen Branches soll die CI laufen?
Empfehlung: nur main/master und PRs gegen main. Auf Entwicklungsbranches CI überspringen spart Ressourcen. Mit `paths` Dokumentationsänderungen ausschließen.
Warum npm ci statt npm install in der CI?
npm ci ist schneller und deterministischer: Installation strikt nach package-lock.json, ohne Änderung der Lock-Datei – ideal für CI. npm install kann Versionen verschieben und Builds instabil machen.
Wie viel Zeit spart die Matrix-Strategie?
Praxiswert: drei Node-Versionen seriell ca. 12 Minuten, mit Matrix parallel ca. 4 Minuten (abhängig von der langsamsten Variante) – oft über 60 % weniger Gesamtzeit.
Warum greift der Cache manchmal nicht?
Der Cache-Schlüssel basiert auf dem Hash von package-lock.json. Ändert sich die Lock-Datei, ist der Cache ungültig. Beim ersten Lauf fehlt er – normal. `cache` muss zum Paketmanager passen (npm/pnpm/yarn).
Wie nutze ich sensible Daten (API Keys, SSH-Schlüssel) in der CI?
GitHub Secrets: unter Repository-Einstellungen anlegen, im Workflow mit `${{ secrets.KEY_NAME }}` referenzieren. Secrets werden in Logs automatisch maskiert.

8 Min. Lesezeit · Veröffentlicht am: 6. Apr. 2026 · Aktualisiert am: 9. Juli 2026

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog