CI/CD
.yml
GitLab · Magento · Hyvä · SCD · Tailwind CSS
Static Content Deployment für Magento
richtig in den Prozess einbauen

Static Content Deployment auf dem Produktionsserver auszuführen bedeutet: lange Wartungsfenster, serverabhängige Build-Ergebnisse und unkontrollierbare Laufzeiten. Der richtige Platz für SCD ist der CI-Build-Job – mit Hyvä Tailwind-Build, setup:di:compile und pub/static als kontrollierbares Artefakt, das auf jeden Server deployt werden kann.

12 Min. Lesezeit SCD · Hyvä Tailwind · pub/static · var/view_preprocessed GitLab CI/CD · Magento 2.4 · Zero Downtime

1. Was Static Content Deployment in Magento macht

Der Befehl bin/magento setup:static-content:deploy – kurz SCD – generiert alle statischen Dateien, die Magento für das Frontend benötigt: CSS, JavaScript, Fonts, Bilder, Less-kompilierte Stylesheets und Template-Dateien, die in pub/static abgelegt werden. Die Ausführung kann je nach Anzahl der Themes, Locales und Module mehrere Minuten dauern. Während des SCD-Laufs sind die statischen Dateien im Übergangszustand – teilweise die alten, teilweise die neuen Versionen – was zu fehlerhaften Browser-Darstellungen führen kann, wenn Benutzer die Seite genau in diesem Moment laden.

SCD ist keine optionale Operation. Jede Änderung an Frontend-Code, Theme-Dateien oder CSS erfordert einen erneuten SCD-Lauf. Wer SCD überspringt und hofft, dass Magento die Dateien on-demand generiert, wird feststellen, dass Magento im Production-Mode keine dynamische Kompilierung durchführt. Das ist ein häufiger Fehler beim ersten Zero-Downtime-Versuch: Der Deploy läuft durch, der Symlink wechselt, aber die Seite zeigt fehlendes CSS und defekte JS-Bundles, weil SCD nicht ausgeführt wurde.

Die entscheidende Frage ist nicht ob SCD ausgeführt werden muss, sondern wann und wo: im CI-Build-Job, der das Artefakt erzeugt, oder auf dem Server während des Deployments. Die Antwort hat weitreichende Konsequenzen für Deployment-Dauer, Reproduzierbarkeit und Wartungsfenster-Länge.

2. Warum SCD in den CI-Build gehört, nicht auf den Server

SCD auf dem Produktionsserver auszuführen hat drei fundamentale Nachteile. Erstens verlängert es das Wartungsfenster erheblich: Wenn SCD drei Minuten läuft und in dieser Zeit der Maintenance Mode aktiv ist oder die alten Static Files schon gelöscht wurden, sind drei Minuten Downtime. Zweitens ist das Ergebnis serverabhängig: Die PHP-Version, die installierten Extensions und die Less-Compiler-Version beeinflussen den SCD-Output. Auf einem anderen Server – zum Beispiel nach einer Migration – sieht der Output anders aus. Drittens kann SCD auf dem Server nicht einfach wiederholt werden, ohne ein weiteres Deployment durchzuführen.

Im CI-Build-Job dagegen läuft SCD in einer kontrollierten Docker-Umgebung mit definierten PHP- und Node-Versionen. Das Ergebnis ist reproduzierbar: Derselbe Commit erzeugt mit demselben Docker-Image dieselben statischen Dateien. Der Build-Job kann beliebig oft wiederholt werden, ohne den Server zu berühren. Und das pub/static-Verzeichnis ist Teil des Artefakts, das nach Staging und – per Promotion – nach Production übertragen wird. SCD läuft einmal, das Ergebnis wird mehrfach deployt.

3. Korrekte Reihenfolge: di:compile vor SCD

