Bash · PHP · Docker · Git · SQL · DevOps
Bash als Glue-Code zwischen
PHP, SQL, Docker und Git

Kein einzelnes Tool beherrscht alle Aspekte eines modernen Deployment-Stacks. Bash als Glue-Code verbindet PHP-Skripte, SQL-Dump-Workflows, Docker-Build-Ketten und Git-Hooks zu einem kohärenten Automatisierungssystem – mit robustem Exit-Code-Handling, gezielter Datenweiterleitung und klarer Fehler-Eskalation, die Probleme früh sichtbar macht.

15 Min. Lesezeit PHP · MySQL · Docker · Git · Exit-Codes · Pipes Bash 4.x · 5.x · Linux · CI/CD

1. Was Glue-Code leistet und wo er versagt

Bash als Glue-Code ist keine Programmiersprache für komplexe Logik, sondern ein Orchestrierungswerkzeug für die Verbindung von Programmen, die jeweils ihren eigenen Domäne beherrschen. PHP ist gut für Anwendungslogik und Datenbankzugriffe, MySQL für relationale Operationen, Docker für Containerverwaltung, Git für Versionskontrolle. Bash als Glue-Code verbindet diese Werkzeuge, leitet Daten zwischen ihnen weiter, wertet ihre Ergebnisse aus und trifft einfache Steuerungsentscheidungen basierend auf Exit-Codes.

Wo Bash als Glue-Code versagt: wenn die Orchestrierungslogik selbst komplex wird. Komplexe Bedingungslogik, tiefe Datenstruktur-Manipulation und ausgedehnte Fehlerbehandlung über viele Werkzeuge hinweg sind Zeichen, dass das Skript in eine höhere Sprache migriert werden sollte – Python, Go oder ein dediziertes Build-Tool. Das Versagen von Bash als Glue-Code ist selten technisch bedingt, sondern fast immer ein Signal, dass das Abstraktionsniveau nicht mehr stimmt. Die Grenze ist subjektiv, aber praktisch: wenn das Skript länger als 300 Zeilen wird und dabei hauptsächlich Logik statt Orchestrierung enthält, lohnt sich die Diskussion.

Das wichtigste Prinzip für Bash als Glue-Code: Exit-Codes sind die einzige zuverlässige Kommunikation zwischen Prozessen. Ein Werkzeug, das mit Exit-Code 0 beendet, hat Erfolg gemeldet. Alles andere ist Fehler. Bash muss diese Codes abfangen, auswerten und eskalieren. Ohne set -euo pipefail und explizites Exit-Code-Handling ist Bash als Glue-Code taub gegenüber den Fehlermeldungen seiner Kinder.

2. Exit-Code-Handling: die Sprache der Tools

Exit-Codes sind das Protokoll zwischen Bash-Glue-Code und den aufgerufenen Tools. Code 0 bedeutet Erfolg, alles andere Fehler – wobei manche Tools differenzierte Codes verwenden: grep gibt 0 zurück wenn Treffer, 1 wenn keine Treffer, 2 bei einem Fehler. Das Muster grep -q "pattern" file || { echo "Pattern not found"; exit 1; } unterscheidet nicht zwischen "kein Treffer" und "Datei nicht lesbar". Das korrekte Glue-Code-Muster prüft den Exit-Code und wertet ihn gegen die bekannte Semantik aus: exit_code=$?; if (( exit_code == 1 )); then echo "no match"; elif (( exit_code == 2 )); then echo "error"; fi.

Bei Pipes verliert Bash standardmäßig alle Exit-Codes außer dem des letzten Befehls. Das Array PIPESTATUS enthält nach einer Pipe die Exit-Codes aller Befehle in der Reihenfolge ihrer Ausführung. Mit set -o pipefail beendet Bash die Ausführung, wenn irgendein Befehl in einer Pipe fehlschlägt – aber PIPESTATUS ist trotzdem nützlich, um zu erkennen, welcher Befehl in der Kette fehlgeschlagen ist. Für Bash-Glue-Code zwischen mehreren Tools ist das die entscheidende Diagnostik.


#!/usr/bin/env bash
# glue_exit_codes.sh — correct exit-code handling in Bash glue code
set -euo pipefail

