CI/CD
.yml
GitLab · Magento · CI/CD · Deployment
env.php, config.php, app/etc
und Shared Configs im Release-Modell

env.php gehört nicht ins Repository und nicht in den Build-Artefakt. Wer das missachtet, baut ein Release-Modell, das beim ersten Umgebungswechsel bricht. Dieser Beitrag erklärt, wie app/etc, Shared-Verzeichnisse und config.php korrekt im Symlink-Release-Modell mit GitLab CI/CD zusammenspielen.

15 Min. Lesezeit env.php · config.php · Shared-Pfade · Symlinks · GitLab Magento 2.4 · PHP 8.4 · GitLab CI/CD

1. Was env.php, config.php und app/etc trennen

In Magento existieren zwei grundlegend verschiedene Konfigurationsdateien im Verzeichnis app/etc/, die in der Praxis häufig verwechselt werden: env.php und config.php. env.php enthält umgebungsspezifische Laufzeitdaten – Datenbankzugangsdaten, Redis-Verbindungen, Crypt-Key, Backend-URL und Session-Konfiguration. Diese Datei darf niemals in das Git-Repository eingecheckt werden, weil sie Geheimnisse enthält, die für jede Umgebung unterschiedlich sind. Sie wird einmalig auf dem Zielserver angelegt und über Shared-Pfade zwischen Releases geteilt.

config.php hingegen enthält den Modulstatus und kann optional Store-spezifische Konfigurationen enthalten, die über bin/magento app:config:dump exportiert wurden. Diese Datei ist deutlich unkritischer und kann im Repository versioniert werden, sofern das Team bewusst entschieden hat, die Konfiguration über das Repository zu steuern. Der Unterschied ist entscheidend für das Release-Modell: Was im Repository steht, wird mit jedem Release ausgerollt; was im Shared-Pfad liegt, bleibt über alle Releases hinweg konstant.

Das Verzeichnis app/etc/ selbst ist ein scheinbar einfacher Ordner, der im Release-Modell jedoch eine kritische Rolle spielt. Wer das Verzeichnis vollständig im Build-Artefakt mitliefert und danach env.php kopiert, arbeitet korrekt. Wer hingegen versucht, app/etc/ als Symlink auf ein Shared-Verzeichnis zu legen, riskiert, dass das Magento-Setup-System beim ersten Lauf Berechtigungsprobleme meldet. Die sicherste Lösung ist ein fester Ordner im Release-Verzeichnis mit einem danach aufgespielten env.php aus dem Shared-Bereich.

2. Das Release-Modell und die Rolle von Shared-Pfaden

Das Symlink-Release-Modell für Magento arbeitet mit einem einfachen Prinzip: Jeder Release landet in einem eigenen Verzeichnis unter releases/. Der aktive Release wird durch einen Symlink current referenziert, den der Webserver als Document-Root nutzt. Das Umschalten von einem Release auf den nächsten ist eine atomare Operation – ein einziger ln -sfn-Aufruf – und dauert Millisekunden. Laufende Requests unter dem alten Release werden noch abgeschlossen, neue Requests landen sofort im neuen Release.

Shared-Pfade sind die Verbindung zwischen diesem isolierten Release-Konzept und dem dauerhaften Serverzustand. Dateien und Verzeichnisse, die über Releases hinweg identisch bleiben oder sich außerhalb des Code-Lebenszyklus verändern, gehören in den shared/-Bereich. Klassische Kandidaten für Magento sind pub/media/, var/log/, var/session/ und app/etc/env.php. Der Deploy-Prozess erstellt für jeden neuen Release Symlinks auf diese Shared-Ressourcen, bevor der Symlink auf current umgestellt wird.

Das Konzept funktioniert nur, wenn die Shared-Struktur vor dem ersten Deployment angelegt und danach konsequent gepflegt wird. Ein häufiger Fehler in der Praxis: Das erste Deployment legt env.php im Release-Verzeichnis an statt im Shared-Ordner. Nach einem Rollback oder beim nächsten Deployment fehlt die Datei im neuen Release, weil niemand den Symlink angelegt hat. GitLab-Pipelines müssen daher einen expliziten Schritt enthalten, der prüft, ob der Shared-Ordner und seine kritischen Dateien vorhanden sind, bevor der Deploy-Job fortfährt.

3. Shared-Verzeichnisstruktur korrekt anlegen