setup:di:compile muss vor setup:static-content:deploy laufen. Der DI-Compiler generiert den generated/-Ordner, der Interceptor-Klassen, Factory-Klassen und andere Code-Generierungsartefakte enthält. SCD verlässt sich auf diese generierten Klassen, um Template-Dateien korrekt zu resolven und Plugin-spezifische JavaScript-Konfigurationen zu ermitteln. Wenn SCD vor di:compile läuft, fehlen Teile der generierten Code-Basis, und das Static Content Deployment kann unvollständige oder fehlerhafte Dateien erzeugen.

Beim Hyvä Theme mit Tailwind CSS kommt eine dritte Abhängigkeit hinzu: Der Tailwind-Build muss laufen, bevor SCD ausgeführt wird, weil Hyvä die kompilierte CSS-Datei als reguläre Theme-Datei einbindet und SCD diese in pub/static verteilt. Die korrekte Reihenfolge im Build-Job lautet also: Composer Install, npm ci und Tailwind-Build, dann di:compile, dann SCD. Wer diese Reihenfolge nicht einhält, bekommt ein pub/static, das Verweise auf CSS enthält, die noch nicht kompiliert wurden – und damit einen defekten Build, der sich erst auf dem Server als Problem zeigt.

build:magento:
  stage: build
  image: php:8.4-cli
  variables:
    COMPOSER_CACHE_DIR: .cache/composer
    NPM_CONFIG_CACHE: .cache/npm
  cache:
    key: "$CI_COMMIT_REF_SLUG"
    paths:
      - .cache/composer
      - .cache/npm
  before_script:
    - apt-get update -qq && apt-get install -y -qq git unzip nodejs npm libzip-dev
    - docker-php-ext-install zip pdo_mysql bcmath intl
    - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
  script:
    # Step 1: PHP dependencies
    - composer install --no-dev --prefer-dist --no-interaction --quiet
    # Step 2: Frontend build — Tailwind must run before SCD
    - npm ci --prefix app/design/frontend/Mironsoft/default/web/tailwind
    - npm run build --prefix app/design/frontend/Mironsoft/default/web/tailwind
    # Step 3: DI compile — must run before SCD
    - php bin/magento setup:di:compile --quiet
    # Step 4: Static content — runs after DI and Tailwind are ready
    - rm -rf var/view_preprocessed pub/static/frontend
    - php bin/magento setup:static-content:deploy de_DE en_US \
        -t Mironsoft/default \
        --force \
        --jobs $(nproc) \
        --quiet
  artifacts:
    paths:
      - vendor/
      - generated/
      - pub/static/
      - app/etc/config.php
    exclude:
      - pub/static/**/*.map
    expire_in: 7 days
  only:
    - main
    - tags

4. Hyvä Theme und Tailwind CSS in GitLab CI bauen

Hyvä Themes setzen auf Tailwind CSS v4 mit einem CSS-First-Ansatz. Die tailwind.config.js und die Eingangs-CSS-Datei befinden sich unter app/design/frontend/Mironsoft/default/web/tailwind/. Der Build-Befehl – typischerweise npm run build – erzeugt die kompilierte CSS-Datei, die Magento dann in pub/static distribuiert. Im CI-Build-Job muss Node.js in der korrekten Version verfügbar sein – hier empfiehlt sich ein Docker-Image, das sowohl PHP 8.4 als auch Node 20+ enthält, oder ein Multi-Stage-Build-Ansatz mit separaten Images.

Ein wichtiger Punkt beim Hyvä-Build: Der Tailwind CSS Purge-Schritt analysiert alle PHP-Templates nach verwendeten CSS-Klassen. Wenn während des Purge-Laufs Templates fehlen – zum Beispiel weil nur ein Teil des Source-Codes im Build-Kontext vorhanden ist – werden CSS-Klassen aus dem finalen Bundle entfernt, die tatsächlich verwendet werden. Das führt zu fehlenden Styles auf der Seite, ohne dass ein Build-Fehler signalisiert wird. Im CI-Build-Job muss deshalb das vollständige Repository inklusive aller Templates im Workspace verfügbar sein, bevor der Tailwind-Build startet.

5. Was ins Artefakt gehört: pub/static und was nicht

