Du kennst das Szenario: Ein Feature ist fertig entwickelt, aber bis es live geht, vergehen Wochen. Manuelle Tests, umständliche Deployments, nervöse Release-Nächte. Am Ende schleichen sich doch Fehler ein, weil irgendjemand einen Schritt vergessen hat. DevOps und CI/CD Pipelines lösen genau dieses Problem — und sie sind kein Hexenwerk, wenn du weißt, wo du anfangen musst.
In diesem Leitfaden zeige ich dir Schritt für Schritt, wie du eine moderne CI/CD Pipeline aufbaust, welche Tools sich bewährt haben und wie du von manuellen Deployments zu automatisierten, zuverlässigen Releases wechselst. Mit echten Code-Beispielen, die du direkt in dein Projekt übernehmen kannst.
1. Was ist DevOps — und warum reicht "nur CI/CD" nicht?
DevOps ist keine einzelne Technologie, sondern eine Kultur. Es geht darum, die Mauer zwischen Entwicklung (Dev) und Betrieb (Ops) einzureißen. Statt getrennter Silos arbeiten Teams gemeinsam an einem Ziel: Software schnell, sicher und zuverlässig zum Nutzer bringen.
CI/CD (Continuous Integration / Continuous Delivery) ist ein zentraler Baustein von DevOps, aber nicht der einzige. DevOps umfasst auch Monitoring, Incident Management, Infrastructure as Code und eine Feedback-Kultur, die kontinuierliche Verbesserung ermöglicht.
Die DevOps-Kernprinzipien
- Automatisierung: Alles, was wiederholt wird, gehört in ein Script oder eine Pipeline.
- Continuous Feedback: Monitoring, Alerting und Post-Mortems statt Schuldzuweisungen.
- Infrastructure as Code (IaC): Server-Konfigurationen werden versioniert wie Anwendungscode.
- Shared Responsibility: Wer Code schreibt, ist auch mitverantwortlich für dessen Betrieb.
- Small Batches: Kleine, häufige Releases statt großer, riskanter Deployments.
2. Die CI/CD Pipeline im Überblick: Vom Commit zum Production-Deployment
Eine CI/CD Pipeline automatisiert den Weg deines Codes vom Repository bis in die Produktionsumgebung. Jede Stufe fungiert als Qualitätsgate — scheitert ein Schritt, wird der Prozess gestoppt und du bekommst sofort Feedback.
Pipeline-Visualisierung
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ COMMIT │───▶│ BUILD │───▶│ TEST │───▶│ STAGE │───▶│ DEPLOY │
│ │ │ │ │ │ │ │ │ │
│ Git Push │ │ Compile │ │ Unit │ │ Staging │ │ Prod │
│ Lint │ │ Bundle │ │ Integr. │ │ E2E │ │ Rollout │
│ Format │ │ Docker │ │ Security │ │ Smoke │ │ Monitor │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
Feedback Feedback Feedback Feedback Feedback
< 1 Min. < 5 Min. < 10 Min. < 15 Min. < 20 Min.Das Ziel: Vom Commit bis zum Live-Deployment in unter 20 Minuten — vollautomatisch und mit maximaler Sicherheit. Jeder Schritt liefert sofortiges Feedback, sodass Fehler dort behoben werden, wo sie entstehen.
3. GitHub Actions: Deine CI/CD Pipeline in der Praxis
GitHub Actions ist einer der beliebtesten CI/CD-Dienste — direkt in GitHub integriert, kostenlos für Open-Source-Projekte und mit einem riesigen Marketplace an fertigen Actions. Hier ist ein produktionsreifes Workflow-Beispiel:
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# ────────────────────────────────────────
# Stage 1: Lint & Type Check
# ────────────────────────────────────────
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- run: bun run lint
- run: bun run typecheck
# ────────────────────────────────────────
# Stage 2: Tests
# ────────────────────────────────────────
test:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- run: bun test
- run: bun test:integration
env:
DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}
# ────────────────────────────────────────
# Stage 3: Build & Push Docker Image
# ────────────────────────────────────────
build:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.ref == 'refs/heads/main' }}
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
# ────────────────────────────────────────
# Stage 4: Deploy to Production
# ────────────────────────────────────────
deploy:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy to server
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
script: |
cd /opt/app
docker compose pull
docker compose up -d --remove-orphans
docker system prune -fDieser Workflow deckt den kompletten Zyklus ab: Linting, Tests, Docker-Build und Deployment. Beachte die needs-Abhängigkeiten — jede Stage läuft erst, wenn die vorherige erfolgreich war.
4. Docker Multi-Stage Builds: Schlanke Production-Images
Docker-Container sind das Rückgrat moderner Deployments. Mit Multi-Stage Builds trennst du die Build-Umgebung von der Runtime und erhältst minimale, sichere Images. Hier ein Beispiel für eine Node.js-Anwendung:
# Dockerfile
# ── Stage 1: Dependencies ──────────────────
FROM node:22-slim AS deps
WORKDIR /app
COPY package.json bun.lockb ./
RUN npm install --omit=dev --ignore-scripts
# ── Stage 2: Build ─────────────────────────
FROM node:22-slim AS build
WORKDIR /app
COPY package.json bun.lockb ./
RUN npm install
COPY . .
RUN npm run build
# ── Stage 3: Production ───────────────────
FROM node:22-slim AS production
WORKDIR /app
# Sicherheit: Nicht als root laufen
RUN addgroup --system appgroup && \
adduser --system --ingroup appgroup appuser
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/build ./build
COPY --from=build /app/public ./public
COPY package.json .
USER appuser
EXPOSE 3000
ENV NODE_ENV=production
CMD ["npm", "start"]Ergebnis: Das finale Image enthält nur den kompilierten Code und Produktions-Dependencies — keine Dev-Tools, keine Source-Maps, kein unnötiger Ballast. Ein typisches React-App-Image schrumpft so von 1,2 GB auf unter 200 MB.
Docker Compose für die lokale Entwicklung
Damit jedes Teammitglied die gleiche Umgebung hat, definierst du alle Services in einer docker-compose.yml:
# docker-compose.yml
services:
app:
build:
context: .
target: build
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
- STRAPI_API_URL=http://cms:1337
depends_on:
- cms
- db
cms:
image: strapi/strapi:latest
ports:
- "1337:1337"
environment:
- DATABASE_CLIENT=postgres
- DATABASE_HOST=db
- DATABASE_PORT=5432
- DATABASE_NAME=strapi
- DATABASE_USERNAME=strapi
- DATABASE_PASSWORD=strapi
depends_on:
- db
db:
image: postgres:16-alpine
environment:
- POSTGRES_DB=strapi
- POSTGRES_USER=strapi
- POSTGRES_PASSWORD=strapi
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
pgdata:Ein einziges docker compose up startet die komplette Entwicklungsumgebung: App, CMS und Datenbank. Kein "Works on my machine" mehr.
5. Infrastructure as Code mit Terraform
Manuelle Server-Konfiguration ist fehleranfällig und nicht reproduzierbar. Mit Terraform definierst du deine gesamte Infrastruktur als Code — versioniert, reviewbar und automatisiert ausrollbar.
# main.tf – Cloud-Infrastruktur für die Anwendung
terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
version = "~> 1.45"
}
}
}
variable "hcloud_token" {
sensitive = true
}
provider "hcloud" {
token = var.hcloud_token
}
# ── Server ──────────────────────────────────
resource "hcloud_server" "app" {
name = "wao-production"
server_type = "cpx21"
image = "ubuntu-24.04"
location = "fsn1"
ssh_keys = [hcloud_ssh_key.deploy.id]
user_data = <<-EOF
#!/bin/bash
apt-get update && apt-get install -y docker.io docker-compose-plugin
systemctl enable docker
usermod -aG docker deploy
EOF
labels = {
environment = "production"
managed_by = "terraform"
}
}
# ── Firewall ────────────────────────────────
resource "hcloud_firewall" "app" {
name = "app-firewall"
rule {
direction = "in"
protocol = "tcp"
port = "80"
source_ips = ["0.0.0.0/0", "::/0"]
}
rule {
direction = "in"
protocol = "tcp"
port = "443"
source_ips = ["0.0.0.0/0", "::/0"]
}
rule {
direction = "in"
protocol = "tcp"
port = "22"
source_ips = ["0.0.0.0/0", "::/0"]
}
}
resource "hcloud_firewall_attachment" "app" {
firewall_id = hcloud_firewall.app.id
server_ids = [hcloud_server.app.id]
}
# ── SSH Key ─────────────────────────────────
resource "hcloud_ssh_key" "deploy" {
name = "deploy-key"
public_key = file("~/.ssh/deploy.pub")
}
# ── DNS ─────────────────────────────────────
output "server_ip" {
value = hcloud_server.app.ipv4_address
}Mit terraform plan siehst du vorab, welche Änderungen vorgenommen werden. terraform apply führt sie aus. Die gesamte Infrastruktur ist damit reproduzierbar und in Git versioniert — genau wie dein Anwendungscode.
6. Die Testing-Pyramide: Qualität systematisch sicherstellen
Automatisierte Tests sind das Sicherheitsnetz deiner Pipeline. Ohne Tests ist CI/CD nur schnelles Deployment von Bugs. Die Testing-Pyramide gibt dir eine bewährte Struktur:
╱╲
╱ ╲
╱ E2E╲ ← Wenige, aber kritische Tests
╱ Tests╲ (Cypress, Playwright)
╱────────╲ ~5% der Tests | ~30 Min.
╱Integra- ╲
╱ tions- ╲ ← API-Tests, DB-Tests
╱ Tests ╲ (Supertest, Testing Library)
╱─────────────────╲ ~15% der Tests | ~5 Min.
╱ ╲
╱ Unit-Tests ╲ ← Schnell, isoliert, zahlreich
╱ (Vitest, Jest) ╲ ~80% der Tests | ~30 Sek.
╱──────────────────────────╲Die Regel: Je weiter unten in der Pyramide, desto mehr Tests solltest du haben. Unit-Tests sind schnell und günstig. E2E-Tests sind langsam und teuer, aber decken echte Nutzer-Szenarien ab.
Praktische Umsetzung in der Pipeline
- Unit-Tests laufen bei jedem Commit (< 30 Sekunden). Sie testen einzelne Funktionen und Komponenten isoliert.
- Integrationstests laufen bei jedem Pull Request (< 5 Minuten). Sie prüfen das Zusammenspiel von Modulen, API-Endpunkten und Datenbankzugriffen.
- E2E-Tests laufen vor dem Production-Deployment (< 30 Minuten). Sie simulieren echte Nutzerinteraktionen im Browser.
7. Monitoring und Alerting: Wissen, was nach dem Deployment passiert
Deployment ist nicht das Ende der Pipeline — es ist der Anfang. Ohne Monitoring fliegst du blind. Prometheus und Grafana sind der De-facto-Standard für Observability in der DevOps-Welt.
# prometheus/alerts.yml
groups:
- name: application
rules:
# ── Hohe Fehlerrate ─────────────────────
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
> 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "Fehlerrate über 5%"
description: >
Die HTTP-5xx-Fehlerrate liegt bei
{{ $value | humanizePercentage }}.
Bitte sofort prüfen.
# ── Langsame Antwortzeiten ──────────────
- alert: HighLatency
expr: |
histogram_quantile(0.95,
rate(http_request_duration_seconds_bucket[5m])
) > 2
for: 10m
labels:
severity: warning
annotations:
summary: "P95 Latenz über 2 Sekunden"
description: >
Die 95. Perzentil-Latenz liegt bei
{{ $value }}s. Performance prüfen.
# ── Container-Neustart ──────────────────
- alert: ContainerRestarting
expr: |
increase(
kube_pod_container_status_restarts_total[1h]
) > 3
labels:
severity: warning
annotations:
summary: "Container startet wiederholt neu"
description: >
Container {{ $labels.container }} im Pod
{{ $labels.pod }} hat in der letzten Stunde
{{ $value }} Neustarts.Diese Alerts informieren dich sofort, wenn nach einem Deployment etwas schiefgeht: steigende Fehlerraten, langsame Antwortzeiten oder instabile Container. Zusammen mit automatischen Rollbacks entsteht ein Sicherheitsnetz, das schnelle Releases erst möglich macht.
8. Vorher vs. Nachher: Der Unterschied in Zahlen
Die Transformation von manuellen Deployments zu einer automatisierten CI/CD Pipeline lässt sich in konkreten Metriken messen. Hier ein realistischer Vergleich aus unseren Kundenprojekten:
| Metrik | Manuell (vorher) | CI/CD Pipeline (nachher) |
|---|---|---|
| Deployment-Frequenz | 1x pro Monat | Mehrmals täglich |
| Lead Time (Commit → Live) | 2-4 Wochen | 15-30 Minuten |
| Deployment-Dauer | 2-4 Stunden (manuell) | 5-10 Minuten (automatisiert) |
| Fehlerrate nach Release | 15-25% | 2-5% |
| Mean Time to Recovery (MTTR) | 4-8 Stunden | 10-30 Minuten |
| Manuelle Schritte pro Deploy | 12-20 Schritte | 1 (Git Push) |
| Nacht- und Wochenend-Deploys | Häufig (weniger Traffic) | Jederzeit (kein Risiko) |
| Testabdeckung | ~20% (manuell) | 80%+ (automatisiert) |
Die beeindruckendste Veränderung ist die Deployment-Frequenz: Statt eines großen, riskanten monatlichen Releases deployest du mehrmals täglich kleine, überschaubare Änderungen. Jedes einzelne Deployment birgt weniger Risiko, und wenn doch etwas schiefgeht, ist der Rollback trivial.
9. Schritt für Schritt zur ersten Pipeline
Du musst nicht alles auf einmal umsetzen. Hier ist ein pragmatischer Fahrplan für den Einstieg:
Phase 1: Continuous Integration (Woche 1-2)
- Richte einen GitHub Actions Workflow ein, der bei jedem Push Linting und Unit-Tests ausführt.
- Führe Branch Protection Rules ein: Kein Merge ohne grüne Pipeline.
- Integriere Code-Qualitäts-Tools wie ESLint und TypeScript-Checks.
Phase 2: Containerisierung (Woche 3-4)
- Erstelle ein Multi-Stage Dockerfile für deine Anwendung.
- Richte eine
docker-compose.ymlfür die lokale Entwicklung ein. - Erweitere die Pipeline um den Docker-Build-Schritt.
Phase 3: Continuous Delivery (Woche 5-6)
- Automatisiere das Deployment auf eine Staging-Umgebung.
- Füge Integrations- und E2E-Tests hinzu, die auf Staging laufen.
- Implementiere das automatische Production-Deployment nach erfolgreichem Staging-Test.
Phase 4: Observability (Woche 7-8)
- Richte Prometheus und Grafana für Monitoring ein.
- Definiere Alerts für die wichtigsten Metriken.
- Implementiere automatische Rollbacks bei kritischen Alerts.
"Perfektion ist nicht erreichbar, aber wenn wir Perfektion anstreben, erreichen wir Exzellenz." — Vince Lombardi. Starte klein, iteriere schnell und verbessere kontinuierlich. Das ist der DevOps-Weg.
10. Häufige Fehler und wie du sie vermeidest
- Zu komplexer Start: Fang nicht mit Kubernetes an, wenn ein einfacher Docker-Container auf einem VPS reicht. Skaliere die Infrastruktur mit den Anforderungen.
- Tests ignorieren: Eine Pipeline ohne Tests ist nur ein automatisierter Weg, Bugs schneller zu deployen. Investiere in die Testing-Pyramide.
- Secrets im Code: Niemals Passwörter, API-Keys oder Tokens in die
docker-compose.ymloder den Workflow committen. Nutze GitHub Secrets oder einen Vault. - Kein Rollback-Plan: Jedes Deployment muss rückgängig gemacht werden können. Blue-Green Deployments oder Canary Releases machen Rollbacks trivial.
- Monitoring vergessen: Ohne Observability weißt du nicht, ob dein Deployment erfolgreich war. "Es wurden keine Fehler gemeldet" ist nicht gleich "Es gibt keine Fehler".
Bereit für schnellere, sicherere Releases?
DevOps und CI/CD sind keine Raketenwissenschaft — aber die Implementierung erfordert Erfahrung und ein durchdachtes Vorgehen. Bei WAO helfen wir Unternehmen, ihre Deployment-Prozesse zu automatisieren und eine DevOps-Kultur zu etablieren. Von der ersten Pipeline bis zum vollständig automatisierten Release-Prozess.
Unsere DevOps- und QA-Services umfassen die komplette Toolchain: CI/CD-Pipelines, Container-Orchestrierung, Infrastructure as Code, Monitoring und automatisiertes Testing. Damit du dich auf das konzentrieren kannst, was zählt: großartige Software bauen.