# Capture exit code explicitly without triggering set -e
run_with_status() {
  local cmd=("$@")
  local exit_code=0
  "${cmd[@]}" || exit_code=$?
  echo "$exit_code"
}

# PHP CLI with exit-code check
php_exit=$(run_with_status php bin/magento setup:upgrade 2>&1 | tee /tmp/magento.log; echo "${PIPESTATUS[0]}")
if [[ "$php_exit" != "0" ]]; then
  echo "[ERROR] Magento setup:upgrade failed (exit $php_exit)" >&2
  exit "$php_exit"
fi

# Check PIPESTATUS after a pipeline — which step failed?
mysqldump --single-transaction magento 2>/dev/null | gzip -9 > /backup/magento.sql.gz
dump_status="${PIPESTATUS[0]}"
gzip_status="${PIPESTATUS[1]}"

if (( dump_status != 0 )); then
  echo "[ERROR] mysqldump failed with code $dump_status" >&2; exit 1
fi
if (( gzip_status != 0 )); then
  echo "[ERROR] gzip compression failed with code $gzip_status" >&2; exit 1
fi
echo "[OK] Database backup completed successfully"

3. Bash und PHP: CLI-Skripte orchestrieren

PHP-Anwendungen wie Magento 2 liefern über ihre CLI-Befehle (bin/magento) mächtige Werkzeuge für Deployment-Aufgaben, die von Bash als Glue-Code orchestriert werden. Das Muster: Bash ruft PHP-Befehle in der richtigen Reihenfolge auf, prüft jeden Exit-Code, sammelt Ausgaben im Log und entscheidet basierend auf dem Ergebnis über den nächsten Schritt. Kritisch ist dabei, dass PHP über Exceptions und Fehlerausgaben kommuniziert, die in Bash-Logs landen müssen – ohne die PHP-Ausgaben in Bash-Logik zu integrieren oder zu parsen, soweit möglich.

Ein häufiger Fehler beim Bash-Glue-Code für PHP: das Parsen von PHP-Ausgaben mit grep und awk, um zu bestimmen, ob ein Befehl erfolgreich war. Das ist fragil, weil Ausgabeformate sich ändern und lokalisiert sein können. Zuverlässiger ist immer der Exit-Code. Wenn ein PHP-Skript keinen zuverlässigen Exit-Code liefert, ist das ein Bug im PHP-Skript, der dort behoben werden sollte – nicht im Bash-Glue-Code durch Ausgabe-Parsing umgangen. Diese Grenze zwischen den Schichten zu respektieren ist das Fundament von wartbarem Bash-Glue-Code.

4. SQL-Dumps, Imports und Datenbankmigrationen

Datenbankoperationen sind ein klassischer Anwendungsfall für Bash als Glue-Code: Dump erstellen, komprimieren, an einen anderen Ort übertragen, importieren und Migrationen ausführen – jede dieser Operationen ist ein eigenständiges Tool, das Bash verbindet. Das grundlegende Muster für einen sicheren Dump: mysqldump --single-transaction --routines --triggers dbname | gzip -9 > dump.sql.gz mit anschließender PIPESTATUS-Prüfung. --single-transaction ist bei InnoDB-Tabellen entscheidend, um konsistente Snapshots ohne exklusive Sperren zu erzeugen.

Beim Import in Bash-Glue-Code: immer zuerst die Zieldatenbank prüfen, dann den Import mit explizitem Exit-Code abfangen. Das Muster mysql -e "SELECT 1" dbname >/dev/null 2>&1 || { echo "DB not accessible"; exit 1; } prüft Erreichbarkeit und Zugriffsrechte, bevor ein stundenlanger Import beginnt. Nach dem Import die Zeilenanzahl in kritischen Tabellen gegen erwartete Mindest-Werte prüfen – ein strukturell korrekter Import mit 0 Zeilen in der Produkt-Tabelle ist ein Fehler, den Exit-Code 0 nicht erkennt.


#!/usr/bin/env bash
# db_glue.sh — Bash glue code for database operations
set -euo pipefail

