CI/CD
.yml
GitLab · Magento · Release-Struktur · Symlink · Rollback
Magento Release-Struktur
releases/, current/ und shared/ von Grund auf

Ein Magento-Shop, der direkt in ein einzelnes Verzeichnis deployt wird, kann nicht in Sekunden zurückgerollt werden. Das Symlink-Modell mit releases/, current/ und shared/ ist die Grundlage jedes Zero-Downtime-Deployments – und es muss einmalig korrekt aufgebaut werden, bevor die erste Pipeline läuft.

14 Min. Lesezeit releases/ · current/ · shared/ · Symlink · Retention · Rollback Magento 2.4 · Linux · GitLab CI · Nginx/Apache

Das Symlink-Modell ist das am weitesten verbreitete Deployment-Muster für PHP-Applikationen und der Standard in Tools wie Deployer, Capistrano und Ansistrano. Die Grundidee ist simpel: Jeder Release bekommt ein eigenes, timestampbasiertes Verzeichnis. Der Webserver zeigt auf einen Symlink namens current, der auf das aktive Release-Verzeichnis zeigt. Ein Deployment wechselt den Symlink atomar auf das neue Verzeichnis. Ein Rollback setzt den Symlink auf ein altes Verzeichnis zurück.

Für Magento ist dieses Modell besonders wichtig, weil Magento viele Dateien hat, die zwischen Releases geteilt werden müssen, aber gleichzeitig streng von Release-spezifischen Artefakten getrennt sein müssen. env.php enthält Datenbankdaten und darf nicht pro Release kopiert werden. pub/media enthält hochgeladene Bilder und darf bei einem Rollback nicht zurückgestellt werden. vendor/ und generated/ gehören dagegen exakt zum jeweiligen Code-Stand und dürfen nicht geteilt werden.

Das Modell ist nicht kompliziert, aber es muss einmalig korrekt aufgebaut werden. Fehler in der initialen Struktur – falsche Symlink-Ziele, fehlende Berechtigungen, nicht vorbereitete shared/-Verzeichnisse – führen zu Deployment-Fehlern, die schwer zu debuggen sind, weil sie erst beim ersten tatsächlichen Deploy sichtbar werden. Deshalb muss der Aufbau der Release-Struktur dokumentiert, geskriptet und auf Staging getestet werden, bevor Production konfiguriert wird.

2. Die vollständige Verzeichnisstruktur

Die folgende Verzeichnisstruktur zeigt, wie ein Magento-Server nach zwei erfolgreichen Deployments aussieht. Das releases/-Verzeichnis enthält timestampbasierte Unterverzeichnisse für jeden Deploy. Das current-Symlink zeigt auf das aktive Release. Das shared/-Verzeichnis enthält alle persistenten Dateien, die zwischen Releases geteilt werden.

# Server directory structure after two successful deployments
# /var/www/magento/ — base deployment path
#
# current -> releases/20260509-143022   (symlink to active release)
# releases/
#   20260509-143022/                    (latest release — current)
#     app/
#       code/
#       design/
#       etc/
#         config.php                    (versioned module config)
#         env.php -> shared/app/etc/env.php   (symlink to shared)
#     generated/                        (DI factories from build)
#     pub/
#       media -> shared/pub/media       (symlink to shared media)
#       static/                         (built CSS/JS — release-specific)
#     var/
#       cache/                          (runtime cache — gitignored)
#       log -> shared/var/log           (symlink to shared logs)
#       session -> shared/var/session   (symlink to shared sessions)
#     vendor/                           (composer dependencies)
#   20260509-120500/                    (previous release — keep for rollback)
#     [same structure as above]
# shared/
#   app/
#     etc/
#       env.php                         (environment-specific config)
#   pub/
#     media/                            (persistent uploaded files)
#   var/
#     log/                              (persistent log files)
#     session/                          (persistent sessions)

Diese Struktur zeigt auf einen Blick, welche Dateien release-spezifisch und welche shared sind. Der Symlink current ist der einzige Zeiger, den der Webserver kennt – er muss nur einmal in der Webserver-Konfiguration definiert werden und gilt dann für alle Releases automatisch. Das ist der entscheidende Vorteil: Der Wechsel zwischen Releases erfordert keine Webserver-Konfigurationsänderung.

