Versions- und Umgebungs-Kombinationen lokal durchtesten, bevor die CI-Matrix geschrieben wird
Bevor eine Matrix-Konfiguration in GitHub Actions oder GitLab CI live geht, lohnt sich ein Prototyp in Bash: dieselbe Kombinatorik aus Versionen, Betriebssystemen und Umgebungen lokal per Schleife durchlaufen, Fehler direkt in der Shell debuggen und erst danach die endgültige YAML-Matrix schreiben. Das spart teure Trial-and-Error-Commits und macht sichtbar, welche Kombinationen überhaupt sinnvoll sind.
Inhaltsverzeichnis
- 1. Warum eine Matrix-Build-Idee zuerst lokal in Bash simulieren
- 2. Grundmuster: eine Bash-Schleife über Versionen und Umgebungen
- 3. Docker als Isolationsschicht pro Matrix-Zelle nutzen
- 4. Matrix-Zellen parallel statt sequenziell ausführen
- 5. Ergebnisse sammeln und einen Report erzeugen
- 6. Von der lokalen Simulation zur echten CI-Matrix-Konfiguration
- 7. Vor- und Nachteile gegenüber nativer CI-Matrix
- 8. Caching und Ressourcen-Grenzen beim lokalen Testen
- 9. Wann sich der Umweg über Bash lohnt, und wann nicht
- 10. Zusammenfassung
- 11. FAQ
1. Warum eine Matrix-Build-Idee zuerst lokal in Bash simulieren
Ein Matrix-Build testet dieselbe Codebasis gegen mehrere Kombinationen aus Sprachversion, Betriebssystem und Abhängigkeits-Set, zum Beispiel PHP 8.2, 8.3 und 8.4 jeweils gegen zwei Datenbank-Versionen. In der CI-Konfiguration entsteht daraus schnell ein Dutzend paralleler Jobs, aber jede Änderung an dieser Matrix kostet einen Commit, einen Push und mehrere Minuten Wartezeit, bis alle Runner durchgelaufen sind, nur um festzustellen, dass eine Kombination einen Tippfehler im Image-Tag enthält.
Eine lokale Bash-Schleife, die dieselbe Kombinatorik durchläuft, liefert dieselbe Information in Sekunden statt Minuten, weil sie ohne Warteschlange, ohne Runner-Zuteilung und ohne Netzwerk-Overhead direkt auf der eigenen Maschine läuft. Wer die Matrix zuerst als Bash-Prototyp baut, findet falsche Versionsnummern, fehlende Umgebungsvariablen und unerwartete Abhängigkeitskonflikte, bevor sie in der echten CI-Pipeline auftauchen und dort deutlich teurer zu debuggen sind.
2. Grundmuster: eine Bash-Schleife über Versionen und Umgebungen
Der Kern einer lokalen Matrix-Simulation ist eine verschachtelte Schleife über zwei oder mehr Bash-Arrays, eines pro Matrix-Dimension. Jede Kombination aus den Elementen der Arrays entspricht genau einer Zelle der späteren CI-Matrix, und die Schleife durchläuft sie in derselben kartesischen Reihenfolge, wie es strategy.matrix in GitHub Actions später auch tun würde.
Wichtig ist, die Dimensionen von Anfang an so zu benennen, wie sie später in der YAML-Matrix heißen werden, etwa PHP_VERSIONS und DB_VERSIONS, damit die Übersetzung am Ende ein reines Copy-Paste der Werte ist und keine neue Namensfindung erfordert. Das reduziert die Fehlerquelle, dass die lokale Simulation etwas anderes testet als die spätere echte Matrix.
#!/usr/bin/env bash
set -euo pipefail
readonly PHP_VERSIONS=("8.2" "8.3" "8.4")
readonly DB_VERSIONS=("mysql:8.0" "mariadb:10.11")
for php in "${PHP_VERSIONS[@]}"; do
for db in "${DB_VERSIONS[@]}"; do
echo "=== Matrix-Zelle: PHP ${php} x ${db} ==="
# Platzhalter fuer den eigentlichen Testlauf dieser Kombination
echo " -> wuerde hier PHP ${php} gegen ${db} testen"
done
done
3. Docker als Isolationsschicht pro Matrix-Zelle nutzen
Eine reine Bash-Schleife allein reicht nicht aus, um verschiedene PHP-Versionen realistisch zu testen, weil auf der lokalen Maschine meist nur eine Version installiert ist. Docker schließt diese Lücke: Für jede Matrix-Zelle wird ein Container mit exakt der Image-Version gestartet, die auch die spätere CI-Matrix verwenden würde, sodass die Simulation nicht nur die Logik, sondern auch die tatsächliche Laufzeitumgebung abbildet.
Entscheidend ist, dieselben Image-Tags zu verwenden, die auch in der CI-Konfiguration stehen werden, zum Beispiel php:8.3-cli statt einer lokal abweichenden Version. Wird das Projektverzeichnis per Bind-Mount eingehängt, laufen die Tests im Container exakt gegen den aktuellen Arbeitsstand, ohne dass Artefakte zwischen den Matrix-Zellen sich gegenseitig beeinflussen können.
#!/usr/bin/env bash
set -euo pipefail
readonly PHP_VERSIONS=("8.2" "8.3" "8.4")
for php in "${PHP_VERSIONS[@]}"; do
echo "=== PHP ${php} in Docker ==="
docker run --rm \
-v "$PWD":/app -w /app \
"php:${php}-cli" \
./run-tests.sh
done
4. Matrix-Zellen parallel statt sequenziell ausführen
Eine sequenzielle Schleife über sechs oder mehr Matrix-Zellen summiert die Laufzeit jedes einzelnen Testlaufs, während eine echte CI-Matrix die Zellen parallel auf mehreren Runnern verteilt. Um diesen Vorteil lokal zumindest teilweise nachzubilden, lässt sich xargs -P nutzen, das die Kombinationen aus einer Liste liest und bis zu einer festgelegten Anzahl gleichzeitig als eigene Prozesse startet.
Die Anzahl paralleler Prozesse sollte sich an der Kernanzahl der lokalen Maschine orientieren, nicht an der Anzahl der CI-Runner, da hier ein einzelner Rechner alle Zellen gleichzeitig verarbeiten muss. Wer stattdessen das klassische &-Muster mit anschließendem wait bevorzugt, sammelt die Prozess-IDs in einem Array und wartet am Ende gesammelt auf alle, was die Kontrolle über einzelne Exit-Codes erleichtert.
#!/usr/bin/env bash
set -euo pipefail
readonly PHP_VERSIONS=("8.2" "8.3" "8.4")
run_cell() {
local php="$1"
docker run --rm -v "$PWD":/app -w /app "php:${php}-cli" ./run-tests.sh
}
export -f run_cell
printf '%s\n' "${PHP_VERSIONS[@]}" | xargs -P 3 -I{} bash -c 'run_cell "$@"' _ {}
5. Ergebnisse sammeln und einen Report erzeugen
Parallel gestartete Testläufe schreiben ihre Ausgabe wild durcheinander auf dasselbe Terminal, wenn nicht jede Matrix-Zelle in eine eigene Log-Datei umgeleitet wird. Ein assoziatives Array, das den Exit-Code jeder Zelle unter einem sprechenden Schlüssel wie php83-mariadb speichert, macht am Ende in einer Schleife sichtbar, welche Kombinationen fehlgeschlagen sind, ohne die verstreuten Log-Dateien einzeln öffnen zu müssen.
Ein einfacher Abschlussreport, der Pass- und Fail-Zeilen zusammenfasst, entspricht in reduzierter Form der Matrix-Übersicht, die GitHub Actions oder GitLab CI in der Weboberfläche zeigen. Für schnelles lokales Debugging reicht das völlig aus, weil der Fokus hier nicht auf einem hübschen Dashboard liegt, sondern darauf, in Sekunden zu erkennen, welche Version-Kombination als Nächstes Aufmerksamkeit braucht.
#!/usr/bin/env bash
set -euo pipefail
declare -A RESULTS=()
for php in 8.2 8.3 8.4; do
log="/tmp/matrix-${php}.log"
if docker run --rm -v "$PWD":/app -w /app "php:${php}-cli" ./run-tests.sh > "$log" 2>&1; then
RESULTS["php${php}"]="PASS"
else
RESULTS["php${php}"]="FAIL (siehe $log)"
fi
done
echo "=== Matrix-Report ==="
for key in "${!RESULTS[@]}"; do
echo " ${key}: ${RESULTS[$key]}"
done
6. Von der lokalen Simulation zur echten CI-Matrix-Konfiguration
Sobald die Bash-Simulation zuverlässig läuft, ist die Übersetzung in eine echte CI-Matrix meist reine Formsache: Die Werte aus PHP_VERSIONS und DB_VERSIONS wandern unverändert in strategy.matrix unter GitHub Actions, beziehungsweise in die parallel: matrix:-Sektion unter GitLab CI. Weil die Kombinationen lokal schon einmal alle durchgelaufen sind, entfällt die übliche Serie kleiner Korrektur-Commits, die nur dazu dienen, Tippfehler in Versionsnummern zu finden.
Wer zusätzlich in der Bash-Schleife einzelne Kombinationen explizit ausschließt, etwa weil PHP 8.2 mit einer bestimmten Datenbank-Version nicht kompatibel ist, kann diese Ausschlüsse direkt als exclude-Einträge in die YAML-Matrix übernehmen. Die lokale Simulation wird damit nicht nur zum Testlauf, sondern auch zur Dokumentation, welche Zellen der Matrix überhaupt sinnvoll sind.
7. Vor- und Nachteile gegenüber nativer CI-Matrix
Der größte Vorteil der lokalen Bash-Simulation ist die Feedback-Geschwindigkeit: kein Warten auf freie Runner, keine CI-Minuten-Kosten und die Möglichkeit, mit einem Debugger oder interaktiver Shell direkt in eine fehlgeschlagene Matrix-Zelle hineinzuspringen, was in einer laufenden CI-Pipeline nur mit Zusatzwerkzeugen wie SSH-Debug-Sessions möglich ist.
Der Nachteil ist, dass eine lokale Maschine niemals die Parallelität einer CI-Farm mit Dutzenden Runnern erreicht, und dass die hübsche Matrix-Übersicht mit grünen und roten Kacheln, Artefakt-Uploads pro Zelle und automatischen Pull-Request-Kommentaren komplett fehlt. Die Bash-Simulation ist ein Entwicklungswerkzeug für die Entstehungsphase einer Matrix, kein Ersatz für die produktive CI-Ausführung.
8. Caching und Ressourcen-Grenzen beim lokalen Testen
Jede zusätzliche Matrix-Dimension multipliziert die Anzahl benötigter Docker-Images, und ohne Aufräumen füllt sich die lokale Festplatte schnell mit Dutzenden Image-Varianten, die nach dem Prototyping nicht mehr gebraucht werden. Ein regelmäßiger docker image prune nach abgeschlossener Simulation hält den Plattenverbrauch im Rahmen, ohne die Images zu löschen, die noch aktiv verwendet werden.
Weil eine lokale Maschine typischerweise deutlich weniger CPU-Kerne und Arbeitsspeicher hat als eine ganze CI-Runner-Flotte, sollte die lokale Matrix nicht zwangsläufig jede einzelne Kombination der späteren echten Matrix abdecken. Eine repräsentative Teilmenge, etwa die älteste und die neueste unterstützte Version pro Dimension, deckt die meisten realen Fehlerquellen ab und hält die lokale Laufzeit überschaubar.
9. Wann sich der Umweg über Bash lohnt, und wann nicht
Der Umweg über eine Bash-Simulation lohnt sich vor allem beim Aufbau einer neuen Matrix, beim Debuggen einer Version-spezifischen Fehlermeldung, die nur in einer bestimmten Zelle auftritt, oder wenn CI-Minuten teuer sind und jeder unnötige Pipeline-Lauf vermieden werden soll. Auch beim Reproduzieren eines gemeldeten Bugs, der nur unter einer alten Sprachversion auftritt, ist die lokale Schleife oft schneller als ein CI-Retrigger.
Für eine bereits stabile, seit Monaten unveränderte Matrix lohnt sich der zusätzliche Pflegeaufwand einer parallelen Bash-Version dagegen nicht, weil hier kaum noch Iteration stattfindet und die native CI-Matrix ihre Stärken bei Parallelität und Reporting voll ausspielt. Die Bash-Simulation ist ein Werkzeug für die Entwicklungsphase, nicht für den Dauerbetrieb einer eingespielten Pipeline.
| Kriterium | Bash-Schleife lokal | Native CI-Matrix | Empfehlung |
|---|---|---|---|
| Feedback-Geschwindigkeit | Sekunden, kein Warten auf Runner | Minuten, abhängig von Runner-Warteschlange | Bash für schnelle Iteration |
| Parallelität | Begrenzt durch lokale CPU-Kerne | Dutzende Runner gleichzeitig möglich | CI für große Matrizen |
| Kosten | Keine CI-Minuten verbraucht | CI-Minuten pro Zelle | Bash beim Prototyping |
| Debugging | Direkter Shell-Zugriff auf fehlgeschlagene Zelle | Nur über Logs oder SSH-Debug-Sessions | Bash für Root-Cause-Analyse |
| Reporting | Einfacher Text-Report | Grafische Matrix-Übersicht, PR-Kommentare | CI für produktiven Betrieb |
Mironsoft
Shell-Automatisierung, DevOps-Tooling und Deployment-Infrastruktur
Shell-Skripte, die in der Produktion zuverlässig laufen?
Wir analysieren bestehende Bash-Skripte, erkennen fragile Muster und ersetzen sie durch robuste Bash-Patterns: mit vollständiger Fehlerbehandlung, Logging und sicherer Parallelisierung für euren Deployment-Stack.
Code-Review
ShellCheck-Analyse und manuelle Prüfung auf kritische Bash-Pattern-Verstöße.
Refactoring
Fehlerbehandlung, Logging und sichere Dateioperationen nachrüsten.
CI-Integration
ShellCheck und BATS in Pipelines integrieren und Regressionstests aufbauen.
10. Zusammenfassung
Matrix-Builds mit Bash simulieren: Das Wichtigste auf einen Blick
Grundprinzip
Verschachtelte Bash-Schleifen über Arrays bilden dieselbe Kombinatorik ab, die später in strategy.matrix oder parallel: matrix landet.
Isolation
Docker mit denselben Image-Tags wie die spätere CI-Matrix macht die lokale Simulation realistisch, nicht nur logisch korrekt.
Parallelisierung
xargs -P oder das & mit wait-Muster verkürzt die Gesamtlaufzeit, begrenzt durch die lokale CPU-Kernanzahl.
Grenzen
Keine Runner-Parallelität, kein grafisches Reporting. Die Bash-Simulation ist ein Entwicklungswerkzeug, kein CI-Ersatz.