Die initiale Einrichtung der Shared-Struktur auf dem Zielserver ist ein einmaliger Schritt, der aber sorgfältig dokumentiert werden muss. Teams, die diesen Schritt nicht dokumentieren, stehen beim Aufsetzen eines zweiten Servers oder nach einem Server-Wechsel vor dem Problem, dass niemand mehr weiß, welche Verzeichnisse manuell angelegt wurden und welche Berechtigungen sie haben. Die Shared-Struktur gehört in die Infrastruktur-Dokumentation und idealerweise in ein Setup-Skript, das versioniert und reproduzierbar ist.

Für ein typisches Magento-Projekt mit mehreren Stores und Hyvä-Frontend sieht die Shared-Struktur auf dem Zielserver folgendermaßen aus. Kritisch ist dabei, dass der var/-Ordner mit seinen Unterverzeichnissen als Symlink angelegt wird, der aber auf einen Shared-Pfad zeigt, der vom Webserver-Nutzer beschreibbar ist. Berechtigungsprobleme in var/ oder pub/media/ nach dem Deploy sind fast immer auf fehlende oder falsche Symlinks zurückzuführen.

# Initial server setup — run once before first deployment
# Script: bin/setup-shared.sh

setup:shared:
  stage: .pre
  when: manual
  script:
    - ssh "$DEPLOY_USER@$DEPLOY_HOST" "bash -s" << 'ENDSSH'
      set -euo pipefail
      BASE="${DEPLOY_PATH}/shared"

      # Create shared directories
      mkdir -p "${BASE}/app/etc"
      mkdir -p "${BASE}/pub/media"
      mkdir -p "${BASE}/var/log"
      mkdir -p "${BASE}/var/session"
      mkdir -p "${BASE}/var/cache"

      # Set correct ownership for web server user
      chown -R www-data:www-data "${BASE}"

      echo "Shared structure ready at ${BASE}"
      ENDSSH

Der manuelle Setup-Job in GitLab ist eine bewusste Entscheidung: Er soll nicht automatisch bei jedem Deployment laufen, aber er soll versioniert und reproduzierbar sein. Wer ihn als manuellen Job in der Pipeline hält, stellt sicher, dass die Shared-Struktur immer auf dieselbe Weise angelegt wird – unabhängig davon, wer im Team die Aufgabe übernimmt.

4. env.php sicher auf den Server bringen

Die env.php enthält Datenbankpasswörter, den Crypt-Key und Session-Konfigurationen. Sie darf weder im Git-Repository noch als GitLab-CI-Artefakt auftauchen. Der sicherste Weg, env.php auf den Server zu bringen, ist ein einmaliger manueller Upload über eine verschlüsselte Verbindung beim initialen Server-Setup. Danach liegt die Datei dauerhaft im Shared-Verzeichnis und wird von keinem Deployment-Prozess überschrieben.

Alternativ kann env.php durch einen Skript-Schritt aus GitLab-CI-Variablen generiert werden. In diesem Fall speichert man die einzelnen Werte als Protected und Masked Variables in GitLab und schreibt ein Skript, das die Datei aus diesen Variablen zusammensetzt. Das ist aufwendiger, aber vollständig automatisierbar und vermeidet manuelle Server-Zugänge für reguläre Deployments. Für Teams mit mehreren Umgebungen ist dieser Ansatz sauberer, weil alle env.php-Werte zentral in GitLab verwaltet werden und Environment-Scopes verhindern, dass Staging-Secrets auf Production landen.

Unabhängig vom gewählten Ansatz muss der Deploy-Job prüfen, ob env.php im Shared-Verzeichnis vorhanden ist, bevor er den Symlink auf current umstellt. Ein Deployment auf eine Umgebung ohne env.php führt unweigerlich zu einem weißen Bildschirm oder einer Datenbankfehlermeldung im Shop.

5. config.php im Build-Artefakt oder im Shared-Ordner?

Die Entscheidung, ob config.php im Git-Repository oder im Shared-Ordner liegt, hat direkte Auswirkungen auf das Release-Modell. Liegt config.php im Repository, wird sie mit jedem Deployment ausgerollt. Das ist der sauberere Ansatz, weil Änderungen am Modulstatus nachvollziehbar versioniert werden und der Build-Artefakt vollständig reproduzierbar ist. Magento-Teams, die Konfigurationsänderungen über bin/magento config:set und anschließendes app:config:dump und Git-Commit vornehmen, arbeiten nach diesem Modell.