3. releases/: das Release-Archiv

Das releases/-Verzeichnis ist das Archiv aller deploybaren Releases. Jeder Deploy-Job erstellt ein neues Unterverzeichnis mit einem timestampbasierten Namen – typischerweise im Format YYYYMMDD-HHMMSS, was automatische Sortierbarkeit nach Datum garantiert. Dieser Timestamp ist gleichzeitig die Release-ID, die für Rollbacks verwendet wird.

Wichtig ist, dass jedes Release-Verzeichnis vollständig und in sich konsistent ist. Das bedeutet: vendor/, generated/, pub/static/ und der Code selbst sind vollständig im Release-Verzeichnis vorhanden. Nur die Shared-Pfade werden als Symlinks eingehängt. Wenn ein Release-Verzeichnis aufgerufen wird, muss Magento ohne weitere Konfigurationsschritte funktionieren – alle notwendigen Dateien sind entweder direkt im Verzeichnis oder über Symlinks erreichbar.

Der current-Symlink ist das Herzstück des Symlink-Deployment-Modells. Er ist ein einfacher symbolischer Link, der auf das aktive Release-Verzeichnis zeigt. Der Befehl ln -sfn /var/www/magento/releases/20260509-143022 /var/www/magento/current setzt den Symlink auf das neue Release – dieser Vorgang ist atomar auf Linux-Dateisystemebene. Das bedeutet: Kein HTTP-Request sieht einen Zwischenzustand, bei dem current auf ein halbfertiges Release zeigt.

Der Webserver muss mit follow_symlinks oder dem Äquivalent konfiguriert sein, damit er dem current-Symlink folgt und das eigentliche Release-Verzeichnis ausliefert. Nginx benötigt die Optionen disable_symlinks off (Standard) oder die Verwendung des absoluten Pfads. Apache benötigt Options +FollowSymlinks. Ein falsch konfigurierter Webserver, der Symlinks nicht folgt, führt zu 403-Fehlern – ein Fehler, der beim initialen Setup auftritt und nicht bei jedem Deploy.

# GitLab CI deploy job — symlink switch is the zero-downtime moment
deploy:production:
  stage: deploy
  script:
    - |
      RELEASE_ID="$(date +%Y%m%d-%H%M%S)"
      BASE="${DEPLOY_PATH}"
      RELEASE="${BASE}/releases/${RELEASE_ID}"
      SHARED="${BASE}/shared"
      CURRENT="${BASE}/current"

      echo "Deploying release: ${RELEASE_ID}"

      # Step 1: Create and populate release directory
      ssh "${DEPLOY_USER}@${DEPLOY_HOST}" "mkdir -p ${RELEASE}"
      rsync -az --delete \
        --exclude="pub/media" \
        --exclude="var/log" \
        --exclude="var/session" \
        --exclude="var/cache" \
        --exclude="app/etc/env.php" \
        ./ "${DEPLOY_USER}@${DEPLOY_HOST}:${RELEASE}/"

      # Step 2: Link shared paths inside new release
      ssh "${DEPLOY_USER}@${DEPLOY_HOST}" bash -s << REMOTE
        set -euo pipefail
        ln -sfn "${SHARED}/app/etc/env.php" "${RELEASE}/app/etc/env.php"
        ln -sfn "${SHARED}/pub/media"       "${RELEASE}/pub/media"
        ln -sfn "${SHARED}/var/log"         "${RELEASE}/var/log"
        ln -sfn "${SHARED}/var/session"     "${RELEASE}/var/session"
REMOTE

      # Step 3: Run Magento release steps before activation
      ssh "${DEPLOY_USER}@${DEPLOY_HOST}" \
        "cd ${RELEASE} && bin/magento setup:upgrade --keep-generated"
      ssh "${DEPLOY_USER}@${DEPLOY_HOST}" \
        "cd ${RELEASE} && bin/magento cache:flush"

      # Step 4: Atomic symlink switch — zero-downtime activation
      ssh "${DEPLOY_USER}@${DEPLOY_HOST}" \
        "ln -sfn ${RELEASE} ${CURRENT}"

      echo "Release ${RELEASE_ID} activated successfully"