Das Build-Artefakt für ein Magento-Deployment mit CI-SCD enthält: vendor/, generated/, pub/static/ und app/etc/config.php. Was explizit nicht ins Artefakt gehört: app/etc/env.php (enthält datenbankspezifische Credentials, kommt aus Shared-Verzeichnis), pub/media/ (user-generated content, im Shared-Verzeichnis), var/ (Logs, Sessions, Cache – serverabhängig), var/view_preprocessed/ (Zwischenergebnis von SCD, nicht für den Server gedacht).

Source Maps (*.map-Dateien) können aus dem Artefakt ausgeschlossen werden, wenn sie auf Production nicht gebraucht werden. Das reduziert die Artefaktgröße signifikant bei wenig Verlust. Für Debugging-Zwecke kann man Source Maps als separates Artefakt mit längerer Retention ablegen, das nur bei Bedarf heruntergeladen wird. Die app/etc/config.php enthält die Liste aktivierter Module und sollte Teil des Artefakts sein – sie ist umgebungsunabhängig und steuert, welche Module Magento beim Start berücksichtigt.

6. var/view_preprocessed: löschen oder nicht?

Das Verzeichnis var/view_preprocessed ist ein interner Cache-Ordner, den Magento während des SCD-Prozesses als Zwischenspeicher nutzt. Wenn man SCD im CI-Build-Job ausführt, sollte var/view_preprocessed auf dem Server nicht aus einem vorherigen Deployment übernommen werden. Der Inhalt ist spezifisch für den letzten lokalen SCD-Lauf und kann zu Inkonsistenzen führen, wenn er mit einem neuen pub/static-Stand gemischt wird.

Die empfohlene Strategie: Im Build-Job wird var/view_preprocessed vor dem SCD-Lauf gelöscht (rm -rf var/view_preprocessed), damit ein sauberer SCD-Lauf stattfindet. Das Verzeichnis kommt nicht ins Artefakt. Auf dem Zielserver liegt var/view_preprocessed im Shared-Verzeichnis, das zwischen Releases geteilt wird. Nach dem Release-Wechsel kann var/view_preprocessed auf dem Server entweder geleert werden oder so bleiben – da es nur ein Zwischencache ist und Magento es bei Bedarf neu befüllt, ist beides vertretbar. Für maximale Sauberkeit: nach dem Symlink-Wechsel leeren.

7. SCD auf Server vs. SCD im CI-Build im Vergleich

Die Entscheidung zwischen SCD auf dem Server und SCD im CI-Build ist eine der wichtigsten Architekturentscheidungen im Magento-Deployment-Prozess. Sie bestimmt die Downtime-Charakteristik des Deployments, die Reproduzierbarkeit und die Komplexität des Rollbacks.

Kriterium SCD auf dem Server SCD im CI-Build-Job Empfehlung
Wartungsfenster Länger (SCD-Laufzeit auf Server) Minimal (nur Symlink-Wechsel) CI-Build
Reproduzierbarkeit Serverabhängig Kontrollierte CI-Umgebung CI-Build
CPU-Last auf Production Hoch während Deploy Keine CI-Build
Rollback von Static SCD erneut ausführen nötig Altes Release-Verzeichnis hat altes pub/static CI-Build für Rollback besser
Artefaktgröße Klein (kein pub/static) Größer (pub/static enthalten) Tradeoff akzeptabel

Die größere Artefaktgröße bei CI-SCD ist der einzige reale Nachteil. Pub/static kann je nach Anzahl der Themes und Locales 50–300 MB groß werden. Das ist beherrschbar mit externem Artefakt-Storage und Source-Map-Ausschluss. Die Vorteile – minimales Wartungsfenster, reproduzierbarer Build, einfacher Rollback – überwiegen bei weitem.

8. Cache-Verhalten nach SCD-Deployment

Nach dem Symlink-Wechsel und dem Deployen eines neuen pub/static muss der Magento-Cache geleert werden. Der full_page-Cache (wenn Varnish oder Magento-internes FPC genutzt wird) enthält möglicherweise gecachte HTML-Seiten, die Referenzen auf Dateipfade des alten Static Contents enthalten. Wenn der neue Static Content unter neuen Hash-Pfaden liegt – Magento erzeugt versionsbasierte Pfade in pub/static/version[hash]/ – zeigen gecachte Seiten auf Pfade, die im neuen Release nicht mehr existieren.