Liegt config.php im Shared-Ordner, kann sie direkt auf dem Server geändert werden, ohne einen neuen Build zu starten. Das ist praktischer für Teams, die häufig Store-Konfigurationen anpassen, hat aber den Nachteil, dass die Konfiguration nicht versioniert ist und bei einem Server-Neuaufbau manuell wiederhergestellt werden muss. Für Produktionssysteme ist das erste Modell fast immer vorzuziehen, weil es Auditing, Rollback und Reproduzierbarkeit ermöglicht.

# deploy.yml — Shared symlinks and env.php placement
deploy:production:
  stage: deploy
  environment:
    name: production
    url: https://shop.example.com
  script:
    - ssh "$DEPLOY_USER@$DEPLOY_HOST" "bash -s" << ENDSSH
      set -euo pipefail

      RELEASE="${DEPLOY_PATH}/releases/$(date +%Y%m%d-%H%M%S)"
      SHARED="${DEPLOY_PATH}/shared"
      CURRENT="${DEPLOY_PATH}/current"

      # Verify shared structure exists before deploying
      test -f "${SHARED}/app/etc/env.php" \
        || { echo "ERROR: env.php missing in shared"; exit 1; }

      mkdir -p "${RELEASE}"
      rsync -az --delete "${CI_PROJECT_DIR}/" "${RELEASE}/"

      # Link env.php from shared (never from repository)
      cp "${SHARED}/app/etc/env.php" "${RELEASE}/app/etc/env.php"

      # Link persistent directories from shared
      rm -rf "${RELEASE}/pub/media"
      ln -sfn "${SHARED}/pub/media" "${RELEASE}/pub/media"
      rm -rf "${RELEASE}/var/log"
      ln -sfn "${SHARED}/var/log" "${RELEASE}/var/log"
      rm -rf "${RELEASE}/var/session"
      ln -sfn "${SHARED}/var/session" "${RELEASE}/var/session"

      # Atomic switch to new release
      ln -sfn "${RELEASE}" "${CURRENT}"
      echo "Release switched to ${RELEASE}"
      ENDSSH
  only:
    - tags

6. GitLab-Pipeline: Symlinks und Shared-Dateien im Deploy-Job

Der Deploy-Job in GitLab trägt die Verantwortung, das gebaute Artefakt auf den Server zu übertragen und die Shared-Pfade korrekt zu verknüpfen. Dieser Job muss idempotent sein – er soll auch dann korrekt funktionieren, wenn er wegen eines Fehlers erneut ausgeführt wird. Idempotenz erfordert, dass jede Operation ihren Zustand prüft, bevor sie Änderungen vornimmt. Ein ln -sfn ist bereits idempotent; ein cp überschreibt bei jedem Lauf, was für env.php korrekt ist.

Ein häufig übersehenes Detail: Wenn der Deploy-Job in einem Docker-Container auf dem GitLab-Runner läuft, kann er nicht direkt SSH-Verbindungen mit einem privaten Schlüssel aufbauen, ohne dass der Schlüssel vorher im Job verfügbar gemacht wird. Das geschieht über eine before_script-Sektion, die den SSH-Agenten startet und den Schlüssel aus einer GitLab-CI-Variable hinzufügt. Wer diesen Schritt vergisst oder unsauber implementiert, hinterlässt SSH-Schlüssel im Docker-Image-Layer.

Für Multi-Server-Deployments, bei denen mehrere Web-Nodes gleichzeitig aktualisiert werden müssen, wiederholt der Deploy-Job die Symlink-Operationen für jeden Server. Die Reihenfolge ist dabei nicht beliebig: Erst wenn alle Nodes das neue Release-Verzeichnis vollständig aufgebaut haben, wird der Symlink auf current umgestellt. So laufen alle Nodes exakt zur gleichen Zeit auf denselben Code.

7. Direkter Vergleich: Shared vs. nicht-Shared

Die Entscheidung, welche Dateien und Verzeichnisse in den Shared-Bereich gehören und welche mit jedem Release neu ausgerollt werden, ist eine der wichtigsten Architekturentscheidungen im Release-Modell. Falsch getroffene Entscheidungen führen zu Datenverlust oder zu Deployments, die bei jedem Lauf manuelle Korrekturen benötigen.