5. shared/: persistente Laufzeitdaten

Das shared/-Verzeichnis enthält alle Dateien, die zwischen Releases geteilt werden: pub/media mit den hochgeladenen Bildern, var/log mit den Logs, var/session mit den Sitzungsdaten und app/etc/env.php mit der umgebungsspezifischen Konfiguration. Diese Dateien existieren außerhalb des Release-Modells – sie sind weder release-spezifisch noch versioniert.

Das shared/-Verzeichnis darf niemals Teil eines Deployments oder Rollbacks sein. Sein Inhalt ändert sich durch Nutzeraktionen (Uploads in pub/media) und durch das System (Logs in var/log), nicht durch Deployments. Eine wichtige Konsequenz: Wenn die env.php in shared/ aktualisiert werden muss – etwa weil sich Datenbankverbindungsdaten geändert haben – muss das außerhalb der Deploy-Pipeline geschehen, direkt auf dem Server oder über ein separates Konfigurations-Management-System.

6. Server-Initialisierung: einmalig und korrekt

Die Initialisierung der Release-Struktur muss vor dem ersten Deploy-Job einmalig auf dem Server ausgeführt werden. Sie umfasst das Anlegen aller notwendigen Verzeichnisse, das Setzen der korrekten Berechtigungen und das Platzieren der initialen env.php in shared/. Ein geskriptetes Initialisierungsverfahren ist besser als manuelle SSH-Befehle, weil es dokumentiert und reproduzierbar ist.

Die env.php für shared/ muss vor dem ersten Deploy vorhanden sein – der Deploy-Job setzt nur den Symlink, erstellt aber keine env.php. Ein fehlender Symlink-Ziel führt zum Deployment-Fehler. Ein Skript, das die Existenz der env.php vor dem Deploy prüft, verhindert diesen Fehler und gibt eine klare Fehlermeldung statt eines kryptischen Symlink-Fehlers.

# One-time server initialization script
# Run this before the first pipeline deployment
initialize:server:
  stage: deploy
  when: manual
  script:
    - |
      ssh "${DEPLOY_USER}@${DEPLOY_HOST}" bash << 'REMOTE'
        set -euo pipefail

        BASE="${DEPLOY_PATH:-/var/www/magento}"
        SHARED="${BASE}/shared"

        echo "=== Initializing Magento release structure ==="

        # Create directory structure
        mkdir -p "${BASE}/releases"
        mkdir -p "${SHARED}/app/etc"
        mkdir -p "${SHARED}/pub/media"
        mkdir -p "${SHARED}/var/log"
        mkdir -p "${SHARED}/var/session"

        # Set permissions (deploy user: rwx, www-data: rx)
        chmod 755 "${BASE}"
        chmod 755 "${BASE}/releases"
        chmod 755 "${SHARED}"
        chmod 775 "${SHARED}/pub/media"   # web server needs write for uploads
        chmod 770 "${SHARED}/var/log"
        chmod 770 "${SHARED}/var/session"
        chmod 750 "${SHARED}/app/etc"

        # Guard: env.php must be placed manually before first deployment
        if [ ! -f "${SHARED}/app/etc/env.php" ]; then
          echo ""
          echo "ERROR: ${SHARED}/app/etc/env.php is missing."
          echo "Copy your production env.php to this path before deploying."
          echo ""
          exit 1
        fi

        echo "=== Initialization complete ==="
        echo "Base path: ${BASE}"
        echo "Releases:  ${BASE}/releases/ (empty)"
        echo "Shared:    ${SHARED}/"
        ls -la "${SHARED}/"
      REMOTE

7. Webserver-Konfiguration für das Symlink-Modell