DB_HOST="${DB_HOST:-localhost}"
DB_USER="${DB_USER:?DB_USER not set}"
DB_PASS="${DB_PASS:?DB_PASS not set}"
DB_NAME="${DB_NAME:?DB_NAME not set}"
BACKUP_DIR="${BACKUP_DIR:-/backups/db}"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)

# MySQL connection test before any operation
mysql_args=(-h"$DB_HOST" -u"$DB_USER" -p"$DB_PASS" --batch --skip-column-names)
mysql "${mysql_args[@]}" -e "SELECT 1" "$DB_NAME" >/dev/null 2>&1 \
  || { echo "[ERROR] Cannot connect to database $DB_NAME" >&2; exit 1; }

# Dump with pipeline exit-code validation
DUMP_FILE="$BACKUP_DIR/${DB_NAME}-${TIMESTAMP}.sql.gz"
mysqldump "${mysql_args[@]}" --single-transaction --routines \
  "$DB_NAME" 2>/tmp/dump.err | gzip -9 > "$DUMP_FILE"

# Check both steps in the pipeline
if (( PIPESTATUS[0] != 0 )); then
  echo "[ERROR] mysqldump failed: $(cat /tmp/dump.err)" >&2; exit 1
fi

# Verify dump integrity
actual_size=$(stat --format="%s" "$DUMP_FILE")
(( actual_size < 1024 )) && { echo "[ERROR] Dump suspiciously small: $actual_size bytes" >&2; exit 1; }
echo "[OK] Dump written: $DUMP_FILE (${actual_size} bytes)"

# Row count verification after import
check_table_count() {
  local table="$1" min_rows="$2"
  local count
  count=$(mysql "${mysql_args[@]}" -e "SELECT COUNT(*) FROM $table" "$DB_NAME")
  (( count < min_rows )) && { echo "[ERROR] Table $table: $count rows < $min_rows expected" >&2; return 1; }
  echo "[OK] Table $table: $count rows"
}

check_table_count "catalog_product_entity" 1
check_table_count "customer_entity" 0

5. Docker-Workflows in Bash orchestrieren

Docker-Befehle in Bash als Glue-Code zu orchestrieren ist ein häufiger Anwendungsfall in CI/CD-Pipelines und Deployment-Skripten. Das grundlegende Muster: Container-Status prüfen, Build ausführen, Container ersetzen, Health-Check abwarten. Jeder dieser Schritte liefert Exit-Codes und ggf. Ausgaben, die ausgewertet werden müssen. Besonders das Warten auf Container-Readiness ist ein häufiger Stolperstein: docker run gibt Exit-Code 0, sobald der Container gestartet ist – nicht wenn er vollständig hochgefahren ist. Ein Bash-Glue-Code muss mit einer Retry-Schleife auf echte Readiness warten.

Das Muster für Container-Readiness in Bash-Glue-Code: eine Schleife mit konfigurierbarer Anzahl von Versuchen und Wartezeit, die eine spezifische Health-Prüfung ausführt – nicht nur docker ps, sondern einen echten Verbindungstest über docker exec oder einen HTTP-Request. Bei Magento könnte das ein docker exec app php bin/magento list sein, der sicherstellt, dass die PHP-Anwendung antwortfähig ist. Nach dem Erreichen der Readiness erst mit dem nächsten Schritt des Deployments fortfahren.


#!/usr/bin/env bash
# docker_glue.sh — Bash glue code for Docker orchestration
set -euo pipefail

IMAGE_NAME="${1:?Usage: $0 IMAGE_NAME}"
CONTAINER_NAME="app"
HEALTH_TIMEOUT=60
HEALTH_INTERVAL=3

# Build with streaming output, capture exit code
echo "[INFO] Building image: $IMAGE_NAME"
if ! docker build -t "$IMAGE_NAME" .; then
  echo "[ERROR] Docker build failed" >&2; exit 1
fi

# Stop and remove existing container if running
if docker ps -q --filter "name=$CONTAINER_NAME" | grep -q .; then
  echo "[INFO] Stopping existing container: $CONTAINER_NAME"
  docker stop "$CONTAINER_NAME" >/dev/null
  docker rm "$CONTAINER_NAME" >/dev/null
fi

