dev, staging und prod sauber trennen
Wer Branches und Deployment-Umgebungen nicht sauber trennt, deployt irgendwann versehentlich ungetesteten Code auf die Produktion. Dieser Artikel zeigt, wie Magento-Teams Branches eindeutig auf dev, staging und prod abbilden, Code per Merge und Tag statt per Rebase durch die Umgebungen befördern, Konfiguration sauber aus der Historie heraushalten und Branches sowie Pipelines pro Umgebung gezielt absichern.
Inhaltsverzeichnis
- 1. Warum Branches genau auf Deployment-Umgebungen abgebildet werden sollten
- 2. Code durch Umgebungen befördern: Merge- und Tag-Progression statt History-Rewrite
- 3. Trunk-based vs. Git-Flow für Magento-Agenturteams
- 4. Umgebungsspezifische Konfiguration: env.php darf niemals ins Repository
- 5. app/etc/env.php zur Deploy-Zeit aus Umgebungsvariablen generieren
- 6. Release-Tags und Rollback-Strategie
- 7. Branch Protection: Pull-Request-Pflicht und CI-Checks pro Umgebung
- 8. CI/CD-Pipeline-Trigger pro Branch
- 9. Anti-Pattern vs. Best Practice im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum Branches genau auf Deployment-Umgebungen abgebildet werden sollten
Sobald ein Magento-Team mehr als eine Umgebung betreibt, dev für die tägliche Entwicklung, staging für die Abnahme und prod für den Live-Shop, entsteht die Frage, welcher Code auf welcher Umgebung landet. Die robusteste Antwort ist eine feste, eindeutige Zuordnung zwischen Branch und Umgebung: Ein Push auf develop deployt automatisch nach dev, ein Merge nach staging deployt nach staging, und nur main beziehungsweise ein Release-Tag darauf deployt nach prod. Diese Eindeutigkeit beseitigt die häufigste Fehlerquelle in Multi-Umgebungs-Setups: manuelles Rätselraten, welcher Commit-Stand gerade wo läuft.
Ein bekanntes Referenzbeispiel für dieses Muster ist Adobe Commerce Cloud (vormals Magento Commerce Cloud): Dort existieren von Haus aus die Branches integration, staging und production, und jeder Push auf einen dieser Branches löst automatisch ein Deployment auf die gleichnamige, isolierte Umgebung aus, inklusive eigener Datenbank, eigenem Suchindex und eigenen Umgebungsvariablen. Zusätzliche Branches erzeugen bei Bedarf temporäre Integrationsumgebungen. Auch ohne Adobe Commerce Cloud lässt sich dieses Prinzip mit selbst gehosteten Deploy-Skripten oder GitHub Actions/GitLab CI nachbilden, solange die Branch-zu-Umgebung-Zuordnung dokumentiert und im Team konsequent eingehalten wird.
2. Code durch Umgebungen befördern: Merge- und Tag-Progression statt History-Rewrite
Der zentrale Grundsatz für Multi-Environment-Branching lautet: Code wird durch Umgebungen befördert, nicht neu geschrieben. Ein Commit, der auf dev getestet wurde, muss bitidentisch auf staging und später auf prod ankommen, sonst testet man effektiv eine andere Version als die, die live geht. Das erreicht man durch Fast-Forward-Merges oder Merge-Commits von develop nach staging nach main, gefolgt von einem Release-Tag auf dem finalen Stand. Was man dabei konsequent vermeidet, ist Cherry-Picking einzelner Commits zwischen Umgebungs-Branches und Rebasing bereits promoteter Commits.
Cherry-Picking erzeugt neue Commit-Hashes für inhaltlich identischen Code, wodurch Git die Beziehung zwischen den Ständen auf dev und prod verliert und spätere Merges unnötige Konflikte produzieren. Rebasing eines bereits nach staging gemergten Branches verändert dessen Historie rückwirkend und macht signierte oder bereits getestete Commits ungültig. Die Regel ist einfach: Sobald ein Commit eine Umgebung erreicht hat, ist er unveränderlich und wird ausschließlich per Merge weitergereicht, niemals per Rebase oder Cherry-Pick erneut angewendet.
# Promote code through environments via merge, never via rebase or cherry-pick
$ git checkout develop
$ git pull origin develop
# Feature is done and tested on dev, promote to staging with a merge commit
$ git checkout staging
$ git merge --no-ff develop -m "Promote develop to staging"
$ git push origin staging
# CI runs smoke tests against staging; once approved, promote to production
$ git checkout main
$ git merge --no-ff staging -m "Promote staging to production"
# Tag the exact commit that goes live, never a re-created equivalent
$ git tag -a v2026.07.1 -m "Release 2026.07.1: checkout fix, catalog import"
$ git push origin main --follow-tags
3. Trunk-based vs. Git-Flow für Magento-Agenturteams
Trunk-based Development setzt auf kurzlebige Feature-Branches, die mehrmals täglich in main gemergt werden, kombiniert mit Feature-Flags für unfertige Funktionen. Für Agenturteams, die mehrere Magento-Shops parallel betreuen und häufig kleine, isolierte Änderungen ausliefern, reduziert das die Merge-Konflikte erheblich und hält die Integration kontinuierlich. Die Umgebungs-Branches develop, staging und main bleiben dabei bestehen, aber Feature-Branches selbst leben nur Stunden bis wenige Tage.
Git-Flow mit eigenen release/*- und hotfix/*-Branches eignet sich besser für Projekte mit längeren Stabilisierungsphasen, etwa bei großen Magento-Versionswechseln oder Shop-Migrationen, bei denen eine Release-Vorbereitung über Tage oder Wochen läuft, während parallel bereits an der nächsten Version entwickelt wird. Für die meisten laufenden Agenturprojekte mit wöchentlichen oder täglichen Deployments ist eine vereinfachte Variante praktikabler: Trunk-based Development für Features, ergänzt um dedizierte hotfix/*-Branches, die direkt von main abzweigen und nach dem Fix sowohl nach main als auch zurück nach develop gemergt werden, damit kein Hotfix beim nächsten regulären Release verloren geht.
4. Umgebungsspezifische Konfiguration: env.php darf niemals ins Repository
app/etc/env.php enthält Datenbank-Zugangsdaten, den Crypt Key, Cache-Backend-Konfiguration, Session-Storage-Einstellungen und den MAGE_MODE. Jede dieser Einstellungen ist umgebungsspezifisch: dev nutzt oft MAGE_MODE=developer mit Filesystem-Cache, prod läuft mit MAGE_MODE=production und Redis- oder Valkey-Backends. Würde env.php committet, landen entweder produktive Zugangsdaten im Repository, oder die dev-Konfiguration überschreibt bei jedem Deployment versehentlich die prod-Einstellungen. Beides ist ein ernstes Sicherheits- und Betriebsrisiko, wie auch der Artikel zu sensiblen Daten im Repository ausführt.
Die saubere Trennung: env.php gehört konsequent in .gitignore und wird niemals committet, auch nicht als Vorlage mit Platzhaltern. app/etc/config.php hingegen, das nur aktivierte Module und Skalare ohne Zugangsdaten enthält, gehört sehr wohl ins Repository, da es umgebungsübergreifend identisch sein soll. Statt Umgebungsunterschiede über Branches oder committete Dateien abzubilden, werden sie über Umgebungsvariablen gelöst, die von der jeweiligen Deploy-Pipeline oder Cloud-Plattform zur Deploy-Zeit injiziert werden, nie über den Commit-Verlauf.
5. app/etc/env.php zur Deploy-Zeit aus Umgebungsvariablen generieren
Statt env.php manuell pro Umgebung zu pflegen, generiert ein Deploy-Hook die Datei bei jedem Deployment neu aus Umgebungsvariablen, die in der CI/CD-Pipeline oder direkt in der Cloud-Plattform als Secrets hinterlegt sind. Adobe Commerce Cloud macht genau das automatisch über .magento.env.yaml und die Deploy-Hooks build/deploy. Bei selbst gehosteten Setups übernimmt ein einfaches Shell- oder PHP-Skript im Deploy-Prozess dieselbe Aufgabe, bevor bin/magento setup:upgrade läuft.
Wichtig ist, dass dieses Skript Teil der Pipeline-Konfiguration ist, also im Repository liegen darf, weil es selbst keine Geheimnisse enthält, sondern nur deren Namen referenziert. Die eigentlichen Werte, Datenbank-Passwort, Redis-Host, Crypt Key, kommen ausschließlich aus dem Secret-Store der CI/CD-Plattform oder der Cloud-Umgebung. So bleibt die Historie frei von sensiblen Daten, während jede Umgebung trotzdem eine korrekt konfigurierte env.php erhält, ohne dass ein Mensch sie manuell pflegen muss.
#!/usr/bin/env bash
# deploy/generate-env-php.sh: build app/etc/env.php from CI/CD secrets,
# never commit the generated file itself
set -euo pipefail
php -r '
$config = [
"db" => [
"connection" => [
"default" => [
"host" => getenv("DB_HOST"),
"dbname" => getenv("DB_NAME"),
"username" => getenv("DB_USER"),
"password" => getenv("DB_PASSWORD"),
],
],
],
"crypt" => ["key" => getenv("MAGE_CRYPT_KEY")],
"cache" => [
"frontend" => [
"default" => [
"backend" => "Cm_Cache_Backend_Redis",
"backend_options" => [
"server" => getenv("REDIS_HOST"),
"port" => getenv("REDIS_PORT"),
],
],
],
],
"session" => ["save" => "redis"],
"MAGE_MODE" => getenv("MAGE_MODE") ?: "production",
];
file_put_contents("app/etc/env.php", "<?php\nreturn " . var_export($config, true) . ";\n");
'
echo "app/etc/env.php generated for environment: ${MAGE_MODE:-production}"
6. Release-Tags und Rollback-Strategie
Jeder produktive Release-Stand sollte mit einem annotierten Git-Tag markiert werden, zum Beispiel v2026.07.1, direkt auf dem Merge-Commit, der nach main gewandert ist. Annotierte Tags (git tag -a) speichern zusätzlich Tagger, Datum und eine Nachricht, im Gegensatz zu leichtgewichtigen Tags, die nur auf einen Commit zeigen. Diese Tags sind der Ankerpunkt für Rollbacks: Statt einen fehlerhaften Commit nachträglich zu reverten oder die Historie umzuschreiben, deployt man im Fehlerfall schlicht den vorherigen Tag erneut.
Diese Tag-basierte Rollback-Strategie ist deutlich sicherer als ein git revert unter Zeitdruck, weil der vorherige Stand bereits vollständig getestet war und keine neue, ungetestete Kombination von Reverts entsteht. Für Hotfixes gilt dieselbe Logik: Ein Hotfix-Branch zweigt vom aktuellen Produktions-Tag ab, wird gefixt, gemergt, erneut getaggt (etwa v2026.07.2) und ausschließlich über diesen neuen Tag deployt. So bleibt jederzeit nachvollziehbar, welcher exakte Commit-Stand zu welchem Zeitpunkt live war, was bei Incident-Analysen unschätzbar wertvoll ist.
# Tag a production release right after promotion to main
$ git checkout main
$ git tag -a v2026.07.1 -m "Release 2026.07.1"
$ git push origin v2026.07.1
# List releases in chronological order for a quick rollback overview
$ git tag --sort=-creatordate | head -5
v2026.07.1
v2026.06.3
v2026.06.2
# Rollback: redeploy the previous known-good tag, do not revert on main
$ git checkout v2026.06.3
$ ./deploy/deploy.sh production
# Hotfix branching from the currently live tag
$ git checkout -b hotfix/checkout-500 v2026.07.1
# ... fix, commit, merge to main ...
$ git tag -a v2026.07.2 -m "Hotfix: checkout 500 error"
$ git push origin main --follow-tags
7. Branch Protection: Pull-Request-Pflicht und CI-Checks pro Umgebung
Ohne technische Absicherung ist jede Branch-zu-Umgebung-Konvention nur eine Vereinbarung, die irgendwann versehentlich gebrochen wird, meist durch einen direkten Push auf main unter Zeitdruck. Branch Protection Rules in GitHub oder GitLab verhindern das strukturell: Für main und staging lassen sich Pull-Request-Pflicht, eine Mindestzahl an Reviewer-Approvals, verpflichtende Status-Checks (PHPStan, PHPCS, Unit-Tests) und ein Verbot von Force-Pushes durchsetzen. Für main empfiehlt sich zusätzlich, dass auch Administratoren diesen Regeln unterliegen, damit keine Ausnahme durch Rollenzuweisung entsteht.
Die Strenge der Regeln sollte mit der Kritikalität der Umgebung steigen: develop kann vergleichsweise offen bleiben, um schnelle Iteration zu ermöglichen, staging verlangt mindestens einen Review plus grüne Pipeline, und main verlangt zusätzlich, dass der Branch vor dem Merge auf dem aktuellen Stand ist (require branches to be up to date), um stille Merge-Konflikte zu vermeiden. Diese abgestufte Strenge spiegelt exakt das Risiko wider, das ein fehlerhafter Merge in der jeweiligen Umgebung verursachen würde.
# .github/branch-protection.yml (documentation-as-code, applied via API/Terraform)
branches:
develop:
required_pull_request_reviews:
required_approving_review_count: 1
required_status_checks:
strict: false
contexts: ["phpcs", "phpstan-level-5"]
allow_force_pushes: false
staging:
required_pull_request_reviews:
required_approving_review_count: 1
required_status_checks:
strict: true
contexts: ["phpcs", "phpstan-level-5", "unit-tests", "integration-tests"]
allow_force_pushes: false
enforce_admins: false
main:
required_pull_request_reviews:
required_approving_review_count: 2
require_code_owner_reviews: true
required_status_checks:
strict: true
contexts: ["phpcs", "phpstan-level-5", "unit-tests", "integration-tests", "smoke-tests"]
allow_force_pushes: false
enforce_admins: true
restrictions:
users: []
teams: ["release-managers"]
8. CI/CD-Pipeline-Trigger pro Branch
Die Branch-zu-Umgebung-Zuordnung wird erst dann verlässlich, wenn sie automatisiert in der CI/CD-Pipeline abgebildet ist, statt manuell per SSH deployt zu werden. Ein Push auf develop löst einen Job aus, der nach dev deployt und dort die schnellsten, aber am wenigsten strengen Checks fährt. Ein Merge nach staging löst zusätzlich Integrationstests und einen automatisierten Smoke-Test gegen den Checkout aus. Ein neuer Tag auf main löst das Produktions-Deployment aus, häufig mit einem manuellen Approval-Gate, bevor der Job tatsächlich ausgeführt wird.
Diese Trigger-Struktur macht das Deployment-Verhalten für das gesamte Team vorhersehbar: Niemand muss sich merken, welcher Befehl auf welcher Umgebung auszuführen ist, die Pipeline entscheidet allein anhand von Branch-Namen und Tag-Mustern. Das reduziert menschliche Fehler drastisch und macht jedes Deployment im CI/CD-Log nachvollziehbar, inklusive Commit-Hash, Tag und verantwortlicher Person, was gerade bei Rollbacks unter Zeitdruck entscheidend Zeit spart.
# .gitlab-ci.yml (excerpt): deploy jobs triggered by branch/tag rules
stages: [test, deploy]
deploy_dev:
stage: deploy
script: ./deploy/deploy.sh dev
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
deploy_staging:
stage: deploy
script:
- ./deploy/deploy.sh staging
- ./deploy/smoke-test.sh https://staging.example.com
rules:
- if: '$CI_COMMIT_BRANCH == "staging"'
deploy_production:
stage: deploy
script: ./deploy/deploy.sh production
rules:
- if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'
when: manual
environment:
name: production
url: https://www.example.com
9. Anti-Pattern vs. Best Practice im direkten Vergleich
Die meisten Probleme im Multi-Environment-Branching lassen sich auf eine Handvoll wiederkehrender Anti-Pattern zurückführen, die alle denselben Kern haben: Ein Umweg um den vorgesehenen Promotion-Pfad, meist aus Zeitdruck gewählt, der kurzfristig schneller wirkt, aber langfristig Vertrauen in den Deployment-Prozess zerstört. Die folgende Tabelle stellt die häufigsten dieser Umwege der jeweils robusten Alternative gegenüber.
| Bereich | Anti-Pattern | Empfohlenes Pattern | Warum das wichtig ist |
|---|---|---|---|
| Hotfix ausliefern | Commit per Cherry-Pick direkt nach main | Tag-basierte Promotion mit Merge und neuem Tag | Erhält identische Commit-Hashes über alle Umgebungen |
| Umgebungs-Konfiguration | env.php mit Zugangsdaten committen | env.php in .gitignore, Generierung aus Env-Vars zur Deploy-Zeit | Keine Zugangsdaten in der Git-Historie |
| Notfall-Fix unter Zeitdruck | Direkter Push auf main ohne Review | Protected Branch, Pull Request und Pflicht-Checks | Verhindert ungeprüften Code auf der Produktion |
| Bereits promoteter Commit | Nachträgliches Rebase auf staging/main | Fast-Forward- oder --no-ff-Merge, Historie bleibt stabil | Verhindert ungültige Commit-Hashes nach dem Deployment |
| Produktions-Deployment | Manuelles Deployment per SSH nach Gefühl | Branch/Tag-getriggerte CI/CD-Pipeline mit Approval-Gate | Nachvollziehbares, wiederholbares Deployment |
Wer diese fünf Muster konsequent auf die rechte Spalte umstellt, hat den größten Teil des Deployment-Risikos in einem Multi-Environment-Setup bereits eliminiert. Die verbleibende Arbeit besteht meist nur noch darin, das Team an die neue Disziplin zu gewöhnen, da Branch Protection und CI/CD-Trigger die Regeln danach technisch durchsetzen, statt sie nur zu dokumentieren.
Mironsoft
Branching-Strategien, CI/CD-Pipelines und Deployment-Workflows für Magento-Teams
Multi-Environment-Branching sauber einführen?
Wir helfen Magento-Teams, dev, staging und prod eindeutig auf Branches abzubilden, Release-Tags und Rollback-Prozesse einzuführen und Branch Protection sowie CI/CD-Pipelines passend zur jeweiligen Umgebung aufzusetzen.
Branching-Workshop
Trunk-based, Git-Flow oder Hybrid passend zur Teamgröße auswählen und einführen
CI/CD-Pipeline-Setup
Branch- und Tag-getriggerte Deployments für dev, staging und prod aufsetzen
Branch-Protection-Audit
Bestehende Repositories auf sichere Merge- und Review-Regeln prüfen
10. Zusammenfassung
Multi-Environment-Branching funktioniert nur, wenn Branches eindeutig auf Umgebungen abgebildet sind und Code ausschließlich per Merge und Tag durch diese Umgebungen befördert wird, niemals per Cherry-Pick oder Rebase bereits promoteter Commits. Adobe Commerce Cloud zeigt mit den Branches integration, staging und production ein bewährtes, direkt übertragbares Referenzmuster. Trunk-based Development mit kurzlebigen Feature-Branches passt für die meisten Agenturteams besser als klassisches Git-Flow, sofern Hotfix-Branches ergänzt werden.
Umgebungsspezifische Konfiguration wie app/etc/env.php gehört niemals in die Git-Historie, sondern wird aus Umgebungsvariablen zur Deploy-Zeit generiert. Release-Tags markieren jeden produktiven Stand und ermöglichen Rollbacks durch erneutes Deployen statt durch riskante Reverts. Branch Protection Rules und branch- beziehungsweise tag-getriggerte CI/CD-Pipelines setzen all diese Regeln technisch durch, statt sie nur als Team-Konvention zu dokumentieren, die irgendwann unter Zeitdruck gebrochen wird.
Multi-Environment-Branching für Magento auf einen Blick
Branch-Mapping
develop zu dev, staging zu staging, main zu prod. Adobe Commerce Cloud als Referenz.
Promotion statt Rewrite
Merge und Tag statt Cherry-Pick und Rebase. Commit-Hashes bleiben über alle Umgebungen identisch.
Konfiguration trennen
env.php nie committen, Generierung aus Env-Vars zur Deploy-Zeit statt Templates in Git.
Protection & CI/CD
Pull-Request-Pflicht, Pflicht-Checks pro Umgebung, Branch/Tag-getriggerte Deployments.