Nginx muss den Document Root auf /var/www/magento/current/pub setzen – nicht auf das releases/-Verzeichnis oder den absoluten Pfad eines bestimmten Releases. Das current-Symlink führt nginx dann zum aktiven Release. Die Direktive disable_symlinks off ist in nginx der Standard und muss nicht explizit gesetzt werden – aber auf manchen gehosteten Umgebungen oder bei restriktiven nginx-Konfigurationen muss sie explizit aktiviert werden.

PHP-FPM zeigt ebenfalls auf das current-Verzeichnis als Root. Ein wichtiger Punkt bei PHP-FPM: Das open_basedir-Setting muss auf den absoluten Pfad beider möglichen Pfade gesetzt werden – also sowohl das releases/-Verzeichnis als auch das shared/-Verzeichnis. Andernfalls schlägt der Zugriff auf Shared-Dateien durch den Symlink fehl, weil PHP den Symlink-Ziel-Pfad nicht im erlaubten Verzeichnisbaum findet.

8. Retention-Policy: alte Releases aufräumen

Ohne Retention-Policy wächst das releases/-Verzeichnis bei jedem Deploy um ein vollständiges Magento-Verzeichnis an – das sind bei aktivem Deployment-Betrieb schnell hunderte Gigabytes. Eine Retention-Policy legt fest, wie viele alte Releases aufbewahrt werden, und löscht ältere automatisch nach jedem erfolgreichen Deployment. Empfohlen werden fünf Releases – das ermöglicht Rollbacks auf die letzten vier Deployments.

Das Aufräumen alter Releases muss nach dem Aktivieren des neuen Releases, aber vor dem Verlassen des Deploy-Jobs erfolgen. Es darf niemals das aktuell aktive Release löschen – deshalb muss die Cleanup-Logik das current-Symlink-Ziel aus der Liste der zu löschenden Releases ausschließen. Der einfachste Ansatz ist, die letzten N Releases nach Datum sortiert zu behalten und alle anderen zu löschen – das funktioniert zuverlässig mit dem timestamp-basierten Namensschema.

9. Release-Modelle im Vergleich

Der Unterschied zwischen einem Deployment direkt in ein einzelnes Verzeichnis und dem Symlink-Modell mit releases/, current/ und shared/ zeigt sich am deutlichsten in Ausnahmesituationen: einem fehlgeschlagenen Deployment, einem dringenden Rollback oder einem Server-Neustart während eines laufenden Deployments.

Szenario Flat-Deployment Symlink-Release-Modell Auswirkung
Rollback nötig Altes Backup einspielen (Minuten) Symlink zurücksetzen (Sekunden) Rollback-Zeit: Sekunden statt Minuten
Deploy bricht ab Shop zeigt inkonsistenten Zustand current zeigt noch altes Release Kein Shop-Ausfall bei gebrochenem Deploy
Mehrere Deployments gleichzeitig Datei-Konflikte möglich Jeder Deploy in eigenem Verzeichnis Kein Konflikt, letzter Symlink-Wechsel gewinnt
Release-Historie Kein Überblick über vergangene Deploys ls releases/ zeigt alle vergangenen Nachvollziehbarkeit und Audit-Trail
Downtime beim Deploy Während rsync aktiv, inkonsistenter Zustand Atomarer Symlink-Wechsel, kein Zwischenzustand Echter Zero-Downtime-Deployment-Moment

Die Tabelle zeigt, dass das Symlink-Release-Modell nicht nur für den Rollback-Fall besser ist, sondern für jedes Ausnahmeszenario. Der atomare Symlink-Wechsel macht den eigentlichen Deployment-Moment sicher. Jedes Release in einem eigenen Verzeichnis verhindert Konflikte und ermöglicht eine klare Release-Historie. Das Flat-Deployment-Modell hat hingegen keine dieser Eigenschaften – es ist einfacher aufzubauen, aber in der Produktion deutlich riskanter.

10. Zusammenfassung

Die Release-Struktur mit releases/, current/ und shared/ ist das Fundament jedes Zero-Downtime-fähigen Magento-Deployments. releases/ beherbergt timestampbasierte Release-Verzeichnisse, die jeweils eine vollständige, in sich konsistente Magento-Installation enthalten. current/ ist ein Symlink, der atomar auf das aktive Release wechselt. shared/ enthält alle persistenten Dateien, die zwischen Releases geteilt werden – und niemals Teil eines Rollbacks sind.