# Start new container
docker run -d \
  --name "$CONTAINER_NAME" \
  --restart unless-stopped \
  -e "APP_ENV=${APP_ENV:-production}" \
  "$IMAGE_NAME"

# Wait for container readiness — not just running, but healthy
echo "[INFO] Waiting for container readiness (max ${HEALTH_TIMEOUT}s)..."
elapsed=0
until docker exec "$CONTAINER_NAME" php -r "echo 'ok';" 2>/dev/null | grep -q "ok"; do
  sleep "$HEALTH_INTERVAL"
  elapsed=$((elapsed + HEALTH_INTERVAL))
  if (( elapsed >= HEALTH_TIMEOUT )); then
    echo "[ERROR] Container $CONTAINER_NAME not ready after ${HEALTH_TIMEOUT}s" >&2
    docker logs --tail 50 "$CONTAINER_NAME" >&2
    exit 1
  fi
  echo "[INFO] Still waiting... (${elapsed}s)"
done
echo "[OK] Container $CONTAINER_NAME is ready after ${elapsed}s"

6. Git-Hooks und automatisierte Git-Workflows

Git-Hooks sind Shell-Skripte, die Git zu bestimmten Zeitpunkten im Workflow aufruft – und damit eine natürliche Heimat für Bash als Glue-Code. Der pre-commit-Hook läuft vor jedem Commit und kann PHP-Syntax-Prüfungen, PHPStan-Analysen oder PHPCS-Prüfungen ausführen. Der pre-push-Hook kann Tests ausführen bevor Code in das Remote-Repository gelangt. Der post-receive-Hook auf dem Server kann Deployments auslösen. In allen Fällen ist der Exit-Code entscheidend: Code 0 lässt Git fortfahren, alles andere bricht die Operation ab.

Eine wichtige Einschränkung beim Bash-Glue-Code in Git-Hooks: Hooks werden nicht automatisch versioniert oder geteilt, wenn sie im .git/hooks/-Verzeichnis liegen. Das Standardmuster für geteilte Hooks in Teams: Hooks in einem versionierten Verzeichnis im Repository ablegen (.githooks/) und mit git config core.hooksPath .githooks aktivieren. So landen Hook-Skripte im Repository, werden mit git pull aktualisiert und können in Code-Reviews begutachtet werden – wie jeder andere Glue-Code auch.

7. Datenweiterleitung zwischen Prozessen

Datenweiterleitung ist die Kernkompetenz von Bash als Glue-Code. Pipes, Umleitungen und Process Substitution bestimmen, wie die Ausgabe eines Tools zur Eingabe des nächsten wird. Das einfachste Muster – cmd1 | cmd2 – hat den bekannten Nachteil des Exit-Code-Verlustes ohne pipefail. Komplexere Weiterleitungen mit tee erlauben, eine Ausgabe gleichzeitig in eine Datei und in einen weiteren Prozess zu schicken: mysqldump dbname | tee >(gzip > backup.gz) | md5sum > backup.md5. Process Substitution macht den Dump gleichzeitig komprimiert speicherbar und prüfsummen-berechenbar – ohne Zwischendatei.

Ein weiteres mächtiges Werkzeug im Bash-Glue-Code für Datenweiterleitung: coproc. Ein Koprozess läuft als Hintergrundprozess und stellt bidirektionale Kommunikation über Dateideskriptoren bereit. Das ermöglicht, einen langlebigen Prozess zu starten und wiederholt Daten hin und her zu schicken, ohne für jede Anfrage einen neuen Kindprozess zu forken. Für Datenbankverbindungen, bei denen jede neue Verbindung Overhead erzeugt, kann ein coproc mysql die Verbindung über mehrere Queries hinweg offenhalten.

8. Fehler-Eskalation: von Bash nach Monitoring

