in den Deployment-Prozess einbauen
setup:upgrade ist der einzige Befehl im Magento-Deployment-Prozess, der die Produktionsdatenbank direkt verändert. Er läuft nicht im Build-Job, nicht als Test – er läuft auf dem Live-System. Wer diesen Schritt nicht absichert, baut einen Deployment-Prozess, der bei Datenbankmigrationen regelmäßig Glück braucht.
Inhaltsverzeichnis
- 1. Was setup:upgrade wirklich macht
- 2. Wann setup:upgrade nötig ist – und wann nicht
- 3. --keep-generated: der wichtigste Flag
- 4. Datenbankbackup vor dem Upgrade
- 5. Reihenfolge im Deploy-Job: wann setup:upgrade läuft
- 6. Expand-Contract: Zero Downtime bei Datenbankänderungen
- 7. Rollback nach setup:upgrade: die Grenzen
- 8. Maintenance Mode: wann er wirklich nötig ist
- 9. Upgrade-Strategien im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Was setup:upgrade wirklich macht
setup:upgrade ist in Magento der Befehl, der alle ausstehenden Datenbankschema-Änderungen und Daten-Patches anwendet. Er liest die aktuelle Datenbankversion jedes installierten Moduls aus der patch_list-Tabelle, vergleicht sie mit der in den Modul-Klassen definierten Zielversion und führt alle noch nicht ausgeführten Migrationen sequenziell aus. Nach dem Ausführen der Schema-Änderungen regeneriert er auch den generierten Code – es sei denn, --keep-generated ist gesetzt.
Das Wichtige daran: setup:upgrade verändert die Produktionsdatenbank direkt und unwiderruflich. Es gibt kein automatisches Rollback für ausgeführte Datenbankmigrationen. Wenn eine Migration auf halbem Weg fehlschlägt, kann die Datenbank in einem inkonsistenten Zustand sein. Wenn der neue Code eine inkompatible Datenbankstruktur erstellt, läuft der alte Code – nach einem Rollback des Dateisystems – möglicherweise nicht mehr korrekt. Das ist der Kern der Herausforderung: setup:upgrade ist der Punkt, an dem Code-Deployment und Datenbankzustand untrennbar verbunden werden.
Trotz dieser Risiken ist setup:upgrade unverzichtbar – es ist der Mechanismus, über den Magento die Datenbank auf dem Stand hält, den der Code erwartet. Die Aufgabe besteht nicht darin, setup:upgrade zu vermeiden, sondern darin, es so in den Deployment-Prozess einzubauen, dass Fehler erkannt werden, bevor sie den Shop betreffen, und dass ein Rollback auch nach einer Migration möglich bleibt.
2. Wann setup:upgrade nötig ist – und wann nicht
setup:upgrade muss nicht bei jedem Deployment ausgeführt werden. Es ist nur dann erforderlich, wenn sich die Datenbankschema-Version eines oder mehrerer Module geändert hat. Das ist der Fall, wenn neue Module installiert, bestehende Module aktualisiert oder Data Patches hinzugefügt werden. Bei einem reinen Frontend-Release – neues CSS, neues Tailwind-Theme, veränderte Templates – ist kein Datenbankschema betroffen und setup:upgrade kann ausgelassen werden.
Wie erkennt man, ob setup:upgrade nötig ist? Magento bietet keinen direkten Befehl, der das vor der Ausführung prüft. Ein pragmatischer Ansatz: setup:upgrade mit --dry-run ist in Magento 2.4.x nicht verfügbar. Die beste Methode ist, die Versionen der setup_module-Tabelle mit den erwarteten Versionen in den Modul-Klassen zu vergleichen – das kann als Pre-Deploy-Check geskriptet werden. Für Teams ohne diese Automatisierung gilt: setup:upgrade bei jedem Deployment ausführen, aber mit --keep-generated, um unnötige Code-Regenerierung zu vermeiden.
3. --keep-generated: der wichtigste Flag
Der Flag --keep-generated ist das wichtigste Argument, das setup:upgrade in einem CI/CD-Kontext mitgegeben werden muss. Ohne ihn regeneriert Magento nach jeder Datenbankmigrierung den gesamten generierten Code neu – das dauert mehrere Minuten und geschieht auf dem Produktionsserver, nicht im Build-Job. Mit --keep-generated wird dieser Schritt übersprungen, weil der Code bereits in der Build-Stage korrekt generiert wurde.
Voraussetzung für --keep-generated: Der generierte Code im Artefakt muss vollständig und korrekt sein. Wenn neue Module hinzugekommen sind, müssen ihre Klassen in der Build-Stage durch DI Compile vollständig generiert worden sein. Wenn --keep-generated eingesetzt wird, ohne dass der Build-Job den neuen generierten Code enthält, fehlen Klassen – das führt zu PHP-Fehlern auf Production. Deshalb ist die Reihenfolge Build-Job → Deploy-Job unverzichtbar: zuerst DI Compile im Build, dann setup:upgrade mit --keep-generated im Deploy.
# Deploy stage: setup:upgrade with database backup and safety checks
deploy:production:
stage: deploy
script:
- |
# === Phase 1: Pre-deployment checks and database backup ===
ssh "${DEPLOY_USER}@${DEPLOY_HOST}" bash << 'PRECHECK'
set -euo pipefail
# Verify database connectivity before starting deployment
cd "${DEPLOY_PATH}/current"
php bin/magento db:status 2>/dev/null || {
echo "ERROR: Cannot connect to database. Aborting deployment."
exit 1
}
# Create timestamped backup before any schema changes
BACKUP_FILE="${DEPLOY_PATH}/db-backups/pre-deploy-$(date +%Y%m%d-%H%M%S).sql.gz"
mkdir -p "${DEPLOY_PATH}/db-backups"
mysqldump \
--single-transaction \
--quick \
--routines \
--triggers \
"${DB_NAME}" | gzip > "${BACKUP_FILE}"
echo "Database backup created: ${BACKUP_FILE}"
ls -lh "${BACKUP_FILE}"
PRECHECK
# === Phase 2: Transfer and prepare new release ===
RELEASE_ID="$(date +%Y%m%d-%H%M%S)"
RELEASE="${DEPLOY_PATH}/releases/${RELEASE_ID}"
ssh "${DEPLOY_USER}@${DEPLOY_HOST}" "mkdir -p ${RELEASE}"
rsync -az --delete \
--exclude="pub/media" --exclude="var/log" \
--exclude="var/session" --exclude="app/etc/env.php" \
./ "${DEPLOY_USER}@${DEPLOY_HOST}:${RELEASE}/"
# Link shared paths
ssh "${DEPLOY_USER}@${DEPLOY_HOST}" bash -s << LINKS
set -euo pipefail
ln -sfn "${DEPLOY_PATH}/shared/app/etc/env.php" "${RELEASE}/app/etc/env.php"
ln -sfn "${DEPLOY_PATH}/shared/pub/media" "${RELEASE}/pub/media"
ln -sfn "${DEPLOY_PATH}/shared/var/log" "${RELEASE}/var/log"
ln -sfn "${DEPLOY_PATH}/shared/var/session" "${RELEASE}/var/session"
LINKS
# === Phase 3: Run setup:upgrade before activating release ===
ssh "${DEPLOY_USER}@${DEPLOY_HOST}" bash -s << UPGRADE
set -euo pipefail
cd "${RELEASE}"
# Run database migrations — keep pre-built generated code
php bin/magento setup:upgrade \
--keep-generated \
--no-interaction
# Flush all caches after schema changes
php bin/magento cache:flush
echo "setup:upgrade completed successfully"
UPGRADE
# === Phase 4: Atomic symlink switch ===
ssh "${DEPLOY_USER}@${DEPLOY_HOST}" \
"ln -sfn ${RELEASE} ${DEPLOY_PATH}/current"
echo "Release ${RELEASE_ID} is now live"
4. Datenbankbackup vor dem Upgrade
Ein Datenbankbackup vor setup:upgrade ist die einzige Absicherung gegen eine fehlgeschlagene Migration, die die Datenbank in einem inkonsistenten Zustand hinterlässt. Das Backup muss vor dem Ausführen von setup:upgrade erstellt werden – nicht nach dem Deployment. Ein Backup, das nach dem Deployment erstellt wird, enthält bereits die neuen Schemaänderungen und kann nicht für einen vollständigen Rollback verwendet werden.
Der Backup-Prozess muss mit --single-transaction ausgeführt werden, damit mysqldump die Datenbank konsistent sichert, ohne andere Transaktionen zu blockieren. Ohne diesen Flag sperrt mysqldump Tabellen während des Backups, was bei großen Datenbanken zu Unterbrechungen im Shop führt. Das Backup-Verzeichnis muss außerhalb des releases/-Verzeichnisses liegen und eine eigene Retention-Policy haben – Backup-Dateien nach dem Stand vor einer Datenbankmigrierung sind nach dem nächsten erfolgreichen Deployment wertlos, sollten aber für mindestens sieben Tage aufbewahrt werden.
5. Reihenfolge im Deploy-Job: wann setup:upgrade läuft
Die korrekte Reihenfolge in einem Magento-Deploy-Job ist entscheidend. setup:upgrade muss nach dem Transfer des Artefakts und nach dem Setzen der Shared-Symlinks ausgeführt werden – es braucht Zugriff auf vendor/, generated/ und app/etc/env.php. Es muss aber vor dem Aktivieren des neuen Releases durch den Symlink-Wechsel ausgeführt werden – die Datenbankstruktur muss zur neuen Code-Version passen, bevor der erste HTTP-Request den neuen Code erreicht.
Das bedeutet: Das neue Release-Verzeichnis ist vorbereitet, Symlinks sind gesetzt, setup:upgrade hat die Datenbankmigrationen durchgeführt – erst dann wird der current-Symlink auf das neue Release gesetzt. Dieser Ablauf minimiert das Zeitfenster, in dem neuer Code und alte Datenbankstruktur gleichzeitig aktiv sind. Beim Symlink-Wechsel sind Datenbank und Code synchron.
6. Expand-Contract: Zero Downtime bei Datenbankänderungen
Das Expand-Contract-Muster ist die einzige Methode, um wirklich Zero-Downtime-fähige Datenbankmigrationen in Magento zu implementieren. Die Grundidee: Datenbankänderungen werden in zwei Phasen aufgeteilt. Die erste Phase (Expand) fügt neue Spalten, Tabellen oder Indizes hinzu, ohne bestehende zu entfernen oder umzubenennen. Der alte Code funktioniert mit der neuen Struktur, weil er die neuen Spalten ignoriert. Der neue Code schreibt in die neuen und liest wahlweise aus alten und neuen Spalten.
Die zweite Phase (Contract) entfernt die alten Spalten, die der neue Code nicht mehr benötigt. Sie wird erst in einem späteren Release ausgeführt, wenn sichergestellt ist, dass kein aktiver Code mehr die alten Spalten verwendet. Zwischen Expand und Contract muss mindestens ein Release liegen, das ohne Rollback stabil war. Dieses Muster ermöglicht einen Rollback des Codes nach Phase 1: Der old Code läuft mit der erweiterten Datenbankstruktur, weil er die neuen Spalten ignoriert. Nach Phase 2 (Contract) ist ein Rollback nicht mehr möglich – der alte Code würde die entfernten Spalten vermissen.
# Expand-Contract pattern for zero-downtime database migrations
# Phase 1 (Expand) — additive changes only, backward-compatible
# app/code/Vendor/Module/Setup/Patch/Schema/AddNewColumn.php
# This migration adds a new column without removing the old one.
# Old code ignores the new column. New code uses it.
# A rollback of the code is still safe after this migration.
# Example: Adding a nullable column with default value
# ALTER TABLE catalog_product_entity
# ADD COLUMN new_field VARCHAR(255) DEFAULT NULL;
# Phase 2 (Contract) — only after at least one stable release
# app/code/Vendor/Module/Setup/Patch/Schema/RemoveOldColumn.php
# This migration removes the column the old code used to write.
# Only execute after verifying no active code reads the old column.
# After this migration, rollback of the code is NO LONGER SAFE.
# Example: Removing deprecated column
# ALTER TABLE catalog_product_entity
# DROP COLUMN old_field;
# GitLab pipeline check: detect potentially breaking migrations
check:migrations:
stage: test
script:
- |
# Scan for DROP or RENAME statements in new migration patches
# These are Contract phase — require special approval
DANGEROUS_PATTERNS="DROP COLUMN|DROP TABLE|RENAME COLUMN|RENAME TABLE|MODIFY COLUMN"
if grep -rPi "${DANGEROUS_PATTERNS}" \
app/code/*/Setup/Patch/ 2>/dev/null; then
echo "WARNING: Potentially breaking schema changes detected."
echo "Verify these are Contract-phase migrations with approval."
echo "Rollback may not be possible after these migrations run."
else
echo "No breaking schema changes detected in this release."
fi
7. Rollback nach setup:upgrade: die Grenzen
Der Rollback nach einem erfolgreichen setup:upgrade ist das schwierigste Problem im Magento-Deployment-Prozess. Ein Datei-Rollback – den current-Symlink auf das vorige Release setzen – ist trivial. Aber wenn die Datenbankmigrationen abwärtsinkompatible Änderungen enthalten haben, läuft der alte Code möglicherweise nicht mehr korrekt mit der neuen Datenbankstruktur. Das ist der Moment, an dem ein Datenbankbackup unverzichtbar ist: Nur eine vollständige Wiederherstellung der Datenbank auf den Stand vor der Migration ermöglicht einen echten Rollback.
Deshalb ist die Entscheidung, welche Art von Datenbankmigration ausgeführt wird, eine Architekturentscheidung mit direkten Auswirkungen auf die Rollback-Fähigkeit. Additive Migrationen (neue Spalten, neue Tabellen, neue Indizes) sind rollback-sicher, weil der alte Code die neuen Strukturen ignoriert. Destruktive Migrationen (Spalten umbenennen, Spalten löschen, Tabellen umbenennen) sind nicht rollback-sicher ohne Datenbankwiederherstellung. Das Expand-Contract-Muster ist die Methode, um destruktive Migrationen in zwei rollback-sichere Phasen aufzuteilen.
8. Maintenance Mode: wann er wirklich nötig ist
Der Maintenance Mode in Magento (bin/magento maintenance:enable) zeigt allen Besuchern eine Wartungsseite und blockiert HTTP-Anfragen. Für Zero-Downtime-Deployments sollte er vermieden werden – der Symlink-Wechsel mit vorbereiteter Datenbankstruktur ist der Weg ohne Maintenance Mode. Es gibt jedoch Szenarien, in denen ein kurzes Wartungsfenster unvermeidlich ist: komplexe, mehrstufige Datenbankmigrationen, die lange laufen und während derer der Shop inkonsistente Daten liefern würde, oder Migrationen, die Tabellen umbenennen und damit für bestehende Transaktionen unsichtbar machen.
Wenn Maintenance Mode eingesetzt wird, muss er so kurz wie möglich aktiv sein: erst direkt vor setup:upgrade aktivieren, sofort nach dem Symlink-Wechsel und cache:flush deaktivieren. Niemals beim Start des Deploy-Jobs aktivieren – dann wäre der Shop für die gesamte Dauer des Deployments im Wartungsmodus, was je nach Setup-Zeit fünf bis fünfzehn Minuten bedeuten kann. Die IP-Whitelist-Funktion (maintenance:enable --ip=X.X.X.X) ermöglicht es dem Team, den Shop während des Wartungsmodus intern zu testen.
9. Upgrade-Strategien im Vergleich
Wie setup:upgrade in den Deployment-Prozess eingebaut wird, hat direkte Auswirkungen auf die Rollback-Fähigkeit, die Downtime und das Risiko bei Deployment-Fehlern.
| Strategie | Rollback-Fähigkeit | Downtime | Empfehlung |
|---|---|---|---|
| Kein Backup, direkt upgraden | Keine DB-Rollback-Option | Minimal | Niemals in Production |
| Backup vor Upgrade, dann upgraden | DB-Wiederherstellung möglich | Minimal | Mindeststandard |
| Expand-Contract + Backup | Datei-Rollback ohne DB-Restore | Keine (Phase 1) | Empfohlen für alle Teams |
| Maintenance Mode + Upgrade | Mit Backup möglich | Downtime während Maintenance | Nur wenn unverm. nötig |
| --keep-generated nicht gesetzt | DI Compile auf Production | Mehrere Minuten | Niemals in Prod ohne CI-Build |
Die Tabelle macht deutlich, dass das Expand-Contract-Muster in Kombination mit einem Backup die einzige Strategie ist, die Zero Downtime mit echter Rollback-Fähigkeit verbindet. Der Mindeststandard – Backup vor Upgrade – ist besser als gar kein Backup, aber er erfordert eine Datenbankwiederherstellung für den Rollback, die je nach Datenbankgröße Minuten bis Stunden dauern kann. Expand-Contract vermeidet diese Notwendigkeit, indem es Migrationen abwärtskompatibel gestaltet.
10. Zusammenfassung
setup:upgrade sicher in den Deployment-Prozess einzubauen bedeutet: immer mit --keep-generated, immer mit einem Datenbankbackup davor, immer nach dem Transfer des Artefakts und vor dem Symlink-Wechsel. Die Datenbankmigrationen müssen das Expand-Contract-Muster einhalten, um Rollbacks ohne Datenbankwiederherstellung zu ermöglichen. Maintenance Mode ist nur dann einzusetzen, wenn die Migration so komplex ist, dass sie ohne Wartungsfenster nicht durchgeführt werden kann.
Die wichtigste Erkenntnis: setup:upgrade ist kein automatischer Befehl, der bei jedem Deployment unkritisch ausgeführt wird. Es ist eine Operation mit direkten Auswirkungen auf die Produktionsdatenbank, die Backup-Strategie und die Rollback-Fähigkeit. Wer setup:upgrade mit dieser Ernsthaftigkeit behandelt und entsprechend absichert, baut einen Deployment-Prozess, der auch bei komplexen Datenbankmigrationen kontrolliert und ohne Überraschungen abläuft.
Magento setup:upgrade — Das Wichtigste auf einen Blick
--keep-generated Pflicht
Immer mit --keep-generated ausführen. Der generierte Code kommt aus dem Build-Job – DI Compile auf Production vermeiden. Ohne Flag: mehrminütige Code-Regenerierung auf dem Server.
Backup davor
mysqldump --single-transaction vor setup:upgrade – die einzige Absicherung gegen inkonsistente Datenbankzustände nach einer fehlgeschlagenen Migration.
Expand-Contract
Additive Migrationen (Phase 1) ermöglichen Datei-Rollbacks ohne DB-Restore. Destruktive Migrationen (Phase 2) erst im nächsten Release nach stabilem Betrieb.
Reihenfolge
Backup → Artefakt übertragen → Shared-Symlinks setzen → setup:upgrade → cache:flush → Symlink-Wechsel. Niemals in einer anderen Reihenfolge.