Git-Pipeline kaputt? So beheben Sie häufige CI/CD- und Git-Probleme
Kaputte CI/CD-Pipelines und Git-Probleme beheben. GitHub Actions Debugging, Merge-Konflikte, Force-Push-Recovery.
Kurzfassung (TL;DR)
CI/CD-Pipelines scheitern aus vorhersehbaren Gründen: Dependency-Fehler, Docker-Build-Probleme, instabile Tests, Merge-Konflikte, falsch konfigurierte Secrets oder fehlende Berechtigungen. Dieser Artikel führt systematisch durch die Fehlersuche für jedes Szenario — mit konkreten Befehlen für GitHub Actions und GitLab CI. Das wichtigste Prinzip: Zuerst die Logs lesen, dann lokal reproduzieren und die eigentliche Ursache beheben, statt blind einen Retry zu starten.
Voraussetzungen
- Grundkenntnisse in Git (Commits, Branches, Push/Pull)
- Zugang zu einer CI/CD-Plattform (GitHub Actions oder GitLab CI)
- Lokal installiertes Git (Version 2.30+)
- Lokal installiertes Docker zur Reproduktion von Build-Problemen
- Ausreichende Repository-Berechtigungen, um Pipeline-Logs einzusehen und Secrets zu verwalten
CI/CD-Logs lesen
GitHub Actions
Die Logs von GitHub Actions sind über den Actions-Tab im Repository erreichbar. Jeder Workflow-Lauf zeigt die einzelnen Jobs an, und jeder Job lässt sich in seine Schritte aufklappen. Fehlgeschlagene Schritte sind rot markiert und automatisch aufgeklappt.
Logs über die Kommandozeile abrufen:
# Letzte Workflow-Läufe anzeigen
gh run list --limit 10
# Einen bestimmten fehlgeschlagenen Lauf anzeigen
gh run view 123456789
# Vollständige Logs herunterladen für Offline-Analyse
gh run view 123456789 --log
# Einen laufenden Workflow in Echtzeit verfolgen
gh run watch 123456789
Worauf man in der Ausgabe achten sollte:
- Exit Codes — ein Exit Code ungleich Null zeigt den exakten Befehl an, der fehlgeschlagen ist.
- Zeitstempel — plötzliche Zeitsprünge deuten auf Netzwerk-Timeouts oder hängende Prozesse hin.
- Der erste Fehler — über Folgefehler hinwegscrollen und die eigentliche Ursache finden. Spätere Fehler sind oft nur Konsequenzen des ersten.
GitLab CI
GitLab-CI-Logs befinden sich unter CI/CD > Pipelines in der Seitenleiste. Man klickt auf die Pipeline und dann auf den jeweiligen Job. GitLab bietet eine nützliche "Im Log suchen"-Funktion direkt im Browser.
# Mit der GitLab CLI (glab)
glab ci status
# Log eines bestimmten Jobs anzeigen
glab ci view <job-id>
# Einen fehlgeschlagenen Job erneut starten
glab ci retry <job-id>
Auf beiden Plattformen kann man Debug-Logging aktivieren, wenn die Standardlogs nicht ausreichen:
# GitHub Actions — im env-Abschnitt des Workflows ergänzen
env:
ACTIONS_RUNNER_DEBUG: true
ACTIONS_STEP_DEBUG: true
# GitLab CI — Variable in der Pipeline-UI oder .gitlab-ci.yml setzen
variables:
CI_DEBUG_TRACE: "true"
Achtung: Debug-Logging kann Secrets in der Log-Ausgabe offenlegen. Nur vorübergehend und bei privaten Repositories aktivieren.
Häufige Pipeline-Fehler
Dependency-Installationsfehler
Der häufigste CI-Fehler ist eine fehlgeschlagene Dependency-Installation. Typischerweise zeigt sich das als Paketmanager-Fehler im Install-Schritt.
Symptome:
npm ERR! 404 Not Found— ein Paket wurde von der Registry entfernt oder die Registry ist nicht erreichbar.pip installscheitert an Versionskonflikten.Could not resolve dependenciesbei Maven oder Gradle.
Diagnose und Behebung:
# Zuerst lokal reproduzieren — Caches leeren, um CI-Bedingungen nachzustellen
npm cache clean --force
rm -rf node_modules package-lock.json
npm install
# Für Python-Projekte
pip install --no-cache-dir -r requirements.txt
# Prüfen, ob eine bestimmte Paketversion noch existiert
npm view some-package@1.2.3
# Lock-Datei fixieren und committen
git add package-lock.json
git commit -m "fix: pin dependency lock file"
Häufige Ursachen:
- Fehlende Lock-Datei — immer
package-lock.json,yarn.lock,Pipfile.lockoderpoetry.lockcommitten. - Authentifizierung bei privaten Registries — CI hat keinen Zugriff auf die lokale
.npmrcoderpip.conf. Registry-Tokens als CI-Secrets konfigurieren. - Node-/Python-Versionsunterschied — exakte Versionen in der CI-Konfiguration angeben, um Überraschungen zu vermeiden.
# GitHub Actions — Node.js-Version festlegen
steps:
- uses: actions/setup-node@v4
with:
node-version: '20.11.0'
cache: 'npm'
Docker-Build-Fehler
Docker-Builds scheitern in CI aus anderen Gründen als lokal — hauptsächlich wegen Caching-Unterschieden und Ressourcenlimits.
# Den CI-Build lokal ohne Cache reproduzieren
docker build --no-cache -t myapp:test .
# Multi-Stage-Build-Probleme isolieren
docker build --target builder -t myapp:builder .
# Layers inspizieren bei Größenproblemen
docker history myapp:test
Häufige Docker-Build-Fehler:
- COPY failed: file not found — der Build-Kontext enthält die Datei nicht.
.dockerignoreprüfen. - Festplatte voll — CI-Runner haben begrenzten Speicherplatz. Multi-Stage-Builds verwenden und in derselben Layer aufräumen.
- Plattform-Mismatch — lokaler Build auf ARM, aber CI läuft auf AMD64 (oder umgekehrt).
# GitLab CI — Docker-Build mit Layer-Caching
build:
stage: build
image: docker:24
services:
- docker:24-dind
variables:
DOCKER_BUILDKIT: "1"
script:
- docker build
--cache-from $CI_REGISTRY_IMAGE:latest
--tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
--tag $CI_REGISTRY_IMAGE:latest
.
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker push $CI_REGISTRY_IMAGE:latest
Test-Fehler
Tests, die lokal bestehen, aber in CI fehlschlagen — umgangssprachlich "Flaky Tests" — gehören zu den frustrierendsten Problemen.
Häufige Ursachen:
- Zeitabhängige Tests — Tests, die auf
sleep()oder Systemzeit basieren. - Fehlende Umgebungsvariablen — Testcode, der
.env-Dateien liest, die in CI nicht vorhanden sind. - Datenbank-/Service-Abhängigkeiten — Tests, die eine laufende Datenbank oder externe API erwarten.
- Reihenfolgeabhängige Tests — Tests, die sequentiell bestehen, aber bei paralleler oder zufälliger Ausführung scheitern.
# Tests lokal in derselben Reihenfolge wie CI ausführen
npx jest --runInBand --forceExit
# Für Python: Testreihenfolge randomisieren, um Abhängigkeiten aufzudecken
pip install pytest-randomly
pytest --randomly-seed=12345
# Nur den fehlschlagenden Test mit ausführlicher Ausgabe laufen lassen
npx jest --verbose --testPathPattern="failing-test"
pytest -xvs tests/test_failing.py
# GitHub Actions — Service-Container für Integrationstests
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: testpass
POSTGRES_DB: testdb
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- run: npm test
env:
DATABASE_URL: postgres://postgres:testpass@localhost:5432/testdb
Merge-Konflikte lösen
Merge-Konflikte sind im engeren Sinne kein CI-Fehler, blockieren aber Merges und können Pipeline-Fehler verursachen, wenn automatische Merges versucht werden.
# Branch mit dem aktuellen Zielbranch aktualisieren
git fetch origin
git checkout feature-branch
git rebase origin/main
# Bei Konflikten zeigt Git die betroffenen Dateien an
git status
# Jede Datei mit Konflikten öffnen und die Markierungen auflösen:
# <<<<<<< HEAD
# (eigene Änderungen)
# =======
# (eingehende Änderungen)
# >>>>>>> origin/main
# Nach dem Auflösen jeder Datei
git add aufgeloeste-datei.txt
# Rebase fortsetzen
git rebase --continue
# Falls es völlig schiefläuft — abbrechen und von vorne anfangen
git rebase --abort
Für komplexe Konflikte empfiehlt sich ein Drei-Wege-Merge-Tool:
# Merge-Tool konfigurieren (z. B. VS Code)
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait --merge $REMOTE $LOCAL $BASE $MERGED'
# Merge-Tool während der Konfliktauflösung starten
git mergetool
Merge vs. Rebase: Rebase erzeugt eine sauberere Historie, schreibt aber Commits um. Auf gemeinsam genutzten Branches lieber git merge origin/main statt Rebase verwenden, um das Umschreiben von Commits zu vermeiden, die andere bereits gepullt haben.
Nach Force Push wiederherstellen
Ein Force Push (git push --force) überschreibt die Remote-Historie. Falls jemand versehentlich einen Force Push über bestehende Arbeit ausführt, ist eine Wiederherstellung über git reflog möglich.
# Das Reflog zeichnet jede Position auf, auf die HEAD gezeigt hat
git reflog
# Die Ausgabe sieht so aus:
# a1b2c3d HEAD@{0}: pull: Fast-forward
# e4f5g6h HEAD@{1}: commit: add user auth module
# i7j8k9l HEAD@{2}: commit: update API endpoint
# Den Commit vor dem Force Push finden und dorthin zurücksetzen
git reset --hard HEAD@{1}
# Oder einen neuen Branch vom verlorenen Commit erstellen
git checkout -b wiederhergestellte-arbeit e4f5g6h
# Den wiederhergestellten Branch pushen
git push origin wiederhergestellte-arbeit
Wichtig: Das Reflog existiert nur auf Rechnern, die die Commits lokal hatten. Falls man die Commits nie gepullt hat, lassen sie sich vom eigenen Rechner nicht wiederherstellen. Bei Teammitgliedern nachfragen oder CI-Build-Artefakte prüfen, die den verlorenen Commit ausgecheckt haben könnten.
Um Force-Push-Katastrophen zu verhindern, stattdessen --force-with-lease verwenden:
# Sicherer Force Push — schlägt fehl, wenn Remote Commits hat, die man nicht kennt
git push --force-with-lease origin feature-branch
# Als Standardverhalten festlegen
git config --global alias.fpush 'push --force-with-lease'
Docker-Builds in CI
Optimierte Docker-Builds in CI reduzieren die Build-Zeiten drastisch. Die wichtigsten Stellschrauben sind Layer-Caching, Multi-Stage-Builds und BuildKit.
# GitHub Actions — Docker-Build mit GitHub Container Registry und Cache
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
Das Dockerfile so strukturieren, dass Cache-Hits maximiert werden:
# GUT: Zuerst Dependency-Dateien kopieren, installieren, dann Quellcode kopieren
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --production=false
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
Secrets und Umgebungsvariablen in Pipelines
Falsch konfigurierte Secrets sind ein stiller Pipeline-Killer — die Pipeline läuft durch, aber die Anwendung scheitert zur Laufzeit oder wird mit fehlender Konfiguration deployt.
GitHub Actions Secrets
# Secrets in einem Workflow referenzieren
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy
env:
API_KEY: ${{ secrets.API_KEY }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
run: |
echo "Deployment mit konfigurierten Secrets..."
./deploy.sh
# Secrets über die CLI setzen
gh secret set API_KEY --body "sk-abc123"
# Aus einer Datei setzen (nützlich für mehrzeilige Secrets wie SSH-Schlüssel)
gh secret set DEPLOY_KEY < ~/.ssh/deploy_key
# Konfigurierte Secrets auflisten (Werte sind verborgen)
gh secret list
# Umgebungsspezifische Secrets setzen
gh secret set API_KEY --env production --body "sk-prod-xyz"
GitLab-CI-Variablen
# .gitlab-ci.yml — Variablen verwenden
deploy:
stage: deploy
script:
- echo "Deployment auf $DEPLOY_HOST"
- scp -i $SSH_PRIVATE_KEY ./build/* user@$DEPLOY_HOST:/app/
variables:
DEPLOY_HOST: "production.example.com"
only:
- main
Fehlende Secrets diagnostizieren:
# Im CI-Skript prüfen, ob ein Secret gesetzt ist (ohne es auszugeben)
if [ -z "$API_KEY" ]; then
echo "FEHLER: API_KEY ist nicht gesetzt"
exit 1
fi
echo "API_KEY ist konfiguriert (Länge: ${#API_KEY})"
Häufige Stolperfallen:
- Secrets stehen bei Pull-Request-Builds aus Forks nicht zur Verfügung (Sicherheitsfunktion).
- Secrets sind in
if:-Bedingungen bei GitHub Actions nicht verfügbar. - GitLab-Variablen mit der Markierung "protected" stehen nur auf geschützten Branches zur Verfügung.
- Mehrzeilige Secrets (SSH-Schlüssel, Zertifikate) erfordern sorgfältige Handhabung — Base64-Encoding verwenden, wenn die Plattform bei Zeilenumbrüchen abschneidet.
Branch Protection und Deploy Keys
Branch-Protection-Regeln
Branch Protection verhindert direkte Pushes und erzwingt Qualitäts-Gates, kann aber auch automatisierte Workflows blockieren, wenn die Konfiguration nicht stimmt.
# GitHub — Branch Protection über die CLI konfigurieren
gh api repos/{owner}/{repo}/branches/main/protection \
--method PUT \
--field required_status_checks='{"strict":true,"contexts":["ci/test","ci/build"]}' \
--field enforce_admins=true \
--field required_pull_request_reviews='{"required_approving_review_count":1}' \
--field restrictions=null
Wenn die Pipeline wegen Branch Protection fehlschlägt:
- "Required status check is failing" — der Check-Name in der CI-Konfiguration muss exakt mit dem in Branch Protection konfigurierten Namen übereinstimmen.
- "Push rejected" — der Bot oder CI-Benutzer hat keine Bypass-Berechtigung. Deploy Key oder App Token mit den richtigen Berechtigungen verwenden.
Deploy Keys
Deploy Keys bieten repository-spezifischen SSH-Zugang, ohne ein persönliches Konto zu verwenden.
# Deploy-Key-Paar erzeugen
ssh-keygen -t ed25519 -C "deploy-key-myrepo" -f deploy_key -N ""
# Öffentlichen Schlüssel zum Repository hinzufügen (standardmäßig nur Lesezugriff)
gh repo deploy-key add deploy_key.pub --title "CI Deploy Key"
# Für Schreibzugriff (Pushes, Tags)
gh repo deploy-key add deploy_key.pub --title "CI Deploy Key" --allow-write
# Privaten Schlüssel als CI-Secret hinzufügen
gh secret set DEPLOY_KEY < deploy_key
# Lokales Schlüsselpaar aufräumen
rm deploy_key deploy_key.pub
Den Deploy Key in einem GitHub-Actions-Workflow verwenden:
steps:
- uses: actions/checkout@v4
with:
ssh-key: ${{ secrets.DEPLOY_KEY }}
Fehlerbehebung — Schnellreferenz
| Symptom | Wahrscheinliche Ursache | Lösung |
|---|---|---|
npm ci scheitert mit ERESOLVE | Lock-Datei nicht synchron | Lokal npm install ausführen, package-lock.json committen |
Docker-Build scheitert bei COPY | Datei durch .dockerignore ausgeschlossen | .dockerignore prüfen, Pfade anpassen |
| Tests lokal erfolgreich, in CI nicht | Fehlende Umgebungsvariablen oder Services | Service-Container ergänzen, Secret-Konfiguration prüfen |
| Pipeline startet nie | Falscher Branch- oder Pfad-Filter | on: / only: / rules: in der CI-Konfiguration prüfen |
| Permission denied beim Push | Branch Protection oder fehlendes Token | Deploy Key oder PAT mit passenden Scopes verwenden |
| Secrets erscheinen als leere Strings | Fork-PR oder geschützte Variable | Auf dem Basis-Repo-Branch ausführen, Variablenschutz prüfen |
| Force Push hat Commits verloren | Kein --force-with-lease verwendet | Über git reflog wiederherstellen, Alias einrichten |
| Cache funktioniert bei Docker nicht | BuildKit nicht aktiviert | DOCKER_BUILDKIT=1 setzen, cache-from verwenden |
Prävention und Best Practices
- Lock-Dateien immer committen. So stellt man sicher, dass CI exakt dieselben Dependency-Versionen wie die lokale Umgebung installiert.
- CI-Tool-Versionen festnageln. Exakte Versionen für Node.js, Python, Docker und sämtliche CLI-Tools in den Workflows angeben.
--force-with-leasestatt--forceverwenden. Als globalen Git-Alias einrichten, damit man nie versehentlich die Remote-Historie überschreibt.- Branch Protection auf Main-/Production-Branches aktivieren. Status-Checks und Code-Review vor dem Merge voraussetzen.
- Secrets am Anfang der Pipeline validieren. Eine einfache Längenprüfung verhindert lange Build-Zeiten, nur um dann im Deploy-Schritt zu scheitern.
- Fehler lokal reproduzieren. Mit
actfür GitHub Actions oder GitLab-CI-Jobs in Docker die CI-Umgebung auf dem eigenen Rechner nachstellen. - Docker-Images klein halten. Multi-Stage-Builds, Alpine-Basisimages und eine ordentliche
.dockerignore-Datei verwenden, um Build-Zeit und Angriffsfläche zu reduzieren. - Secrets regelmäßig rotieren. CI-Secrets wie Produktions-Zugangsdaten behandeln — regelmäßig rotieren und Zugriffe auditieren.
- Pipeline-Dauer überwachen. Eine plötzlich langsame Pipeline deutet oft auf eine Caching-Regression oder eine Dependency hin, die zu viele Daten zieht.
- CI/CD-Setup dokumentieren. Eine kurze Beschreibung jedes Pipeline-Schritts im Repository-README oder einer dedizierten
docs/ci.md-Datei pflegen.
Experten-Hilfe gebraucht?
Pipeline immer noch kaputt? Ich repariere live per Screensharing. €49.
Jetzt buchen — €49100% Geld-zurück-Garantie