CI/CD
.yml
GitLab · Magento · setup:upgrade · Datenbankmigrationen · Zero Downtime
Magento setup:upgrade sicher
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.

15 Min. Lesezeit setup:upgrade · --keep-generated · Backup · Expand-Contract · Rollback Magento 2.4 · MySQL · GitLab CI · Zero Downtime

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.

11. FAQ: Magento setup:upgrade im Deployment-Prozess

1Muss setup:upgrade bei jedem Deployment laufen?
Nein. Nur bei Datenbankschema-Änderungen – neue Module, Modul-Updates mit Schema-Patches. Reine Frontend- oder Code-Releases ohne Schema-Änderungen: setup:upgrade auslassen.
2Was macht --keep-generated?
Überspringt DI Compile nach der Migration. Der generierte Code kommt aus dem Build-Job – keine Regenerierung auf dem Server nötig. Spart mehrere Minuten auf Production.
3Was passiert wenn setup:upgrade fehlschlägt?
Datenbank kann inkonsistent sein. current zeigt noch auf altes Release – Shop läuft. Datenbank aus Backup wiederherstellen, bevor das Deployment wiederholt werden kann.
4Was ist das Expand-Contract-Muster?
Expand: Neue Strukturen hinzufügen, nichts entfernen – Datei-Rollback sicher. Contract: Alte Strukturen im nächsten Release entfernen. Zusammen: Zero Downtime bei DB-Migrationen.
5Datei-Rollback nach setup:upgrade möglich?
Nur nach Expand-Migrationen. Alter Code ignoriert neue Spalten – Datei-Rollback sicher. Nach Contract-Migrationen: Datenbankwiederherstellung notwendig.
6Wann Maintenance Mode einsetzen?
Nur bei unvermeidlich komplexen Migrationen. Direkt vor setup:upgrade aktivieren, direkt nach cache:flush deaktivieren. So kurz wie möglich – nicht beim Start des Deploy-Jobs.
7Warum --single-transaction beim Backup?
Ohne --single-transaction sperrt mysqldump Tabellen – das blockiert Shop-Transaktionen. Mit dem Flag: konsistentes Backup ohne Unterbrechung des Live-Betriebs.
8Wo Datenbankbackups speichern?
Außerhalb von releases/ in db-backups/ mit eigenem Retention-Cleanup. Mindestens 7 Tage aufbewahren, auf externen Speicher replizieren.
9Vor oder nach dem Symlink-Wechsel?
Vor dem Symlink-Wechsel. Datenbank muss zur neuen Code-Version passen, bevor der erste Request den neuen Code erreicht. Danach: atomar umschalten.
10Was, wenn setup:upgrade sehr lange läuft?
Während der Migration zeigt current noch auf das alte Release – der Shop läuft unverändert. Erst nach erfolgreichem Abschluss wird der Symlink gewechselt. Kein Shop-Ausfall durch lange Migrationen.