und Dry-Run-Modus
Ein Release-Skript ohne Dry-Run-Modus ist ein Blindflug in die Produktion. Mit Checkpoint-Flags, --dry-run, Rollback-Logik und einem vollständigen Schritt-für-Schritt-Protokoll entstehen Release-Skripte, die man vorher testen, bei Fehlern abbrechen und nach Unterbrechungen fortsetzen kann.
Inhaltsverzeichnis
- 1. Warum Release-Skripte Dry-Run und Checkpoints brauchen
- 2. Grundarchitektur eines Release-Skripts
- 3. Dry-Run-Modus: Befehle ankündigen statt ausführen
- 4. Checkpoint-Flags: Abgebrochene Releases fortsetzen
- 5. Rollback-Logik: Automatisch zurückrollen
- 6. Schritt-für-Schritt-Protokoll und Audit-Trail
- 7. Kommandozeilen-Flags mit getopts und manuellem Parsing
- 8. Stage-basierte Ausführung mit Abhängigkeiten
- 9. Release-Skript-Muster im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum Release-Skripte Dry-Run und Checkpoints brauchen
Release-Skripte sind unter den Shell-Skripten diejenigen mit dem höchsten Schadenspotenzial: Sie modifizieren Datenbanken, überschreiben Dateien, starten und stoppen Dienste und leeren Caches — in einer Produktionsumgebung, die echten Traffic verarbeitet. Ein Release-Skript, das bei einem Fehler in Schritt 7 von 12 abbricht und keinen definierten Zustand hinterlässt, schafft Mehrarbeit und Ausfallzeiten, die durch eine bessere Struktur vermeidbar wären.
Der Dry-Run-Modus ist die erste Schutzmaßnahme: Er führt alle Prüfungen, Validierungen und Protokollierungen durch, überspringt aber alle destruktiven Operationen. So kann ein Release-Skript vor dem echten Einsatz vollständig gegen die Zielumgebung simuliert werden, ohne Daten zu verändern. Checkpoints sind die zweite Schutzmaßnahme: Nach jedem erfolgreich abgeschlossenen Schritt wird ein Status-Flag gesetzt, sodass bei einem Fehler und erneutem Start nur die nicht abgeschlossenen Schritte wiederholt werden — nicht das gesamte Release von vorne.
Rollback-Logik ist die dritte Ebene: Wenn ein Schritt fehlschlägt, stellt das Release-Skript den Zustand vor dem Schritt automatisch wieder her. Das setzt voraus, dass vor jedem destruktiven Schritt ein Snapshot oder Backup erstellt wird. Diese drei Mechanismen — Dry-Run, Checkpoints und Rollback — zusammen machen Release-Skripte zu deterministischen, sicheren Werkzeugen statt zu nervösen Einmaloperationen.
2. Grundarchitektur eines Release-Skripts
Ein produktionstaugliches Release-Skript folgt einer festen Struktur: Header mit Abhängigkeiten und Konfiguration, Flag-Parsing, Precondition-Checks, sequenzielle Schrittausführung mit Checkpoint-Tracking, Cleanup und abschließendem Deployment-Report. Die Schritte werden als Funktionen implementiert, die genau drei Dinge tun: den Vorher-Zustand sichern, die Änderung durchführen und bei Fehler die gesicherte Rollback-Funktion aufrufen. Diese Struktur macht das Release-Skript lesbar, testbar und erweiterbar ohne globale Seiteneffekte.
Die zentrale Variable für den Dry-Run-Modus ist DRY_RUN. Alle Funktionen, die Änderungen vornehmen, rufen ihre Befehle über eine Wrapper-Funktion run_cmd auf, die im Dry-Run-Modus den Befehl nur ausgibt, ohne ihn auszuführen. Das ist eleganter als das Verstreuen von [[ $DRY_RUN -eq 1 ]] && echo-Prüfungen über das ganze Release-Skript. Die Wrapper-Funktion protokolliert außerdem jeden Befehl mit Zeitstempel in das Audit-Log — sowohl im Dry-Run als auch im echten Lauf.
#!/usr/bin/env bash
# release.sh — Structured release script with dry-run and checkpoints
set -euo pipefail
IFS=$'\n\t'
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly RELEASE_ID="$(date +%Y%m%d-%H%M%S)-$$"
readonly CHECKPOINT_DIR="/var/run/release-checkpoints"
readonly LOG_FILE="/var/log/releases/${RELEASE_ID}.log"
readonly ROLLBACK_STACK_FILE="${CHECKPOINT_DIR}/${RELEASE_ID}.rollback"
# ---- Flags (defaults) ----
DRY_RUN=0
START_FROM=""
SKIP_STEPS=()
FORCE=0
# ---- Core: run_cmd wraps all destructive operations ----
run_cmd() {
local description="$1"; shift
if [[ $DRY_RUN -eq 1 ]]; then
printf '[DRY-RUN] %s\n $ %s\n' "$description" "$*" >&2
return 0
fi
printf '[EXEC] %s\n $ %s\n' "$description" "$*" >&2
"$@"
}
# ---- Rollback stack: push commands to run on failure ----
push_rollback() {
printf '%s\n' "$*" >> "$ROLLBACK_STACK_FILE"
}
execute_rollback() {
if [[ ! -f "$ROLLBACK_STACK_FILE" ]]; then
return 0
fi
echo "[ROLLBACK] Führe Rollback-Schritte aus..." >&2
# Execute in reverse order (tac = reverse cat)
while IFS= read -r cmd; do
eval "$cmd" || echo "[ROLLBACK WARN] Schritt fehlgeschlagen: $cmd" >&2
done < <(tac "$ROLLBACK_STACK_FILE")
rm -f "$ROLLBACK_STACK_FILE"
}
3. Dry-Run-Modus: Befehle ankündigen statt ausführen
Die Implementierung eines robusten Dry-Run-Modus in Release-Skripten geht über das einfache Überspringen von Befehlen hinaus. Ein vollständiger Dry-Run führt alle Precondition-Checks durch — Umgebungsvariablen vorhanden? Abhängige Dienste erreichbar? Berechtigungen korrekt? — und gibt für jeden Schritt aus, welcher Befehl ausgeführt würde, mit welchen Argumenten und welche Seiteneffekte zu erwarten sind. Der Operator sieht nach einem Dry-Run genau, was das Release-Skript tun wird, ohne dass etwas geändert wurde.
Wichtig für den Dry-Run-Modus in Release-Skripten: Nicht alle Operationen können vollständig simuliert werden. Datenbankmigrationen, die auf dem aktuellen Datenbankzustand basieren, können im Dry-Run nur ihren SQL ausgeben, nicht ihre tatsächlichen Auswirkungen. Das Release-Skript muss in diesen Fällen explizit dokumentieren, dass der Dry-Run für diesen Schritt unvollständig ist. Eine gute Praxis: Jede Schritt-Funktion hat eine separate describe_step()-Funktion, die im Dry-Run aufgerufen wird und den geplanten Schritt menschenlesbar beschreibt.
4. Checkpoint-Flags: Abgebrochene Releases fortsetzen
Checkpoints in Release-Skripten lösen das Problem des unvollständig ausgeführten Releases. Wenn ein Release-Prozess in Schritt 7 von 12 fehlschlägt und nach Behebung des Fehlers neu gestartet wird, sollen die Schritte 1–6 nicht erneut ausgeführt werden. Das Checkpoint-System speichert nach jedem erfolgreich abgeschlossenen Schritt eine Markierungsdatei in einem dedizierten Verzeichnis. Beim Start prüft das Release-Skript, welche Checkpoints bereits gesetzt sind, und überspringt die entsprechenden Schritte.
Das Checkpoint-Verzeichnis muss pro Release-Lauf eindeutig sein — mit der Release-ID als Teil des Pfads. So können parallele Releases auf verschiedenen Servern nicht gegenseitig ihre Checkpoints überschreiben. Die --resume-Option des Release-Skripts gibt die Release-ID an, deren Checkpoints weiterverwendet werden sollen. Mit --from STEP_NAME kann das Release-Skript auch ohne gespeicherte Checkpoints ab einem bestimmten Schritt gestartet werden — nützlich für Notfall-Deployments, die nur einen Teilbereich aktualisieren müssen.
#!/usr/bin/env bash
set -euo pipefail
# ---- Checkpoint management ----
readonly CHECKPOINT_DIR="${CHECKPOINT_DIR:-/var/run/release-checkpoints}"
mkdir -p "$CHECKPOINT_DIR"
checkpoint_set() {
local step="$1"
touch "${CHECKPOINT_DIR}/${RELEASE_ID}.${step}"
printf '[CHECKPOINT] %s abgeschlossen\n' "$step" >&2
}
checkpoint_done() {
local step="$1"
[[ -f "${CHECKPOINT_DIR}/${RELEASE_ID}.${step}" ]]
}
checkpoint_clear() {
rm -f "${CHECKPOINT_DIR}/${RELEASE_ID}."*
printf '[CHECKPOINT] Alle Checkpoints für %s gelöscht\n' "$RELEASE_ID" >&2
}
# ---- Step execution with checkpoint logic ----
run_step() {
local step_name="$1"
local step_fn="$2"
# Skip if checkpoint already set (resume mode)
if checkpoint_done "$step_name"; then
printf '[SKIP] Schritt %s bereits abgeschlossen (Checkpoint)\n' "$step_name" >&2
return 0
fi
# Skip if step is in SKIP_STEPS array
local skip
for skip in "${SKIP_STEPS[@]:-}"; do
if [[ "$skip" == "$step_name" ]]; then
printf '[SKIP] Schritt %s übersprungen (--skip Flag)\n' "$step_name" >&2
return 0
fi
done
printf '\n[STEP] === %s ===\n' "$step_name" >&2
local start_ts
start_ts=$(date +%s)
"$step_fn" # Call the step function
local duration=$(( $(date +%s) - start_ts ))
printf '[STEP] %s abgeschlossen in %ds\n' "$step_name" "$duration" >&2
[[ $DRY_RUN -eq 0 ]] && checkpoint_set "$step_name"
}
# ---- Example step implementation ----
step_composer_install() {
push_rollback "echo 'Composer rollback: restore vendor from backup'"
run_cmd "Composer-Abhängigkeiten installieren" \
composer install --no-dev --optimize-autoloader --no-interaction
}
step_db_migrate() {
push_rollback "bin/magento setup:rollback --db-rollback-file=${DB_BACKUP_FILE:-}"
run_cmd "Datenbankmigrationen ausführen" \
bin/magento setup:upgrade --keep-generated
}
# ---- Main release sequence ----
main() {
run_step "composer_install" step_composer_install
run_step "db_migrate" step_db_migrate
# Add further steps as needed
[[ $DRY_RUN -eq 0 ]] && checkpoint_clear
printf '\n[RELEASE] Abgeschlossen: %s\n' "$RELEASE_ID" >&2
}
5. Rollback-Logik: Automatisch zurückrollen
Rollback in Release-Skripten ist kein optionales Feature, sondern eine Grundvoraussetzung für Produktionssicherheit. Die Implementierung folgt dem Stack-Prinzip: Vor jedem destruktiven Schritt wird ein Rollback-Kommando auf einen Stack geschrieben. Bei einem Fehler werden die Rollback-Kommandos in umgekehrter Reihenfolge ausgeführt — das zuletzt eingetragene zuerst. Das stellt sicher, dass Abhängigkeiten zwischen Schritten korrekt berücksichtigt werden: Eine Datenbankänderung, die auf einer Dateiänderung aufbaut, muss zuerst rückgängig gemacht werden, bevor die Datei wiederhergestellt wird.
Die Rollback-Logik in Release-Skripten muss selbst fehlertolerant sein. Wenn ein Rollback-Schritt fehlschlägt, darf der nächste Rollback-Schritt trotzdem ausgeführt werden. Jeder Fehler im Rollback wird geloggt, aber nicht als fataler Fehler behandelt. Am Ende des Rollbacks gibt das Release-Skript einen vollständigen Bericht aus: welche Rollback-Schritte erfolgreich waren und welche manuelles Eingreifen erfordern. Dieser Bericht ist die Grundlage für den Post-Mortem-Prozess.
6. Schritt-für-Schritt-Protokoll und Audit-Trail
Ein vollständiges Audit-Log ist für Release-Skripte in regulierten Umgebungen Pflicht und in allen anderen Umgebungen wertvolle Debugging-Hilfe. Das Log enthält für jeden Schritt: Zeitstempel des Starts und Endes, ausgeführter Befehl mit allen Argumenten, Exit-Code, stdout und stderr. Im Dry-Run-Modus enthält das Log, welche Befehle ausgeführt worden wären. So ist das Audit-Log eines Dry-Runs und des echten Releases strukturell identisch — der Unterschied liegt nur in einem Flag-Wert.
Für Release-Skripte in Multi-Server-Umgebungen ist ein zentralisiertes Log-Format wichtig. JSON-formatierte Log-Zeilen ({ "timestamp": "...", "release_id": "...", "step": "...", "status": "..." }) lassen sich mit jq filtern und in Monitoring-Systeme wie Elasticsearch oder Datadog importieren. Die Release-ID in jeder Log-Zeile ermöglicht die Korrelation aller Logs eines Releases über alle beteiligten Server hinweg — unabdingbar für Incident-Analysen nach einem fehlgeschlagenen Deploy.
#!/usr/bin/env bash
set -euo pipefail
readonly LOG_FILE="${LOG_DIR:-/var/log/releases}/${RELEASE_ID:-test}.log"
mkdir -p "$(dirname "$LOG_FILE")"
# ---- Structured JSON audit log ----
audit_log() {
local level="$1" step="$2" message="$3"
local ts
ts="$(date --iso-8601=seconds 2>/dev/null || date -u '+%Y-%m-%dT%H:%M:%SZ')"
printf '{"timestamp":"%s","release_id":"%s","level":"%s","step":"%s","message":"%s","dry_run":%s}\n' \
"$ts" "${RELEASE_ID:-unknown}" "$level" "$step" \
"${message//\"/\\\"}" "$([[ ${DRY_RUN:-0} -eq 1 ]] && echo true || echo false)" \
>> "$LOG_FILE"
}
# ---- Step report summary ----
declare -A STEP_STATUS=()
declare -A STEP_DURATION=()
record_step_result() {
local step="$1" status="$2" duration="$3"
STEP_STATUS["$step"]="$status"
STEP_DURATION["$step"]="$duration"
audit_log "$status" "$step" "Step completed in ${duration}s"
}
print_release_report() {
local sep
sep="$(printf '%0.s─' {1..60})"
printf '\n%s\n' "$sep" >&2
printf '%-30s %10s %10s\n' "RELEASE REPORT: ${RELEASE_ID:-unknown}" "STATUS" "DAUER" >&2
printf '%s\n' "$sep" >&2
for step in "${!STEP_STATUS[@]}"; do
local st="${STEP_STATUS[$step]}"
local dur="${STEP_DURATION[$step]:-?}s"
printf '%-30s %10s %10s\n' "$step" "$st" "$dur" >&2
done
printf '%s\n' "$sep" >&2
printf 'Vollständiges Log: %s\n' "$LOG_FILE" >&2
}
# Ensure report is printed on exit
trap print_release_report EXIT
7. Kommandozeilen-Flags mit getopts und manuellem Parsing
Release-Skripte benötigen eine vollständige Kommandozeilen-Schnittstelle. Die Bash-internen Möglichkeiten: getopts für einzelne Buchstaben-Flags (-d, -f, -v) und manuelles Parsing für lange Flags (--dry-run, --from, --skip, --resume). getopts ist portabel und korrekt, aber nur für kurze Optionen. Für Release-Skripte mit vielen Optionen empfiehlt sich manuelles Parsing in einer while/case-Schleife, die $1 verbraucht und mit shift voranschreitet.
Das Flag-Parsing-Modul eines Release-Skripts sollte bei unbekannten Flags eine verständliche Fehlermeldung und einen Usage-Text ausgeben. Mit einer usage()-Funktion, die alle Flags mit Beschreibung und Standardwerten auflistet, wird das Release-Skript selbstdokumentierend. --help gibt usage() aus und beendet das Skript mit Exit-Code 0. --version gibt die Versionsnummer aus. Diese Konventionen machen Release-Skripte für neue Teammitglieder ohne Dokumentation benutzbar.
8. Stage-basierte Ausführung mit Abhängigkeiten
Komplexe Release-Skripte lassen sich durch Stage-Abhängigkeiten strukturieren. Statt einer einfachen sequenziellen Schritt-Liste definiert man Stages mit optionalen Abhängigkeiten: Stage B kann erst ausgeführt werden, wenn Stage A erfolgreich war. Das erlaubt parallele Ausführung unabhängiger Stages und erzwingt die korrekte Reihenfolge für abhängige Stages. In Bash ist eine einfache Abhängigkeitsprüfung über die Checkpoint-Flags realisierbar: Vor einer Stage wird geprüft, ob alle Dependency-Checkpoints gesetzt sind.
Ein praxisrelevantes Muster für Release-Skripte: Verschiedene Environments (dev, staging, prod) mit unterschiedlichen Stage-Konfigurationen. Eine Konfigurations-Map ordnet jedem Environment zu, welche Stages ausgeführt werden und welche übersprungen werden. In dev wird die Datenbankmigrationsphase in einem Dry-Run-ähnlichen Modus ausgeführt, der nur den SQL ausgibt. In staging läuft alles vollständig. In prod wird vor der Datenbankmigrationsphase automatisch ein Backup erstellt und ein Checkpoint mit dem Backup-Pfad gesetzt, sodass bei Rollback das korrekte Backup verwendet wird.
9. Release-Skript-Muster im Vergleich
Verschiedene Implementierungsansätze für Release-Skripte haben unterschiedliche Stärken. Die Wahl hängt von der Komplexität des Releases, der Teamgröße und den Anforderungen an Rollback und Auditierbarkeit ab.
| Muster | Dry-Run | Checkpoints | Rollback | Einsatz |
|---|---|---|---|---|
| Sequenziell | Einfach | Nachrüstbar | Manuell | Einfache Deployments |
| Mit Checkpoints | Vollständig | Automatisch | Stack-basiert | Produktions-Releases |
| Stage-basiert | Pro Stage | Pro Stage | Pro Stage | Komplexe Multi-Service-Releases |
| Ohne Struktur | Kein | Kein | Kein | Nur für Dev-Umgebungen |
Für die meisten Produktionsumgebungen ist das Checkpoint-Muster der beste Einstieg. Es ist überschaubar komplex, lässt sich schrittweise in bestehende Release-Skripte einbauen und bringt sofort den größten Nutzen: Abgebrochene Releases können sicher fortgesetzt werden, ohne die bereits abgeschlossenen Schritte zu wiederholen. Das Stage-basierte Muster empfiehlt sich erst, wenn mehrere unabhängige Systeme in einem Release koordiniert werden müssen — zum Beispiel bei Microservice-Deployments oder Multi-Region-Rollouts.
Mironsoft
Shell-Automatisierung, DevOps-Tooling und Deployment-Infrastruktur
Release-Skripte, die man vorher testen kann?
Wir entwickeln Release-Skripte mit vollständigem Dry-Run-Modus, Checkpoint-System und automatischer Rollback-Logik — für sichere, reproduzierbare Deployments in eurer Produktionsumgebung.
Release-Automation
Vollständige Release-Skripte mit Dry-Run, Checkpoints und Rollback für eure Deployment-Pipeline
Audit-Trail
JSON-strukturierte Deployment-Logs mit Release-ID-Korrelation für Monitoring und Post-Mortems
Rollback-Logik
Automatische Rollback-Strategie für Datenbank, Dateisystem und Service-Konfigurationen
10. Zusammenfassung
Produktionstaugliche Release-Skripte benötigen drei zentrale Sicherheitsmechanismen: Dry-Run-Modus für gefahrlose Vorab-Tests, Checkpoint-Flags für das sichere Fortsetzen nach Unterbrechungen und Rollback-Logik für die automatische Wiederherstellung bei Fehlern. Diese Mechanismen zusammen machen Release-Skripte von nervösen Einmaloperationen zu deterministischen, testbaren Werkzeugen. Der strukturelle Aufwand — die run_cmd-Wrapper-Funktion, das Checkpoint-Verzeichnis und der Rollback-Stack — amortisiert sich beim ersten abgebrochenen Release, das ohne Datenverlust fortgesetzt werden kann.
Das vollständige Audit-Log mit JSON-Struktur und Release-ID ist der weitere wichtige Bestandteil. Es ermöglicht die Nachverfolgung jedes Deployments — wer hat wann was deployed, welche Schritte wurden ausgeführt, wo wurde abgebrochen und wie wurde der Rollback durchgeführt. Für Teams, die mehrere Release-Skripte für verschiedene Komponenten betreiben, empfiehlt sich eine gemeinsame Bibliothek (lib/release.sh) mit den Checkpoint-, Rollback- und Logging-Funktionen. Das stellt sicher, dass alle Release-Skripte dieselben Sicherheitsmechanismen verwenden.
Release-Skripte mit Checkpoints und Dry-Run — Das Wichtigste auf einen Blick
Dry-Run-Modus
run_cmd-Wrapper für alle destruktiven Operationen. Im Dry-Run: Befehl ausgeben, nicht ausführen. Precondition-Checks laufen trotzdem vollständig.
Checkpoints
Nach jedem Schritt Markierungsdatei setzen. Beim Start abgeschlossene Schritte überspringen. --resume für Fortsetzung nach Abbruch.
Rollback-Stack
Vor jedem destruktiven Schritt Rollback-Kommando auf Stack schreiben. Bei Fehler: Kommandos in umgekehrter Reihenfolge ausführen.
Audit-Log
JSON-strukturierte Logs mit Release-ID, Schritt, Status und Dauer. Im Dry-Run und echten Lauf identisches Format — nur dry_run-Flag unterscheidet sich.