CI/CD: Grundlagen, Aufbau und Praxisregeln
Continuous Integration, Delivery und Deployment sauber getrennt: Pipeline-Stufen mit Laufzeitzielen, Artefakt-Weitergabe, Rollback, Feature Flags und DORA-Metriken.
Voraussetzungen
Benötigt
- Ein Projekt in einem Git-Repository
- Zugang zu GitLab CI, GitHub Actions oder einem vergleichbaren CI-System
Getestet mit
- GitLab CI (GitLab 18.x)
- GitHub Actions
Kaum ein Begriffspaar wird so oft synonym verwendet und so oft falsch verstanden wie CI und CD. In Projektgesprächen heißt „wir haben CI/CD“ meist: es existiert eine YAML-Datei, die bei jedem Push Tests startet. Das ist Continuous Integration im engeren Sinn – und noch keine Continuous Delivery. Der Unterschied ist nicht akademisch: Er entscheidet darüber, ob ein Release ein Routinevorgang von 15 Minuten ist oder ein terminierter Sonderfall mit Freigaberunde, Wartungsfenster und Nervosität.
Die Kurzfassung: Continuous Integration heißt, dass alle Entwickler ihre Änderungen mindestens täglich in den Hauptzweig integrieren und jede Integration automatisch verifiziert wird. Continuous Delivery heißt, dass am Ende jeder erfolgreichen Pipeline ein deploybares Artefakt steht, das jederzeit auf Knopfdruck in Produktion gehen könnte – die Freigabe bleibt eine Geschäftsentscheidung. Continuous Deployment heißt, dass diese Freigabe entfällt: Jeder grüne Commit auf dem Hauptzweig geht automatisch in Produktion. CI ist Voraussetzung für CD; Continuous Deployment ist der Spezialfall von Continuous Delivery ohne manuelles Gate.
Dieser Artikel trennt die Begriffe, zeigt den Aufbau einer Pipeline in Stufen mit konkreten Laufzeitzielen, liefert lauffähige Beispiele für GitLab CI und GitHub Actions und benennt die Regeln, die eine Pipeline über Jahre brauchbar halten: reproduzierbarer Build, Artefakt einmal bauen und weiterreichen, Secrets über den Secret-Store, kurzlebige Branches, Rollback als vollwertiger, geübter Vorgang und DORA-Metriken als Steuerungsgrößen.
Die drei Begriffe sauber getrennt
Continuous Integration: der Hauptzweig bleibt integrierbar
CI ist zuerst eine Arbeitspraxis, dann ein Werkzeug. Die Praxis: kleine Änderungen, mindestens täglich in den Hauptzweig, jede Integration durch eine automatische Verifikation abgesichert. Das Werkzeug ist die Pipeline, die Lint, Typprüfung, Unit- und Integrationstests ausführt. Wer einen Feature-Branch drei Wochen offen hält und dann eine Pipeline darauf laufen lässt, betreibt automatisierte Tests – aber keine Continuous Integration. Der Integrationsschmerz wird dabei nicht kleiner, er wird nur verschoben.
- Continuous Integration
Praxis, bei der jede Änderung mindestens täglich in den gemeinsamen Hauptzweig integriert wird und jede Integration automatisch gebaut und getestet wird. Ein fehlgeschlagener Hauptzweig hat Vorrang vor jeder Feature-Arbeit.
Continuous Delivery: jederzeit releasefähig
Continuous Delivery erweitert CI um die Deployment-Fähigkeit. Nach der Verifikation entsteht ein versioniertes, unveränderliches Artefakt – ein Container-Image, ein Debian-Paket, ein signiertes Archiv – das durch dieselbe automatisierte Kette in Staging deployt und dort per Smoke-Test geprüft wird. Der Zustand, den Sie erreichen wollen: Der Hauptzweig ist zu jedem Zeitpunkt in einem Zustand, in dem er in Produktion gehen könnte. Ob er es tut, entscheidet ein Mensch.
Continuous Deployment: kein manuelles Gate
Continuous Deployment entfernt das Gate. Jeder grüne Commit erreicht Produktion, ohne dass jemand einen Knopf drückt. Das setzt technisch dreierlei voraus: eine Testsuite, der Sie tatsächlich vertrauen, eine risikoarme Deployment-Strategie mit automatischer Rückabwicklung und eine Trennung von Deployment und Release über Feature Flags. Ohne diese drei Bausteine ist Continuous Deployment kein Reifegrad, sondern ein Risiko.
| Merkmal | Continuous Integration | Continuous Delivery | Continuous Deployment |
|---|---|---|---|
| Auslöser | Push oder Merge Request | Merge in den Hauptzweig | Merge in den Hauptzweig |
| Endzustand der Pipeline | Verifizierter Commit | Deploybares Artefakt in Staging | Änderung läuft in Produktion |
| Manuelles Gate | nicht relevant | ja, bewusst gesetzt | nein |
| Notwendige Testtiefe | Unit, Lint, Typprüfung | zusätzlich Integration, Smoke | zusätzlich automatisches Rollback |
| Typische Blocker | lange Branch-Laufzeit | manuelle Staging-Aufbauten | flakige Tests, fehlende Flags |
Die Pipeline in Stufen: Ziele und Laufzeitbudgets
Eine Pipeline ist keine Liste von Befehlen, sondern eine Abfolge von Filtern mit absteigender Häufigkeit und steigenden Kosten. Billige Prüfungen laufen zuerst und scheitern schnell; teure Prüfungen laufen nur, wenn die billigen bestanden sind. Entscheidend ist die Rückmeldezeit: Was länger als etwa zehn Minuten braucht, wird nicht mehr abgewartet – der Entwickler wechselt den Kontext, und der Kontextwechsel kostet mehr als die Pipeline selbst.
| Pipeline-Stufe | Ziel | Akzeptable Laufzeit |
|---|---|---|
| Lint, Format, Typprüfung | Syntax- und Stilfehler ausschließen, bevor Rechenzeit fließt | unter 60 s |
| Unit-Tests | Fachlogik isoliert verifizieren, hohe Ausführungsdichte | unter 3 min |
| Build und Artefakt | ein unveränderliches, versioniertes Artefakt erzeugen | unter 5 min |
| Security- und Abhängigkeitsscan | bekannte CVEs, Lizenzverstöße, SBOM erzeugen | unter 3 min |
| Integrationstests | Zusammenspiel mit Datenbank, Queue, externen APIs | unter 10 min |
| Deployment Staging | Artefakt in produktionsnaher Umgebung ausrollen | unter 3 min |
| Smoke-Test | Grundfunktionen und Health-Endpunkte prüfen | unter 2 min |
| Deployment Produktion | dasselbe Artefakt ausrollen, Rollback bereithalten | unter 5 min |
Als Faustregel: Feedback auf einem Merge Request unter zehn Minuten, Durchlauf bis Staging unter zwanzig Minuten. Wenn Sie das nicht erreichen, ist die erste Maßnahme nicht ein größerer Runner, sondern Parallelisierung, Test-Splitting und ein funktionierender Abhängigkeits-Cache.
Beispiel: GitLab CI
Das folgende Beispiel zeigt die Stufenfolge mit einem Artefakt, das genau einmal gebaut und über den unveränderlichen Commit-SHA weitergereicht wird:
stages: [verify, build, staging, production]
default:
image: node:22-bookworm-slim
interruptible: true
variables:
npm_config_cache: "$CI_PROJECT_DIR/.npm"
IMAGE: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"
lint:
stage: verify
cache:
key:
files: [package-lock.json]
paths: [.npm/]
script:
- npm ci --prefer-offline
- npm run lint
- npm run typecheck
unit:
stage: verify
script:
- npm ci --prefer-offline
- npm run test:unit -- --coverage
artifacts:
when: always
reports:
junit: reports/junit.xml
expire_in: 1 week
build-image:
stage: build
image: docker:28-cli
services: [docker:28-dind]
variables:
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs"
DOCKER_TLS_VERIFY: 1
DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"
script:
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
- docker build --pull -t "$IMAGE" .
- docker push "$IMAGE"
deploy-staging:
stage: staging
environment:
name: staging
script:
- ./deploy.sh staging "$IMAGE"
- ./smoke-test.sh "$STAGING_URL"
deploy-production:
stage: production
environment:
name: production
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manual
script:
- ./deploy.sh production "$IMAGE"
interruptible: true bricht überholte Pipelines ab, sobald ein neuer Commit auf demselben Branch landet – vorausgesetzt, im Projekt ist das automatische Abbrechen redundanter Pipelines aktiviert. Das ist der billigste Weg, Runner-Kapazität freizubekommen. Die vier DOCKER_*-Variablen sind kein Beiwerk: Der dind-Dienst erzeugt in aktuellen Versionen beim Start selbst TLS-Zertifikate unter DOCKER_TLS_CERTDIR und nimmt Verbindungen auf Port 2376 nur über TLS an – ohne die passenden Variablen findet der Client den Daemon nicht. Details zu Stufen, Regeln und Environments stehen in der GitLab-CI-Dokumentation.
Beispiel: GitHub Actions
Dieselbe Struktur in GitHub Actions, mit minimalen Berechtigungen und dem kurzlebigen, auf den Job begrenzten GITHUB_TOKEN statt eines dauerhaft im Repository hinterlegten Registry-Passworts:
name: pipeline
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
verify:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: '22'
cache: npm
- run: npm ci
- run: npm run lint
- run: npm run test:unit
package:
needs: verify
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v5
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
provenance: mode=max
sbom: true
Zwei Details sind wichtiger, als sie aussehen: permissions auf Repository-Ebene defensiv auf contents: read setzen und nur dort erweitern, wo ein Job tatsächlich schreibt. Und Actions auf einen Commit-SHA statt auf einen Tag zu pinnen, sobald es um Jobs mit Schreibrechten geht – ein Tag ist verschiebbar. Im Beispiel oben stehen der Lesbarkeit halber Tags; produktiv gehört dort der vollständige SHA. Wie sich diese Kette insgesamt absichern lässt, behandeln wir in Software Supply Chain absichern.
Regeln, die eine Pipeline dauerhaft brauchbar halten
Das Artefakt einmal bauen und weiterreichen
Der häufigste Architekturfehler in CI-Systemen: Für Staging und Produktion wird jeweils neu gebaut. Damit testen Sie ein anderes Artefakt als das, was in Produktion läuft – unterschiedliche Basis-Images, aktualisierte transitive Abhängigkeiten, ein anderer Zeitstempel im Build. Richtig ist: einmal bauen, unter einem unveränderlichen Identifikator ablegen (Commit-SHA, nicht latest), und genau dieses Artefakt durch alle Umgebungen schieben. Umgebungsspezifisches gehört in Konfiguration und Secrets zur Laufzeit, nicht in den Build.
Reproduzierbarer Build statt „läuft bei mir“
Ein Build ist reproduzierbar, wenn derselbe Quellstand zweimal dasselbe Ergebnis liefert. Praktisch bedeutet das: Lockfiles im Repository und npm ci statt npm install, Basis-Images per Digest statt per Tag referenzieren, keine Netzwerkzugriffe während des Builds außer auf einen kontrollierten Paketspiegel, und Build-Zeitstempel normalisieren. Das Reproducible-Builds-Projekt dokumentiert die typischen Quellen von Nichtdeterminismus. Wie Layer-Caching und Multi-Stage-Builds dabei zusammenspielen, beschreiben wir in Docker im produktiven Einsatz.
Flakige Tests nicht tolerieren
Ein Test, der bei unverändertem Code manchmal rot ist, ist schlimmer als kein Test: Er trainiert das Team darauf, rote Pipelines zu ignorieren. Ab diesem Punkt ist die gesamte Testsuite als Signal wertlos. Behandeln Sie Flakiness wie einen Produktionsfehler – mit Ticket, Ursachenanalyse und Frist.
Pipeline as Code und Secrets im Secret-Store
Die Pipeline-Definition gehört ins Repository, wird im Merge Request reviewt und ist damit nachvollziehbar. In Web-UIs zusammengeklickte Build-Jobs sind nicht versionierbar, nicht reviewbar und nicht reproduzierbar. Ebenso klar ist die Regel für Zugangsdaten: nichts im Repository, nichts in Umgebungsvariablen im Klartext-Log. Nutzen Sie den Secret-Store des CI-Systems, kurzlebige OIDC-Tokens gegenüber Cloud-Anbietern und maskierte, geschützte Variablen, die nur auf geschützten Branches verfügbar sind. Für den Zugriff auf eigene Server gilt dasselbe Prinzip wie in Linux-Server absichern: keine dauerhaften Schlüssel mit weitreichenden Rechten.
Trunk-based Development gegen langlebige Branches
Lange Feature-Branches und Continuous Integration sind ein Widerspruch. Je länger ein Branch lebt, desto weiter divergiert er, desto größer der Merge, desto höher das Risiko. Trunk-based Development kehrt das um: kurzlebige Zweige mit einer Lebensdauer von Stunden bis maximal zwei Tagen, kleine Merge Requests, ein einziger Integrationszweig. Unfertige Funktionalität wird nicht durch einen Branch versteckt, sondern durch ein Feature Flag.
- Hauptzweig schützen: Direkte Pushes sperren, Merge nur über Review und grüne Pipeline.
- Änderungen klein halten: Die brauchbare Grenze liegt dort, wo ein Reviewer die Änderung noch in einem Durchgang vollständig lesen kann. Wird sie größer, entsteht Zustimmung ohne Prüfung.
- Vor dem Merge gegen den aktuellen Hauptzweig testen, nicht gegen den Stand von vorgestern (Merge-Result-Pipelines beziehungsweise Merge Queue).
- Unfertige Funktionalität hinter einem Flag einbauen und den Branch trotzdem täglich mergen.
- Roten Hauptzweig sofort reparieren oder den verursachenden Commit zurücknehmen – vor jeder anderen Aufgabe.
Deployment-Strategien, Rollback und Feature Flags
Wie das Artefakt in Produktion kommt, bestimmt das Risiko einer einzelnen Änderung stärker als die Testtiefe. Die drei praxisrelevanten Muster unterscheiden sich in Ressourcenbedarf und Rückabwicklungsgeschwindigkeit.
| Strategie | Ablauf | Rollback | Kosten und Grenzen |
|---|---|---|---|
| Rolling Update | Instanzen werden stufenweise ersetzt | erneutes Rolling zurück, Minuten | günstig; erfordert Kompatibilität beider Versionen gleichzeitig |
| Blue/Green | zweite vollständige Umgebung, Umschalten am Router | Umschalten zurück, Sekunden | doppelte Kapazität; Datenbankmigrationen bleiben der Knackpunkt |
| Canary | kleiner Traffic-Anteil auf die neue Version, metrikbasierte Ausweitung | Anteil auf null, Sekunden | benötigt belastbare Metriken und Traffic-Steuerung |
Rollback als vollwertiger Vorgang
Ein Rollback, das im Ernstfall improvisiert wird, ist kein Rollback. Es muss dieselbe Qualität haben wie das Deployment: automatisiert, getestet, dokumentiert, mit einem festen Zeitziel – unter fünf Minuten von der Entscheidung bis zum stabilen Zustand. Der wunde Punkt sind fast immer Datenbankmigrationen. Die belastbare Praxis ist eine Trennung in zwei Schritte: Schemaänderungen erst additiv und rückwärtskompatibel ausrollen, den Code danach, und die alte Spalte erst entfernen, wenn kein laufender Code sie mehr liest.
-- Schritt 1: additiv, alter Code läuft weiter
ALTER TABLE accounts ADD COLUMN email_normalized text;
-- Schritt 2: Backfill in Batches, außerhalb der Migration
-- Schritt 3: Code liest die neue Spalte
-- Schritt 4 (späteres Release): alte Spalte entfernen
Feature Flags entkoppeln Deployment und Release
Deployment ist ein technischer Vorgang, Release eine fachliche Entscheidung. Feature Flags trennen beides: Der Code liegt in Produktion, ist aber inaktiv, bis er für definierte Nutzergruppen eingeschaltet wird. Das erlaubt kleine Merges bei unfertigen Funktionen und ein Abschalten im Sekundenbereich, ohne Deployment. Der Preis ist Disziplin – jedes Flag ist temporärer Code mit zwei Pfaden. Ohne Ablaufdatum und Aufräumroutine sammeln sich diese Verzweigungen an, bis niemand mehr sagen kann, welcher Pfad in Produktion tatsächlich durchlaufen wird. Wie sich solche Verzweigungen in gewachsenen Systemen kontrollieren lassen, ist Thema von Legacy-Software modernisieren.
DORA-Metriken als Steuerungsgrößen
Für die Bewertung einer Delivery-Pipeline haben sich vier Kennzahlen aus der DORA-Forschung durchgesetzt: Deployment-Frequenz, Vorlaufzeit von Commit bis Produktion, Anteil fehlerhafter Änderungen und Wiederherstellungszeit nach einem Ausfall. Ihr Wert liegt in der Kombination: Die ersten beiden messen Tempo, die letzten beiden Stabilität. Wer nur Tempo optimiert, produziert Ausfälle; wer nur Stabilität optimiert, produziert Stillstand.
Erheben lassen sich alle vier Werte aus vorhandenen Daten: Git-Historie und Deployment-Log für Frequenz und Vorlaufzeit, Incident-Tracking für Fehlerquote und Wiederherstellungszeit. Die aktuellen Vergleichswerte und die Methodik dokumentiert dora.dev.
Fazit
CI/CD ist keine Werkzeugentscheidung, sondern eine Kette von Zusagen: dass der Hauptzweig integrierbar bleibt, dass ein Artefakt genau einmal entsteht und unverändert bis Produktion wandert, dass Rückmeldung in Minuten statt Stunden kommt und dass eine Rückabwicklung so gut geübt ist wie das Deployment selbst. Die Werkzeuge – GitLab CI, GitHub Actions und andere – sind austauschbar; diese Zusagen sind es nicht. Wer eine bestehende Pipeline verbessern will, beginnt mit zwei Messungen: der Laufzeit je Stufe und der Frage, ob Staging und Produktion tatsächlich dasselbe Artefakt ausführen. Beide Antworten führen fast immer direkt zu den lohnendsten Eingriffen. Beim Aufbau und Umbau solcher Pipelines unterstützen wir im Rahmen unserer Arbeit zu DevOps und Cloud.
Quellen und weiterführende Dokumentation
Passende Leistung