Datei / Verzeichnis Shared Im Release-Artefakt Begründung
app/etc/env.php Ja Nein Enthält Secrets, umgebungsspezifisch
app/etc/config.php Möglich Bevorzugt Modulstatus ist Teil des Releases
pub/media/ Ja Nein Nutzer-Uploads, release-unabhängig
pub/static/ Nein Ja (Build-Artefakt) Wird je Release neu gebaut
var/log/ Ja Nein Logs überspannen mehrere Releases

Die Tabelle zeigt eine klare Linie: Alles, was Secrets enthält, durch Nutzerinteraktion entsteht oder über Releases hinweg konsistent sein muss, gehört in den Shared-Bereich. Alles, was durch den Build-Prozess entsteht und zur Code-Version gehört, ist Teil des Release-Artefakts. Diese Trennung ist nicht nur sauberer, sondern ermöglicht auch Rollbacks, die nicht zu Datenverlust führen, weil die persistenten Daten niemals im Release-Verzeichnis liegen.

8. Typische Fehlerbilder bei env.php und Shared Configs

Das häufigste Fehlerbild in der Praxis ist eine fehlende oder leere env.php nach einem Deployment. Das passiert, wenn der Deploy-Job env.php aus dem Shared-Verzeichnis kopieren soll, das Shared-Verzeichnis aber nicht existiert oder die Datei fehlt. Magento gibt in diesem Fall keine verständliche Fehlermeldung aus – stattdessen erscheint eine leere Seite oder ein interner Serverfehler. Der Verify-Schritt in der Pipeline muss daher explizit prüfen, ob env.php vorhanden und syntaktisch gültig ist, bevor der Smoke Test auf die Shop-URL zugreift.

Ein zweites häufiges Fehlerbild entsteht beim erstmaligen Deployment auf eine neue Umgebung: Der Entwickler kopiert env.php ins Release-Verzeichnis statt ins Shared-Verzeichnis. Das erste Deployment funktioniert. Das zweite Deployment legt ein neues Release-Verzeichnis an, das keine env.php enthält, weil sie nur im ersten Release-Verzeichnis lag. Dieses Problem tritt immer dann auf, wenn die Shared-Struktur und der Deploy-Prozess nicht gemeinsam eingerichtet wurden.

Ein drittes Fehlerbild betrifft config.php und den Modulstatus. Wenn config.php im Shared-Ordner liegt und ein Deployment neue Module aktiviert, ohne die Shared-config.php zu aktualisieren, startet Magento nach dem Deployment mit einem inkonsistenten Modulstatus. Das äußert sich typischerweise in fehlenden Layoutblöcken, Fehlern im Admin-Panel oder nicht funktionierenden Extensions. Die Lösung ist, config.php entweder konsequent im Repository zu versionieren oder einen Post-Deploy-Schritt zu implementieren, der bin/magento app:config:import ausführt.

9. Rollback und Shared-Dateien: Was sich ändert, was bleibt

Der entscheidende Vorteil des Symlink-Release-Modells ist, dass ein Rollback auf einen früheren Release keine Daten zerstört. Da alle persistenten Daten im Shared-Bereich liegen und nicht im Release-Verzeichnis, passen die Daten zum alten Release genauso wie zum neuen. Ein Rollback ist semantisch identisch mit dem Umstellen des current-Symlinks auf ein älteres Release-Verzeichnis, gefolgt von einem Cache-Flush.

Die einzige Ausnahme sind Datenbankmigrationen. Wenn ein neuer Release Datenbankschema-Änderungen enthält, die durch bin/magento setup:upgrade ausgeführt wurden, ist die Datenbank nach dem Rollback möglicherweise nicht mehr vollständig kompatibel mit dem alten Code. Für diesen Fall muss der Rollback-Plan explizit die Datenbank-Kompatibilität adressieren – entweder durch rückwärtskompatible Schemaänderungen (Expand-Contract-Pattern) oder durch ein Datenbank-Backup vor dem Deployment.

# rollback.yml — Rollback to a specific previous release
rollback:production:
  stage: rollback
  when: manual
  environment:
    name: production
  script:
    - ssh "$DEPLOY_USER@$DEPLOY_HOST" "bash -s" << ENDSSH
      set -euo pipefail

      # List available releases for manual selection
      echo "Available releases:"
      ls -lt "${DEPLOY_PATH}/releases/" | head -10

      # PREVIOUS_RELEASE must be passed as variable in manual trigger
      TARGET="${DEPLOY_PATH}/releases/${ROLLBACK_TO}"
      test -d "${TARGET}" || { echo "ERROR: Release not found: ${TARGET}"; exit 1; }

      # Shared files remain unchanged — only the symlink switches
      ln -sfn "${TARGET}" "${DEPLOY_PATH}/current"

      cd "${DEPLOY_PATH}/current"
      bin/magento cache:flush
      echo "Rolled back to ${TARGET}"
      ENDSSH
  variables:
    ROLLBACK_TO: ""  # Set via GitLab manual job trigger

