Schritt für Schritt
Der atomare Symlink-Wechsel ist der technische Kern von Zero-Downtime-Deployments für Magento. Statt Dateien direkt zu überschreiben – was Inkonsistenz erzeugt – wird das neue Release vollständig vorbereitet und dann in einem einzigen Kernel-Systemcall als current aktiviert. Rollback ist damit kein Notfallplan, sondern eine Millisekunden-Operation.
Inhaltsverzeichnis
- 1. Das Symlink-Switch-Konzept erklärt
- 2. Verzeichnisstruktur auf dem Server vorbereiten
- 3. Shared-Pfade: was zwischen Releases geteilt wird
- 4. Der atomare Symlink-Wechsel: ln -sfn im Detail
- 5. Neues Release vorbereiten: Schritt für Schritt
- 6. Symlink-Switch in der GitLab-Pipeline integrieren
- 7. Symlink-Switch vs. direktes Überschreiben im Vergleich
- 8. Rollback: in Sekunden auf das vorherige Release zurück
- 9. Release-Retention: alte Releases aufräumen
- 10. Zusammenfassung
- 11. FAQ
1. Das Symlink-Switch-Konzept erklärt
Beim Symlink-Switch-Deployment – auch bekannt als Capistrano-Style oder Release-basiertes Deployment – wird jedes Release als eigenes Verzeichnis auf dem Server abgelegt. Ein Symlink namens current zeigt immer auf das aktuell aktive Release. Der Webserver (nginx, apache) ist so konfiguriert, dass sein document_root oder root auf das current-Verzeichnis zeigt. Um eine neue Version zu aktivieren, genügt ein einziger Befehl: ln -sfn /var/www/magento/releases/20260509-120000 /var/www/magento/current. Dieser Befehl ist atomar – aus Sicht des Kernels ist es eine einzige Operation, die entweder vollständig gelingt oder vollständig fehlschlägt.
Die Atomarität ist das Entscheidende. Wer Dateien direkt überschreibt – zum Beispiel mit rsync --delete direkt in das Webserver-Root – erzeugt eine Zeitspanne, in der manche Dateien schon die neue und manche noch die alte Version haben. In dieser Inkonsistenzphase können PHP-Klassen geladen werden, die mit anderen Klassen aus unterschiedlichen Releases nicht kompatibel sind. Der Symlink-Switch eliminiert diese Inkonsistenzphase: Der Wechsel passiert auf Kernel-Ebene in einer Operation, und alle nachfolgenden Requests sehen ausschließlich das neue Release.
Für Magento ist dieses Deployment-Modell besonders geeignet, weil Magento eine komplexe Abhängigkeitsstruktur zwischen PHP-Klassen, Generated Code, Static Content und Konfigurationsdateien hat. Ein Inkonsistenz-Fenster zwischen diesen Komponenten führt zu Fehlern, die schwer zu reproduzieren und zu debuggen sind. Der Symlink-Switch schließt dieses Fenster vollständig.
2. Verzeichnisstruktur auf dem Server vorbereiten
Bevor das erste Deployment stattfinden kann, muss die Verzeichnisstruktur auf dem Zielserver initialisiert werden. Diese Einrichtung ist ein einmaliger Schritt, der manuell oder per Skript durchgeführt wird. Die Struktur besteht aus drei Hauptkomponenten: dem releases-Verzeichnis, dem shared-Verzeichnis und dem current-Symlink. Das releases-Verzeichnis enthält nummerierte oder datumsstempelbasierte Unterverzeichnisse, eines pro Release. Das shared-Verzeichnis enthält alle Dateien und Verzeichnisse, die zwischen Releases geteilt werden – sie sollen nicht mit jedem Deployment überschrieben werden. Der current-Symlink zeigt auf das aktuell aktive Release.
Die Shared-Verzeichnisse werden beim allerersten Deployment mit ihren initialen Inhalten befüllt. Bei allen weiteren Deployments werden nur Symlinks in das neue Release angelegt, die auf die Shared-Verzeichnisse zeigen – der Inhalt selbst bleibt unberührt. Das bedeutet: Hochgeladene Bilder in pub/media bleiben über alle Releases hinweg erhalten, Logs akkumulieren über Releases, und die datenbankspezifische env.php muss nur einmal angelegt werden.
# One-time server initialization — run manually or via dedicated init job
# This sets up the directory structure before the first deployment
initialize:server:
stage: deploy
when: manual
before_script:
- eval $(ssh-agent -s)
- ssh-add "$SSH_PRIVATE_KEY"
- mkdir -p ~/.ssh && echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
script:
- |
ssh "$DEPLOY_USER@$DEPLOY_HOST" bash -s <<'REMOTE'
set -euo pipefail
# Create base directory structure
mkdir -p "$DEPLOY_PATH/releases"
mkdir -p "$DEPLOY_PATH/shared/app/etc"
mkdir -p "$DEPLOY_PATH/shared/pub/media"
mkdir -p "$DEPLOY_PATH/shared/var/log"
mkdir -p "$DEPLOY_PATH/shared/var/session"
mkdir -p "$DEPLOY_PATH/shared/var/cache"
# Set correct permissions for shared directories
chmod 775 "$DEPLOY_PATH/shared/pub/media"
chmod 775 "$DEPLOY_PATH/shared/var/log"
chmod 775 "$DEPLOY_PATH/shared/var/session"
echo "[OK] Server initialized at $DEPLOY_PATH"
echo "[INFO] Next step: place env.php in $DEPLOY_PATH/shared/app/etc/"
REMOTE
environment:
name: production
3. Shared-Pfade: was zwischen Releases geteilt wird
Die Entscheidung, welche Pfade geteilt und welche per-Release-separat gehalten werden, ist zentral für das Symlink-Switch-Modell. Als Faustregel gilt: Alles, was sich mit dem Deployment-Prozess ändert (Code, Generated, Static), ist per-Release. Alles, was umgebungsabhängig ist oder von Benutzern beeinflusst wird, ist Shared. Für Magento ergibt sich damit folgende Unterteilung.
Shared sind: app/etc/env.php (datenbankspezifische Credentials und Umgebungskonfiguration), pub/media (user-generated content: Produktbilder, CMS-Uploads), var/log (Log-Dateien, die über Releases hinweg akkumulieren), var/session (wenn dateibasierte Sessions verwendet werden) und optional var/cache (wenn Cache-Inhalte zwischen Releases wiederverwendet werden sollen – ist aber oft effizienter, den Cache nach dem Switch zu flushen statt zu teilen). Per-Release sind: vendor/, generated/, pub/static/, app/code/, app/design/, app/etc/config.php und alle anderen Source-Code-Dateien.
4. Der atomare Symlink-Wechsel: ln -sfn im Detail
Der Befehl ln -sfn /pfad/zu/neuem/release /pfad/zu/current ist die Kernoperation des Symlink-Switch-Deployments. Die Flags bedeuten: -s (symbolischer Link), -f (force: ersetze existierenden Link), -n (no-dereference: behandle existierenden Symlink als Datei, nicht als Ziel des Links). Das -n-Flag ist entscheidend: Ohne es würde ln -sf bei einem existierenden Symlink versuchen, den Link innerhalb des verlinkten Verzeichnisses zu erstellen, statt den Link selbst zu ersetzen. Mit -n wird der existierende Symlink direkt ersetzt – und das ist die atomare Operation.
Auf Linux-Systemen ist ln -sfn intern eine rename(2)-Syscall-Operation, die garantiert atomar ist: Das Verzeichniseintrag ändert sich von einem Inodezeiger zum nächsten ohne Zwischenzustand. Aus Sicht aller laufenden Prozesse – inklusive nginx-Worker-Prozesse, die Requests bearbeiten – ist der Switch instantan. Requests, die bereits im Bearbeitung sind und auf das alte Release zugreifen, können zu Ende laufen. Alle neuen Requests nach dem Switch sehen das neue Release. Es gibt keine Situation, in der ein Request auf ein Verzeichnis zeigt, das sich gerade im Übergang befindet.
5. Neues Release vorbereiten: Schritt für Schritt
Die Vorbereitung eines neuen Releases folgt einer festen Reihenfolge, die vor dem Symlink-Switch vollständig abgeschlossen sein muss. Erst wenn alle Vorbereitungsschritte erfolgreich waren, darf der Switch stattfinden. Dieser Grundsatz verhindert, dass ein halbfertiges Release aktiviert wird. In der Praxis bedeutet das: Das Artefakt wird ins Release-Verzeichnis entpackt, Shared-Pfade werden als Symlinks eingebunden, Datenbankmigrationen werden ausgeführt (wenn kompatibel mit dem alten Release – andernfalls ist Maintenance Mode notwendig), und alle anderen serverseitigen Schritte laufen durch – und nur wenn all das erfolgreich ist, folgt der ln -sfn-Befehl.
Der Maintenance Mode in Magento setzt eine Datei var/.maintenance.flag. Wenn diese Datei existiert, zeigt Magento eine Wartungsseite für alle nicht-freigegebenen IPs. Für echtes Zero Downtime muss der Maintenance Mode so kurz wie möglich sein oder ganz vermieden werden. Mit Expand-Contract-Datenbankmigrationen (rückwärtskompatible Schemaänderungen) ist es möglich, setup:upgrade ohne Maintenance Mode laufen zu lassen – das neue Schema ist kompatibel mit dem alten Code. Erst nach dem Symlink-Switch greift der neue Code auf das neue Schema zu.
deploy:production:
stage: deploy
before_script:
- eval $(ssh-agent -s)
- ssh-add "$SSH_PRIVATE_KEY"
- mkdir -p ~/.ssh && echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
script:
# Transfer artifact to server
- scp -o StrictHostKeyChecking=yes \
"$ARTIFACT_NAME" \
"$DEPLOY_USER@$DEPLOY_HOST:/tmp/$ARTIFACT_NAME"
- |
ssh -o StrictHostKeyChecking=yes "$DEPLOY_USER@$DEPLOY_HOST" bash -s <<REMOTE
set -euo pipefail
# Generate unique release ID from timestamp
readonly RELEASE_ID="\$(date +%Y%m%d-%H%M%S)"
readonly RELEASE_PATH="\$DEPLOY_PATH/releases/\$RELEASE_ID"
readonly SHARED="\$DEPLOY_PATH/shared"
echo "[INFO] Preparing release \$RELEASE_ID"
mkdir -p "\$RELEASE_PATH"
# Unpack artifact into release directory
tar -xzf "/tmp/$ARTIFACT_NAME" -C "\$RELEASE_PATH"
rm -f "/tmp/$ARTIFACT_NAME"
# Link shared files — env.php is environment-specific, must not be in artifact
ln -sfn "\$SHARED/app/etc/env.php" "\$RELEASE_PATH/app/etc/env.php"
# Link shared directories — media persists across releases
rm -rf "\$RELEASE_PATH/pub/media"
ln -sfn "\$SHARED/pub/media" "\$RELEASE_PATH/pub/media"
rm -rf "\$RELEASE_PATH/var/log"
ln -sfn "\$SHARED/var/log" "\$RELEASE_PATH/var/log"
rm -rf "\$RELEASE_PATH/var/session"
ln -sfn "\$SHARED/var/session" "\$RELEASE_PATH/var/session"
# Run database migrations — use expand-contract for zero downtime
cd "\$RELEASE_PATH"
bin/magento setup:upgrade --keep-generated --no-interaction
# Atomic symlink switch — this is the zero-downtime moment
ln -sfn "\$RELEASE_PATH" "\$DEPLOY_PATH/current"
echo "[OK] Symlink switched to \$RELEASE_ID"
# Flush cache after switch
bin/magento cache:flush
echo "[OK] Cache flushed"
# Cleanup: keep only last 5 releases
ls -1dt "\$DEPLOY_PATH/releases"/*/ | tail -n +6 | xargs --no-run-if-empty rm -rf
echo "[OK] Old releases cleaned up"
REMOTE
environment:
name: production
needs: [verify:staging]
when: manual
only:
- tags
6. Symlink-Switch in der GitLab-Pipeline integrieren
In GitLab wird der Symlink-Switch-Deployment-Prozess als Deploy-Job implementiert, der per SSH auf dem Zielserver ein Skript ausführt. Das Skript enthält alle Schritte von der Artefakt-Entpackung bis zum Symlink-Switch in einer einzigen SSH-Verbindung. Das ist wichtig: Wenn jeder Schritt eine eigene SSH-Verbindung öffnet, gibt es keine Garantie, dass Schritte atomar in Bezug auf andere Prozesse auf dem Server ablaufen. Eine einzige SSH-Verbindung mit einem Bash-Heredoc sorgt dafür, dass alle Schritte sequenziell und in einer kontrollierten Subshell ablaufen.
Ein häufiger Fehler bei der GitLab-Integration: Der Deploy-Job schlägt in der Mitte fehl, nachdem der Symlink bereits gewechselt hat, aber bevor der Cache-Flush abgeschlossen ist. Um das zu verhindern, verwendet man set -euo pipefail im Remote-Skript und stellt sicher, dass der ln -sfn-Befehl so spät wie möglich im Skript steht – also erst nachdem alle Vorbereitungsschritte erfolgreich waren. Wenn der Job dann fehlschlägt, zeigt der Symlink immer noch auf das alte, funktionierende Release.
7. Symlink-Switch vs. direktes Überschreiben im Vergleich
Der direkte Vergleich zwischen dem Symlink-Switch-Modell und dem klassischen Ansatz, Dateien direkt in das Webserver-Root zu kopieren, zeigt deutlich, warum das Symlink-Modell für Produktionssysteme das einzig vertretbare ist.
| Kriterium | Direktes Überschreiben | Symlink-Switch | Vorteil |
|---|---|---|---|
| Atomarität | Keine — Inkonsistenzfenster | Kernel-atomar (rename syscall) | Symlink eliminiert Inkonsistenz |
| Rollback | Erneutes Deployment notwendig | ln -sfn auf vorheriges Release | Symlink in Millisekunden |
| Deploy-Risiko | Aktives Verzeichnis während rsync | Neues Release vorbereitet, dann Switch | Symlink risikoärmer |
| Parallele Releases | Nicht möglich | Mehrere Releases gleichzeitig auf Server | Symlink ermöglicht Parallelität |
| Speicherbedarf | Ein Release auf dem Server | Mehrere Releases (konfigurierbar) | Direktes Überschreiben sparsamer |
Der einzige reale Nachteil des Symlink-Modells ist der erhöhte Speicherbedarf durch mehrere vorgehaltene Releases. Bei fünf vorgehaltenen Releases und einem Magento-Paket von 500 MB sind das 2,5 GB Speicher, der für die Release-Historie reserviert ist. Das ist auf modernen Servern kein ernstes Hindernis und wird durch die Vorteile in Sicherheit, Rollback-Fähigkeit und Deployment-Qualität weit übertroffen.
8. Rollback: in Sekunden auf das vorherige Release zurück
Rollback im Symlink-Switch-Modell ist die eleganteste Eigenschaft des gesamten Deployment-Ansatzes. Das vorherige Release liegt bereits vollständig vorbereitet im releases-Verzeichnis. Ein einziger ln -sfn-Befehl auf das vorherige Release-Verzeichnis macht es wieder aktiv. Das dauert Millisekunden. Anschließend folgt ein Cache-Flush – der dauert Sekunden. Das gesamte Rollback ist in unter einer Minute abgeschlossen, unabhängig von der Größe der Magento-Installation.
In GitLab wird ein Rollback-Job mit when: manual vorbereitet. Er bestimmt automatisch das vorherige Release, indem er die Release-Verzeichnisse nach Datum sortiert und das zweitneuste wählt. Der Job kann auch eine Variable ROLLBACK_RELEASE akzeptieren, mit der ein bestimmtes Release explizit angegeben werden kann – praktisch, wenn man nicht auf das unmittelbar vorherige, sondern auf ein älteres Release zurückrollen möchte. Nach dem Rollback sollte ein Verify-Job laufen, um sicherzustellen, dass das alte Release tatsächlich korrekt funktioniert.
9. Release-Retention: alte Releases aufräumen
Ohne automatisches Aufräumen wächst das releases-Verzeichnis mit jedem Deployment. Bei täglichen Deployments wären nach einem Monat 30 Release-Verzeichnisse vorhanden. Das ist nicht nur ein Speicherproblem, sondern auch ein Übersichtlichkeitsproblem. Die empfohlene Retention liegt bei fünf Releases: Das aktuelle Release und vier historische Releases, auf die im Notfall zurückgerollt werden kann. Diese Zahl ist konfigurierbar und sollte auf die eigene Rollback-Strategie abgestimmt sein.
Das Aufräumen geschieht am Ende des Deploy-Skripts, nachdem der Symlink-Switch erfolgreich war. Der Befehl ls -1dt "$DEPLOY_PATH/releases"/*/ | tail -n +6 | xargs --no-run-if-empty rm -rf listet alle Release-Verzeichnisse sortiert nach Datum (neustes zuerst), überspringt die ersten fünf und löscht alle weiteren. Das aktive current-Verzeichnis ist durch den Symlink nicht gefährdet, auch wenn das Verzeichnis gelöscht wird – aber die Retention sollte immer so konfiguriert sein, dass das aktuell aktive Release nie in der Löschliste landet. Mit korrekter Datumsreihenfolge ist das gewährleistet.
rollback:production:
stage: deploy
when: manual
allow_failure: false
before_script:
- eval $(ssh-agent -s)
- ssh-add "$SSH_PRIVATE_KEY"
- mkdir -p ~/.ssh && echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
script:
- |
ssh -o StrictHostKeyChecking=yes "$DEPLOY_USER@$DEPLOY_HOST" bash -s <<REMOTE
set -euo pipefail
# Determine rollback target — use ROLLBACK_RELEASE variable if set, else previous
if [[ -n "${ROLLBACK_RELEASE:-}" ]]; then
TARGET_RELEASE="\$DEPLOY_PATH/releases/$ROLLBACK_RELEASE"
else
# Auto-detect previous release (second newest by date)
TARGET_RELEASE="\$(ls -1dt "\$DEPLOY_PATH/releases"/*/ | sed -n '2p' | tr -d '/')"
fi
if [[ -z "\$TARGET_RELEASE" ]] || [[ ! -d "\$TARGET_RELEASE" ]]; then
echo "[ERROR] No rollback target found at: \$TARGET_RELEASE" >&2
exit 1
fi
CURRENT_RELEASE="\$(readlink -f "\$DEPLOY_PATH/current")"
echo "[INFO] Rolling back from: \$CURRENT_RELEASE"
echo "[INFO] Rolling back to: \$TARGET_RELEASE"
# Atomic symlink switch to previous release
ln -sfn "\$TARGET_RELEASE" "\$DEPLOY_PATH/current"
echo "[OK] Symlink switched to: \$TARGET_RELEASE"
# Flush cache after rollback
cd "\$DEPLOY_PATH/current"
bin/magento cache:flush
echo "[OK] Cache flushed after rollback"
REMOTE
environment:
name: production
variables:
ROLLBACK_RELEASE: ""
only:
- tags
- main
10. Zusammenfassung
Das Symlink-Switch-Deployment-Modell ist das Fundament von Zero-Downtime-Deployments für Magento. Die Release-Verzeichnisstruktur mit releases/, shared/ und dem current-Symlink ist einmalig auf dem Server einzurichten und bleibt dann stabil. Jedes neue Deployment legt ein neues Release-Verzeichnis an, bereitet es vollständig vor – inklusive Shared-Symlinks, Datenbankmigrationen und Cache-Vorbereitung – und aktiviert es dann mit einem einzigen ln -sfn-Befehl. Dieser Befehl ist atomar, dauert Millisekunden und erzeugt kein Inkonsistenzfenster.
Der größte Hebel liegt in der Rollback-Fähigkeit: Weil jedes Release vollständig auf dem Server vorliegt, ist ein Rollback kein zweites Deployment, sondern eine Millisekunden-Operation. Das verändert die Entscheidungsqualität im Team grundlegend: Deployments können mutiger und häufiger stattfinden, weil der Rückweg immer klar und schnell ist. Wer dieses Modell einmal eingeführt hat, will nicht mehr auf direktes Überschreiben zurückgehen.
Symlink-Switch-Deployment für Magento — Das Wichtigste auf einen Blick
Atomarität
ln -sfn ist eine Kernel-rename-Operation – kein Inkonsistenzfenster. Alle neuen Requests nach dem Switch sehen ausschließlich das neue Release.
Shared vs. Per-Release
env.php, pub/media, var/log und var/session sind Shared. vendor/, generated/, pub/static/ und alle Code-Dateien sind per-Release im Artefakt.
Deploy-Reihenfolge
Artefakt entpacken, Shared verlinken, Migrationen, dann erst Switch – nie vor Abschluss aller Vorbereitungsschritte wechseln.
Rollback
ln -sfn auf das vorherige Release + cache:flush. Unter einer Minute Gesamtdauer, unabhängig von der Installations-Größe.