Timing, Caches und Generated Code
setup:di:compile ist einer der zeitintensivsten Schritte im Magento-Build-Prozess — und einer der am häufigsten falsch platzierten. Zu früh ausgeführt fehlt die vollständige Composer-Basis; zu spät ausgeführt verzögert es den Deploy. Dieser Artikel zeigt, wann und wie di:compile in einer GitLab-Pipeline am besten läuft.
Inhaltsverzeichnis
- 1. Was setup:di:compile tatsächlich tut
- 2. Timing: wann in der Pipeline di:compile laufen muss
- 3. generated/ als Build-Artefakt behandeln
- 4. Caching-Strategien für di:compile in GitLab
- 5. Vollständiger Build-Job mit di:compile
- 6. Häufige Compile-Fehler und ihre Ursachen
- 7. generated/ und Zero-Downtime-Deployment
- 8. Vergleich: di:compile im Build vs. auf dem Server
- 9. Typische Fehler im Build-Prozess
- 10. Zusammenfassung
- 11. FAQ
1. Was setup:di:compile tatsächlich tut
setup:di:compile ist der Magento-Befehl, der den Dependency-Injection-Framework kompiliert. Er analysiert alle Magento-Module, liest ihre di.xml-Konfigurationen, löst Abhängigkeiten auf und generiert PHP-Code, der zur Laufzeit der Anwendung verwendet wird. Das Ergebnis ist das generated/-Verzeichnis mit Proxy-Klassen, Factories, Interceptors und Preference-Mappings. Ohne diesen generierten Code würde Magento entweder deutlich langsamer laufen (weil Klassen dynamisch generiert werden) oder bei bestimmten DI-Konfigurationen gar nicht starten.
Der Prozess ist zeitintensiv: Auf einem typischen Magento-Projekt mit rund 150 aktiven Modulen dauert setup:di:compile zwischen zwei und fünf Minuten, abhängig von der Server-Leistung und der PHP-Version. In PHP 8.4 mit JIT ist er etwas schneller als in PHP 7.4, aber er ist und bleibt ein erheblicher Anteil der Gesamtbuild-Zeit. Das ist der erste Grund, warum er im CI/CD-Build-Prozess sorgfältig positioniert werden muss: einmal falsch platziert, verlängert er die Pipeline unnötig oder muss im schlimmsten Fall mehrfach ausgeführt werden.
Ein häufiges Missverständnis: setup:di:compile benötigt keine laufende Datenbank. Es liest nur PHP-Code und XML-Konfigurationen. Das macht es ideal für den Build-Job in der CI-Pipeline, der in einem isolierten Docker-Container ohne Datenbankverbindung läuft. Was es jedoch benötigt: eine vollständige vendor/-Installation. Es muss also nach composer install, aber vor dem Paketieren der Artefakte laufen.
2. Timing: wann in der Pipeline di:compile laufen muss
Die korrekte Reihenfolge in der Magento-Build-Pipeline ist eindeutig: zuerst composer install, dann setup:di:compile, dann der Frontend-Build (Tailwind CSS, NPM), dann setup:static-content:deploy (wenn im Build-Job), dann das Paketieren der Artefakte. di:compile muss nach composer install laufen, weil es Zugriff auf alle Klassen im vendor/-Verzeichnis benötigt. Fehlen Klassen, scheitert der Compile-Prozess mit einem ClassNotFoundException oder einem fehlerhaften DI-Graph.
setup:static-content:deploy kann — muss aber nicht — nach di:compile im Build-Job laufen. Wenn Static Content Deploy im Build-Job läuft und das pub/static/-Verzeichnis als Artefakt verpackt wird, entfällt dieser Schritt im Deploy-Job. Das spart Zeit auf dem Produktionsserver, erhöht aber die Artefakt-Größe. Viele Teams delegieren setup:static-content:deploy an den Deploy-Job, weil der Befehl Store-View-spezifische Daten benötigt, die vom Server abhängen. Für reine Magento-Setups ohne Store-View-spezifische Abhängigkeiten im Build-Kontext kann er aber zuverlässig im Build-Job laufen.
3. generated/ als Build-Artefakt behandeln
Das generated/-Verzeichnis ist ein Artefakt des Build-Prozesses: Es wird aus Quellcode generiert und muss nicht in Git versioniert werden. In einer Symlink-Release-Struktur kommt das generated/-Verzeichnis aus dem Build-Artefakt und wird ins Release-Verzeichnis platziert. Es darf nicht im Shared-Verzeichnis liegen, weil verschiedene Releases möglicherweise unterschiedliche generierte Klassen haben. Jeder Release trägt sein eigenes generated/-Verzeichnis mit sich.
In der GitLab-Pipeline wird das generated/-Verzeichnis im artifacts-Block des Build-Jobs deklariert. GitLab speichert es nach dem Build-Job und stellt es im Deploy-Job zur Verfügung. Die Größe des Artefakts ist ein kritischer Faktor: Ein typisches generated/-Verzeichnis umfasst 50–150 MB an PHP-Dateien. Zusammen mit vendor/ (200–400 MB) und pub/static/ (optional, 100–500 MB) können Artefakte schnell 500 MB bis 1 GB groß werden. Große Artefakte verlangsamen den Upload nach dem Build-Job und den Download vor dem Deploy-Job erheblich.
4. Caching-Strategien für di:compile in GitLab
GitLab-Caches unterscheiden sich von Artefakten: Artefakte werden zwischen Jobs in derselben Pipeline weitergegeben. Caches werden zwischen verschiedenen Pipeline-Läufen wiederverwendet. Für di:compile ist ein direktes Caching des generated/-Verzeichnisses nicht empfehlenswert, weil die generierten Klassen exakt zum aktuellen Stand des vendor/- und App-Code passen müssen. Ein alter Cache aus einem anderen Commit kann zu subtilen DI-Fehlern führen, die schwer zu debuggen sind.
Was sich dagegen gut cachen lässt: Das vendor/-Verzeichnis aus composer install (Cache-Key: Hash der composer.lock). Wenn composer.lock sich nicht geändert hat, kann der Build-Job den Composer-Cache nutzen und composer install deutlich beschleunigen. Damit verkürzt sich die Gesamt-Build-Zeit für Commits ohne Dependency-Änderungen erheblich, weil der zeitintensive Download-Schritt entfällt. di:compile selbst läuft dann auf dem bereits vorhandenen vendor/-Verzeichnis, was die Gesamtzeit von typischerweise 8–12 Minuten auf 3–5 Minuten reduzieren kann.
5. Vollständiger Build-Job mit di:compile
Die folgende Konfiguration zeigt einen vollständigen Build-Job für Magento 2.4 mit PHP 8.4, der setup:di:compile korrekt positioniert, Caching für Composer nutzt und das generated/-Verzeichnis als Artefakt bereitstellt. Die Kommentare erläutern die Entscheidungen hinter der Reihenfolge.
# .gitlab-ci.yml — Magento build stage with setup:di:compile
stages:
- build
- test
- package
- deploy
- verify
variables:
GIT_STRATEGY: fetch
COMPOSER_HOME: "/tmp/composer"
COMPOSER_CACHE_DIR: ".cache/composer"
PHP_MEMORY_LIMIT: "2G"
build:magento:
stage: build
image: php:8.4-cli
before_script:
# Install system dependencies for Magento build
- apt-get update -qq && apt-get install -y -qq git unzip libzip-dev libicu-dev
- docker-php-ext-install zip intl bcmath pdo_mysql
# Inject Composer auth credentials — never hardcode in .gitlab-ci.yml
- mkdir -p "$COMPOSER_HOME"
- echo "$COMPOSER_AUTH" > "$COMPOSER_HOME/auth.json"
script:
# Step 1: Install PHP dependencies — must run before di:compile
- composer install --no-dev --prefer-dist --no-interaction --no-progress \
--optimize-autoloader
# Step 2: DI compilation — runs after full vendor/ is available
- php -d memory_limit="${PHP_MEMORY_LIMIT}" bin/magento setup:di:compile
# Step 3: Static content deploy — runs after di:compile (uses generated classes)
- php bin/magento setup:static-content:deploy de_DE en_US \
-t Mironsoft/default --jobs=4 -f
# Step 4: Frontend build (Tailwind CSS)
- npm ci --prefix app/design/frontend/Mironsoft/default/web/tailwind
- npm run build --prefix app/design/frontend/Mironsoft/default/web/tailwind
# Remove auth credentials before artifact packaging
- rm -f "$COMPOSER_HOME/auth.json"
after_script:
# Cleanup sensitive data even on failure
- rm -f "/tmp/composer/auth.json"
cache:
# Cache vendor/ keyed by composer.lock hash — reuse on unchanged dependencies
key:
files:
- composer.lock
paths:
- .cache/composer/
policy: pull-push
artifacts:
paths:
- vendor/
- generated/
- pub/static/
expire_in: 4 hours
# Exclude test files from artifact to reduce size
exclude:
- vendor/**/Test/**
- vendor/**/Tests/**
- vendor/**/*.md
tags:
- build
- php84
- docker
Zwei Details in dieser Konfiguration sind besonders wichtig: Erstens läuft setup:static-content:deploy nach setup:di:compile, nicht davor. Static Content Deploy erzeugt in manchen Magento-Versionen Zugriffe auf generierte Klassen (z.B. Übersetzungs-Klassen aus generierten Code-Strukturen). Zweitens ist --optimize-autoloader in composer install aktiviert: Das verbessert die Autoload-Performance in Production erheblich und sollte in Build-Jobs immer gesetzt sein. Der Parameter --jobs=4 bei static-content:deploy parallelisiert die Asset-Generierung und nutzt die verfügbaren CPU-Kerne des Build-Containers.
6. Häufige Compile-Fehler und ihre Ursachen
Der häufigste Fehler bei setup:di:compile in CI-Umgebungen ist ein Memory-Limit-Problem. Im Standard läuft php mit dem PHP-ini-Standardwert, der oft bei 128 MB oder 256 MB liegt. di:compile benötigt für große Magento-Installationen mit vielen Drittmodulen zwischen 512 MB und 2 GB RAM. Ohne explizites Memory-Limit schlägt der Prozess mit einem Allowed memory size exhausted-Fehler fehl. Die Lösung: php -d memory_limit=2G vor dem Befehl setzen oder in der php.ini des Docker-Images konfigurieren.
Der zweite häufige Fehler ist eine fehlende PHP-Extension im Build-Container. di:compile analysiert PHP-Klassen aus allen Modulen, darunter häufig solche, die Extensions wie intl, bcmath oder soap voraussetzen. Fehlt die Extension im Container, schlägt der Compile-Prozess mit einem Fatal error: Uncaught Error: Call to undefined function fehl. Die Lösung: im Build-Job-Image alle PHP-Extensions installieren, die Magento im Betrieb benötigt — nicht nur die, die Composer für die Installation braucht.
7. generated/ und Zero-Downtime-Deployment
In einer Symlink-Release-Struktur ist das generated/-Verzeichnis Teil des Release-Verzeichnisses. Beim atomaren Symlink-Wechsel wechselt der current-Symlink von einem Release-Verzeichnis zum nächsten. Das neue Release-Verzeichnis enthält das vollständige generated/-Verzeichnis aus dem Build-Artefakt. Damit ist sichergestellt, dass das neu aktive Release immer das zur Codeversion passende generierte Code-Set hat — kein manuelles Löschen und Neuerstellen von generated/ auf dem Server nötig.
Ein kritischer Punkt: Auf dem Produktionsserver darf das generated/-Verzeichnis nicht im Shared-Verzeichnis liegen. Wenn zwei Releases gleichzeitig aktiv sein könnten (was im atomaren Symlink-Switch nicht der Fall ist, aber in anderen Deployment-Modellen vorkommt), würden sich verschiedene Versionen des generierten Codes gegenseitig überschreiben. Im Symlink-Modell ist dieses Problem strukturell ausgeschlossen: Jedes Release-Verzeichnis trägt sein eigenes generated/-Verzeichnis, und der aktive Symlink zeigt immer auf genau eines davon.
8. Vergleich: di:compile im Build vs. auf dem Server
Es gibt zwei Ansätze, wo setup:di:compile im Deployment-Prozess ausgeführt wird. Der Build-in-CI-Ansatz führt es einmalig im Build-Job aus und verteilt das Ergebnis über das Artefakt. Der On-Server-Ansatz führt es auf jedem Zielserver direkt vor dem Symlink-Wechsel aus. Beide haben Vor- und Nachteile.
| Aspekt | Build in CI (empfohlen) | Auf dem Server |
|---|---|---|
| Reproduzierbarkeit | Einmalig, konsistent für alle Server | Abhängig von Server-PHP-Version und -Extensions |
| Deploy-Dauer | Kürzer (kein Compile auf Server) | Länger (2–5 Min. Compile pro Server) |
| Fehler-Erkennung | Im Build-Job — vor dem Deploy | Erst auf dem Server — zu spät |
| Artefakt-Größe | Größer (generated/ enthalten) | Kleiner (kein generated/ im Artefakt) |
| Multi-Server-Deploy | Identisches generated/ auf allen Servern | Compile läuft auf jedem Server separat |
Für nahezu alle produktiven Magento-Setups ist der Build-in-CI-Ansatz vorzuziehen: Fehler in der DI-Konfiguration werden bereits im Build-Job entdeckt, bevor ein Deploy überhaupt startet. Bei Multi-Server-Setups (Web-Cluster) läuft der Compile-Prozess genau einmal, statt auf jedem Server einzeln. Das verkürzt das Deployment-Fenster erheblich. Der einzige stichhaltige Grund für On-Server-Compilation: wenn der Build-Container eine wesentlich andere PHP-Umgebung hat als der Produktionsserver — was ein separates Problem ist und durch eine konsistente PHP-Umgebung in CI und Production behoben werden sollte.
9. Typische Fehler im Build-Prozess
Ein verbreiteter Fehler ist das Ausführen von setup:di:compile ohne vorheriges composer install oder mit einer unvollständigen Vendor-Installation. Wenn composer install mit --no-dev ausgeführt wird, fehlen Development-only-Abhängigkeiten — das ist gewollt. Wenn es aber ohne --no-plugins ausgeführt wird und ein Plugin scheitert, kann die Vendor-Installation unvollständig sein, was di:compile mit kryptischen Fehler beendet. Lösung: immer erst prüfen, ob composer install sauber durchgelaufen ist, bevor di:compile startet. Der Exit-Code von composer install signalisiert das zuverlässig.
Ein zweiter Fehler ist das Cachen des generated/-Verzeichnisses über Pipeline-Runs hinweg mit einem zu generischen Cache-Key. Wenn der Cache-Key nicht den aktuellen Commit-Hash oder den Hash aller Module enthält, kann ein veraltetes generated/-Verzeichnis aus dem Cache verwendet werden, das nicht zum aktuellen Code passt. Das kann dazu führen, dass Magento auf dem Server mit einem Interceptor-Set startet, das zu einer veralteten Klassen-Hierarchie gehört — ein subtiler Fehler, der oft erst bei bestimmten Benutzeraktionen sichtbar wird.
10. Zusammenfassung
setup:di:compile gehört in den Build-Job der GitLab-Pipeline, nach composer install und vor der Artefakt-Paketierung. Das generierten Code-Verzeichnis wird als Build-Artefakt behandelt und mit dem Release-Verzeichnis auf den Server gebracht — nicht manuell auf dem Server regeneriert. Compile-Fehler werden so im Build-Job entdeckt, bevor ein Deploy überhaupt startet. Memory-Limit explizit setzen (php -d memory_limit=2G), alle benötigten PHP-Extensions im Build-Container installieren und --optimize-autoloader für die Composer-Installation aktivieren.
Caching des generated/-Verzeichnisses zwischen Pipeline-Runs ist nicht sinnvoll, weil das Verzeichnis exakt zum aktuellen Commit-Stand passen muss. Composer-Cache hingegen ist sinnvoll und verkürzt Build-Zeiten für Commits ohne Dependency-Änderungen erheblich. Mit dieser Konfiguration ist setup:di:compile ein zuverlässiger, reproduzierbarer Bestandteil des Build-Prozesses — und nicht eine Fehlerquelle auf dem Produktionsserver.
setup:di:compile im Build-Prozess — Das Wichtigste auf einen Blick
Reihenfolge
composer install → di:compile → static-content:deploy → Frontend-Build. Nie di:compile vor composer install ausführen.
Memory-Limit
php -d memory_limit=2G bin/magento setup:di:compile — ohne explizites Limit schlägt di:compile bei großen Setups fehl.
generated/ als Artefakt
Im Artefakt-Block deklarieren und mit dem Release-Verzeichnis deployen. Nie im Shared-Verzeichnis. Nie zwischen Pipeline-Runs cachen.
Fehler-Erkennung
DI-Kompilierungsfehler im Build-Job — vor dem Deploy. Nie erst auf dem Server entdecken. Build-Job schlägt fehl, kein Deploy startet.