10. Zusammenfassung

Die korrekte Behandlung von env.php, config.php und Shared-Verzeichnissen im Release-Modell ist keine optionale Verbesserung, sondern die Grundlage für stabile Magento-Deployments. env.php gehört ausschließlich in den Shared-Bereich und darf weder im Repository noch im Build-Artefakt auftauchen. config.php sollte bevorzugt im Repository versioniert werden, damit der Modulstatus Teil des release-spezifischen Artefakts ist. Verzeichnisse wie pub/media/, var/log/ und var/session/ sind durch Symlinks mit dem Shared-Bereich zu verbinden, bevor der Symlink auf current umgestellt wird.

Der Deploy-Job in GitLab CI muss diese Schritte in der richtigen Reihenfolge ausführen und vor dem Umstellen des current-Symlinks prüfen, ob alle Shared-Dateien vorhanden sind. Ein Verify-Job nach dem Deployment stellt sicher, dass env.php korrekt eingebunden wurde und der Shop erreichbar ist. Rollbacks funktionieren im Symlink-Modell ohne Datenverlust, sofern die Datenbank-Kompatibilität berücksichtigt wird.

env.php und Shared Configs im Release-Modell — Das Wichtigste auf einen Blick

env.php

Liegt im Shared-Ordner, wird nie versioniert. Deploy-Job prüft Existenz vor dem Symlink-Wechsel. Generierung aus GitLab-Variablen ist die sauberste Lösung.

config.php

Bevorzugt im Repository versioniert – Modulstatus ist Teil des Releases. Post-Deploy app:config:import stellt Konsistenz sicher.

Shared-Struktur

pub/media, var/log, var/session als Symlinks auf Shared-Pfade. Einmalige Einrichtung, versioniertes Setup-Skript, reproduzierbar.

Rollback

Symlink auf alten Release umstellen, Cache flushen. Shared-Daten bleiben unberührt. Datenbank-Kompatibilität separat planen.

11. FAQ: env.php, config.php und Shared Configs im Release-Modell

1Darf env.php ins Git-Repository?
Nein. env.php enthält Secrets und ist umgebungsspezifisch. Sie gehört ausschließlich in das Shared-Verzeichnis auf dem Zielserver.
2Unterschied env.php vs. config.php?
env.php enthält Laufzeit-Secrets. config.php enthält Modulstatus und kann im Repository versioniert werden.
3Wie wird env.php sicher auf den Server gebracht?
Einmaliger manueller Upload beim Server-Setup oder Generierung aus GitLab Protected Masked Variables im Deploy-Skript.
4Was passiert bei Rollback mit Shared-Dateien?
Shared-Dateien bleiben unberührt. Nur der current-Symlink wechselt auf ein älteres Release-Verzeichnis. Kein Datenverlust.
5app/etc/ als Symlink auf Shared?
Nicht empfohlen. Sicherer ist ein fester Ordner im Release mit kopierter env.php aus dem Shared-Bereich.
6Wann wird config.php zum Problem?
Wenn sie im Shared-Ordner liegt und neue Module aktiviert werden ohne die Shared-Datei zu aktualisieren – inkonsistenter Modulstatus.
7Wie prüft der Verify-Job env.php?
php -r 'require "app/etc/env.php";' auf dem Server prüft Existenz und Syntax. bin/magento cache:status validiert die Datenbankverbindung.
8Wie viele alte Releases behalten?
Zwei bis drei für schnellen Rollback. Mehr als fünf bis sieben belegen unnötig Speicherplatz. Cleanup-Job automatisieren.
9pub/static/ in den Shared-Bereich?
Nein. pub/static/ wird je Release neu gebaut und ist Teil des Release-Artefakts, nicht des Shared-Bereichs.
10Risiko bei nicht-versionierter Shared-Struktur?
Beim Server-Neuaufbau weiß niemand, welche Verzeichnisse mit welchen Berechtigungen angelegt wurden. Setup-Skript muss versioniert sein.