Das cache:flush-Kommando nach dem Symlink-Wechsel ist deshalb nicht optional. Bei Varnish-Integration muss zusätzlich der Varnish-Cache geleert werden – entweder per varnishadm ban.url '.' oder über Magentos eingebaute Varnish-Invalidierung per PURGE-Requests. Wer einen CDN (Cloudflare, Fastly) vor Magento betreibt, muss nach dem Deployment ebenfalls die relevanten Pfade invalidieren, damit Nutzer nicht die gecachten statischen Dateien der alten Version erhalten.

9. Static Content beim Rollback

Einer der stärksten Argumente für CI-SCD ist das Rollback-Verhalten. Wenn jedes Release-Verzeichnis sein eigenes pub/static enthält – als Teil des Artefakts – ist ein Rollback auf ein beliebiges vorheriges Release trivial: Der Symlink zeigt auf das alte Release-Verzeichnis, und das alte pub/static ist automatisch aktiv. Kein erneuter SCD-Lauf, kein Wartungsfenster für die Rollback-Operation.

Wenn SCD hingegen auf dem Server läuft und pub/static ein Shared-Verzeichnis ist (das zwischen Releases geteilt wird), überschreibt jeder neue Deploy den Inhalt. Ein Rollback bedeutet dann, SCD erneut für das alte Release auszuführen – was Minuten dauert und während dieser Zeit inkonistente Static Files erzeugt. Mit CI-SCD und per-Release-pub/static ist Rollback eine Millisekunden-Operation: der ln -sfn-Befehl. Der Cache-Flush danach dauert Sekunden. Das ist die Rollback-Qualität, die Zero Downtime ermöglicht.

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:
    - |
      ssh "$DEPLOY_USER@$DEPLOY_HOST" bash -s <<'REMOTE'
      set -euo pipefail

      readonly RELEASE_ID="$(date +%Y%m%d-%H%M%S)"
      readonly RELEASE_PATH="$DEPLOY_PATH/releases/$RELEASE_ID"
      readonly SHARED="$DEPLOY_PATH/shared"

      # Create new release directory and unpack artifact
      mkdir -p "$RELEASE_PATH"
      cd "$RELEASE_PATH"
      tar -xzf "/tmp/$ARTIFACT_NAME" 2>/dev/null

      # Link shared directories — env.php, media, logs, sessions
      ln -sfn "$SHARED/app/etc/env.php" app/etc/env.php
      rm -rf pub/media var/log var/session
      ln -sfn "$SHARED/pub/media" pub/media
      ln -sfn "$SHARED/var/log" var/log
      ln -sfn "$SHARED/var/session" var/session

      # Run schema upgrades if needed
      bin/magento setup:upgrade --keep-generated --no-interaction

      # Switch symlink atomically — pub/static already in release dir from CI build
      ln -sfn "$RELEASE_PATH" "$DEPLOY_PATH/current"

      # Flush cache — full_page cache must be cleared after static content switch
      bin/magento cache:flush
      echo "[OK] Release $RELEASE_ID deployed"

      # Cleanup old releases — keep last 5
      ls -1dt "$DEPLOY_PATH/releases"/*/ | tail -n +6 | xargs rm -rf
      REMOTE
  environment:
    name: production
  only:
    - tags
  when: manual

10. Zusammenfassung

Static Content Deployment in Magento gehört in den CI-Build-Job, nicht auf den Produktionsserver. Die Begründung ist technisch und operativ: Die Build-Umgebung ist kontrollierbar und reproduzierbar, die Deployment-Laufzeit auf dem Server wird minimal, und der Rollback ist eine Millisekunden-Operation, weil jedes Release sein eigenes pub/static mitbringt. Die korrekte Reihenfolge – Composer Install, Tailwind-Build, di:compile, SCD – ist nicht verhandelbar, weil jeder Schritt von den Ergebnissen des vorherigen abhängt.

Hyvä Themes mit Tailwind CSS verstärken dieses Argument: Der Tailwind-Build ist ein Node.js-Prozess, der auf einem PHP-Server nichts zu suchen hat. Er gehört in die CI-Pipeline, wo Node in der richtigen Version vorhanden ist und das Ergebnis als stabile Datei Teil des Artefakts wird. Wer diesen Prozess einmal sauber aufgebaut hat, deployt schneller, sicherer und mit einem klaren Rollback-Pfad – unabhängig davon, ob das Deployment auf Staging oder auf Production stattfindet.

Static Content Deployment in GitLab CI — Das Wichtigste auf einen Blick

Reihenfolge

Composer Install, Tailwind-Build, di:compile, dann SCD. Jeder Schritt abhängig vom vorherigen – Reihenfolge nicht änderbar.

Artefakt-Inhalt

vendor/, generated/, pub/static/, app/etc/config.php. Keine env.php, keine pub/media, kein var/view_preprocessed.

Hyvä + Tailwind

npm ci + npm run build vor di:compile und SCD. Node.js im Build-Docker-Image – nicht auf dem Server. Vollständige Templates für korrekten Purge.

Rollback-Vorteil

Per-Release pub/static ermöglicht Rollback per Symlink-Wechsel ohne SCD-Wiederholung. Cache flush danach – fertig.

11. FAQ: Static Content Deployment für Magento in GitLab CI

1Muss SCD immer ausgeführt werden?
Ja, bei jeder Änderung an Frontend-Code, Templates oder CSS. Im Production-Mode generiert Magento keine Static Files on-demand. Wer SCD überspringt, deployt mit defektem Frontend.
2Warum muss di:compile vor SCD laufen?
SCD verlässt sich auf generierten Code in generated/. Fehlt dieser, kann SCD Template-Abhängigkeiten nicht resolven und erzeugt unvollständige Static Files.
3Wie lange dauert SCD typischerweise?
1 bis 10 Minuten je nach Modulanzahl, Themes und Locales. Mit --jobs $(nproc) wird SCD parallelisiert. Im CI-Build-Job unkritisch für die Deployment-Dauer auf dem Server.
4Kann pub/static ein Shared-Verzeichnis sein?
Technisch möglich, aber nicht empfohlen bei CI-SCD. Als Shared-Verzeichnis teilen alle Releases denselben Static Content – Rollback wird kompliziert. Per-Release ist besser.
5Tailwind-Purge entfernt verwendete CSS-Klassen?
Ja, wenn Templates beim Purge-Lauf fehlen. Alle PHP-Templates müssen im CI-Workspace vorhanden sein, wenn der Tailwind-Build läuft, damit Purge korrekt arbeitet.
6var/view_preprocessed ins Artefakt?
Nein. Interner SCD-Zwischencache, serverabhängig und groß. Vor SCD-Lauf im CI löschen, nicht ins Artefakt aufnehmen.
7Welche Locales beim SCD deployen?
Nur die tatsächlich verwendeten – z. B. de_DE en_US. Jede Locale erhöht SCD-Laufzeit und Artefaktgröße. In Magento unter Stores > Configuration > Locale Options nachsehen.
8Muss cache:flush nach jedem Deployment laufen?
Ja. Der full_page-Cache enthält möglicherweise Referenzen auf Static-File-Pfade des alten Releases. Nach Symlink-Wechsel zwingend flushen.
9Wie beeinflusst ein CDN das SCD?
Magento erzeugt versionierte Pfade (pub/static/version[hash]/). Neue Static Files haben neue URLs – CDN zieht sie automatisch frisch. Alte Pfade werden nicht mehr referenziert.
10--force bei SCD im CI-Build?
Ja, immer --force im CI-Build-Job verwenden, weil der Workspace möglicherweise Reste eines vorherigen Builds enthält. Stellt sicher, dass alle Dateien neu generiert werden.