DevOps

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.

OSSDL Lab Veröffentlicht 11 Min. Lesezeit

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.

  1. Hauptzweig schützen: Direkte Pushes sperren, Merge nur über Review und grüne Pipeline.
  2. Ä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.
  3. Vor dem Merge gegen den aktuellen Hauptzweig testen, nicht gegen den Stand von vorgestern (Merge-Result-Pipelines beziehungsweise Merge Queue).
  4. Unfertige Funktionalität hinter einem Flag einbauen und den Branch trotzdem täglich mergen.
  5. 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

  1. GitLab CI/CD – offizielle Dokumentation
  2. GitHub Actions – offizielle Dokumentation
  3. DORA – Forschung zu Software-Delivery-Performance
  4. SLSA – Supply-chain Levels for Software Artifacts
  5. Reproducible Builds

Themen

  • CI/CD
  • DevOps
  • Automatisierung
  • Testing
  • Deployment

Passende Leistung

Dazu arbeiten wir konkret

Kontakt

Technische Herausforderung besprechen

Beschreiben Sie kurz Ihr System und die offene Frage. Wir antworten mit einer fachlichen Einschätzung – ohne Vertriebsschleife.