Fehler in Bash-Glue-Code müssen mehr als nur lokale Logging-Einträge produzieren. In einem produktiven Stack erwartet man, dass kritische Fehler Alerts in Monitoring-Systemen auslösen, Slack-Nachrichten senden oder Tickets erstellen. Das Standardmuster: eine notify_failure-Funktion in der trap cleanup EXIT-Routine, die bei einem Nicht-Null-Exit-Code aktiv wird. In ihr kann ein HTTP-Webhook aufgerufen werden (curl -X POST -d "…" https://hooks.slack.com/…), eine E-Mail versendet oder ein Monitoring-System per API informiert werden.

Das Muster für strukturierte Fehler-Eskalation in Bash-Glue-Skripten: Fehlerkontext sammeln (welche Phase fehlschlug, welcher Exit-Code, welche letzten Logzeilen) und in einem strukturierten Format weiterleiten. Eine JSON-Payload mit printf '{"status":"error","phase":"%s","exit_code":%d,"log":"%s"}\n' "$PHASE" "$EXIT_CODE" "$LOG_EXCERPT" ermöglicht Monitoring-Systemen, die Fehler maschinell auszuwerten. Der Unterschied zwischen "Deployment fehlgeschlagen" und "Deployment fehlgeschlagen in Phase 'setup:upgrade', Exit-Code 127, letzte Zeile: Class X not found" ist in der Produktion erheblich.

9. Glue-Code-Ansätze im Vergleich

Für die Orchestrierung von PHP, SQL, Docker und Git gibt es mehrere Ansätze – Bash als Glue-Code ist dabei nicht immer die beste Wahl, aber oft die einfachste.

Ansatz Stärken Schwächen Empfehlung
Bash Glue-Code Überall vorhanden, keine Deps Komplex bei Logik, Debug schwer Bis ~200 Zeilen Orchestrierung
Makefile Dependency-Graph, parallel Keine Schleifen, Quoting-Fallen Build-Targets, nicht Flows
Python-Skript Volle Sprache, testbar Python-Version, venv nötig Komplexe Logik, API-Calls
Ansible Playbook Idempotent, deklarativ Overhead, YAML-Komplexität Infrastruktur-Konfiguration
CI/CD YAML Integriert, visuell Nur in Pipeline ausführbar Für Pipeline-spezifische Steps

Die Praxis zeigt: die beste Architektur für komplexe Deployments kombiniert Bash-Glue-Code für einfache Orchestrierung (Befehlsreihenfolge, Exit-Code-Prüfung, Log-Sammlung) mit einem höheren Tool für komplexere Logik. Ein Bash-Wrapper um ein Python-Deployment-Skript, der die Umgebungsvariablen setzt, Logs sammelt und Fehler eskaliert, ist oft wartbarer als ein monolithisches Bash-Skript, das alles selbst macht.

Mironsoft

Deployment-Infrastruktur, Bash-Automatisierung und DevOps-Tooling

Deployment-Skripte, die PHP, Docker und Git zuverlässig verbinden?

Wir bauen Bash-Glue-Code, der PHP-Anwendungen, Datenbankoperationen, Docker-Workflows und Git-Hooks zu einem kohärenten Deployment-System verbindet – mit vollständigem Exit-Code-Handling, strukturiertem Logging und automatischer Fehler-Eskalation.

Deployment-Skripte

Magento/PHP-Deployments mit robustem Glue-Code automatisieren

DB-Orchestrierung

Dump, Kompression, Import und Migrations-Workflows in Bash

Git-Hook-Integration

Pre-commit und Pre-push Hooks für PHP-Qualitätssicherung

10. Zusammenfassung

Bash als Glue-Code zwischen PHP, SQL, Docker und Git ist am effektivsten, wenn er sich auf seine Kernaufgabe konzentriert: Befehlsreihenfolge steuern, Exit-Codes auswerten und Daten zwischen Prozessen weiterleiten. set -euo pipefail und PIPESTATUS sind die technischen Grundlagen für zuverlässiges Exit-Code-Handling. Process Substitution mit >(cmd) ermöglicht gleichzeitige Datenweiterleitung ohne Zwischendateien. Readiness-Schleifen für Docker-Container ersetzen blindes Warten nach docker run. Git-Hooks in versionierten Verzeichnissen machen Qualitätssicherung zum automatischen Teil des Workflows.

Die Grenze von Bash als Glue-Code ist die Komplexität der Orchestrierungslogik. Wenn ein Skript anfängt, komplexe Bedingungen über viele Zustände hinweg zu verwalten oder tief in die Ausgaben der aufgerufenen Tools zu parsen, ist der Zeitpunkt für eine Neubewertung des Werkzeugs erreicht. Die Kombination aus Bash für einfache Orchestrierung und einem spezialisierten Tool für komplexe Logik – ein Ansible-Playbook, ein Python-Skript oder ein CI/CD-System – ist oft die wartbarste Architektur.

Bash als Glue-Code — Das Wichtigste auf einen Blick

Exit-Codes

PIPESTATUS[@] nach Pipes. set -o pipefail als Basis. Exit-Code-Semantik pro Tool verstehen – grep 1 ≠ Fehler.

PHP & SQL

Ausgaben nicht parsen – Exit-Codes auswerten. DB-Verbindung vor dem Dump testen. Zeilenanzahl nach Import verifizieren.

Docker

docker run liefert Exit-Code 0 bei Start, nicht bei Readiness. Retry-Schleife mit echtem Health-Check ist Pflicht.

Git-Hooks

Hooks in .githooks/ versionieren, git config core.hooksPath setzen. Exit ≠ 0 bricht git commit/push ab.

11. FAQ: Bash als Glue-Code zwischen PHP, SQL, Docker und Git

1Was ist Bash-Glue-Code?
Shell-Skript, das PHP-CLI, MySQL, Docker, Git in der richtigen Reihenfolge aufruft, Exit-Codes auswertet und Daten weiterleitet. Keine Anwendungslogik – Orchestrierung.
2Exit-Codes aus Bash-Pipe lesen?
PIPESTATUS[@] enthält Exit-Codes aller Pipe-Befehle: cmd1 | cmd2; echo ${PIPESTATUS[0]} ${PIPESTATUS[1]}. set -o pipefail bricht bei Fehler ab. PIPESTATUS zeigt welcher Befehl versagt hat.
3Docker auf echte Readiness warten?
docker run gibt Exit 0 bei Start, nicht bei Readiness. Retry-Schleife mit echtem Health-Check nötig: docker exec container curl -sf localhost/health. Nach Timeout: docker logs ausgeben und mit Exit != 0 abbrechen.
4PHP-Ausgaben nicht parsen?
Exit-Codes sind zuverlässiger als Ausgabe-Parsing. Kein String-Matching auf PHP-Ausgaben. PHP-Exit-Codes korrekt setzen lassen. Im Bash-Skript nur auswerten: php cmd; (( $? != 0 )) && exit 1.
5Git-Hooks versionieren?
Hooks in .githooks/ ablegen, git config core.hooksPath .githooks setzen. Werden mit git pull aktualisiert und sind im Code-Review sichtbar. Nicht in .git/hooks/ ablegen – das ist nicht versioniert.
6Wann ist Bash nicht mehr geeignet?
Ab ~200 Zeilen mit komplexer Logik, tiefem Ausgabe-Parsing oder Testbarkeitsanforderungen. Dann Python, Go oder ein CI/CD-System evaluieren. Bash bleibt für sequenzielle Befehlsketten mit klaren Exit-Codes ideal.
7Fehler-Alerts nach Slack senden?
In trap cleanup EXIT: bei exit_code != 0 curl -X POST mit JSON-Payload an Webhook. Phase, Exit-Code und Logauszug mitschicken. Jeder Produktionsfehler wird automatisch eskaliert.
8MySQL-Verbindung vor Import testen?
mysql -e 'SELECT 1' $DB >/dev/null 2>&1 || { echo 'DB not accessible'; exit 1; }. Prüft Netzwerk, Zugangsdaten und DB-Existenz in einem Schritt. Immer vor langem Import ausführen.
9Daten gleichzeitig in zwei Prozesse?
tee mit Process Substitution: cmd | tee >(gzip > backup.gz) >(sha256sum > backup.sha). Process Substitution >() startet Prozesse und stellt virtuelle Dateien bereit. Kein Tmpfile nötig.
10Robusten Magento-Deployment-Glue bauen?
Phasen: DB-Backup → Code-Update → Composer → setup:upgrade → Static Deploy → Cache flush → Health-Check. Jede Phase als Funktion. Bei Fehler: Rollback in trap cleanup EXIT. Jede Phase mit Exit-Code-Prüfung.