Der Aufbau dieser Struktur ist eine einmalige Investition, die sich bei jedem folgenden Deployment auszahlt. Rollbacks dauern Sekunden statt Minuten. Deployments, die während der Übertragungsphase abbrechen, hinterlassen den Shop in einem funktionsfähigen Zustand. Die Release-Historie ist auf dem Server sichtbar und ermöglicht Audits und Diagnosen. Und das wichtigste: Die Basis für Zero-Downtime ist gelegt, ohne dass ein Loadbalancer oder eine Blue-Green-Infrastruktur benötigt wird.

Magento Release-Struktur — Das Wichtigste auf einen Blick

releases/

Timestampbasierte Verzeichnisse, eines pro Deploy. Jedes vollständig und konsistent. Mindestens 3–5 für Rollback-Fähigkeit behalten.

current/

Atomarer Symlink auf das aktive Release. Kein Webserver-Neustart beim Wechsel. ln -sfn ist die eigentliche Zero-Downtime-Operation.

shared/

pub/media, var/log, var/session, app/etc/env.php. Persistent über alle Releases – nie Teil eines Rollbacks. Einmalig beim Server-Setup anlegen.

Initialisierung

Einmalig vor dem ersten Deploy. shared/-Struktur anlegen, Berechtigungen setzen, env.php platzieren. Danach läuft jeder Deploy-Job automatisch.

11. FAQ: Magento Release-Struktur mit releases/, current/ und shared/

1Wie viele Releases aufbewahren?
Mindestens 3, empfohlen 5. Ermöglicht Rollback auf letzte vier Deployments. Mehr als 10 belastet Speicher unnötig – jedes Release hat vendor und generated.
2Ist ln -sfn wirklich atomar?
Ja. Der Linux-Kernel ersetzt den Symlink in einer einzigen Operation – kein HTTP-Request sieht einen Zwischenzustand. Das ist die eigentliche Zero-Downtime-Garantie.
3Was passiert wenn ein Deploy während rsync abbricht?
current zeigt noch auf das alte Release – der Shop läuft unverändert weiter. Das halbfertige Release-Verzeichnis kann gefahrlos gelöscht werden.
4Muss der Webserver nach dem Symlink-Wechsel neu starten?
Nein. Nginx und Apache folgen dem Symlink bei jeder Anfrage. PHP-FPM reload ist für OPcache-Invalidierung empfohlen – aber kein harter Neustart nötig.
5OPcache nach Symlink-Wechsel invalidieren?
php-fpm reload oder OPcache-Reset-Skript nach dem Symlink-Wechsel. Ohne Invalidierung liefert PHP-FPM möglicherweise gecachten Code des alten Releases aus.
6Release-Struktur auf mehreren Servern?
Jeder Server bekommt eigene Release-Struktur. Deploy-Job überträgt und wechselt auf allen Servern. pub/media kann über NFS oder S3 geteilt werden.
7Welches Namensschema für Release-Verzeichnisse?
YYYYMMDD-HHMMSS ist bewährt: automatisch chronologisch sortierbar, Retention-Cleanup trivial. Git-Tag oder Pipeline-ID verliert die Sortierbarkeit.
8open_basedir und Symlinks in PHP-FPM?
open_basedir muss releases/ und shared/ einschließen. Sonst schlägt PHP-FPM beim Zugriff auf Shared-Dateien durch Symlinks fehl – 403 oder Permission-Fehler.
9Wie env.php beim ersten Deployment bereitstellen?
Manuell per SSH oder Provisioning-Skript außerhalb der Deploy-Pipeline. Niemals als Pipeline-Artefakt – env.php enthält Secrets (DB-Passwörter, Redis-Keys).
10releases/ und shared/ auf verschiedenen Dateisystemen?
Nein. Symlinks funktionieren nicht über Dateisystemgrenzen. releases/, current/ und shared/ müssen auf demselben Dateisystem liegen.