Docker · Magento 2 · Build-Container · DevOps
Magento Static Content & DI Compile
sauber im Docker Build-Container

Wer DI Compile und Static Content Deploy zur Laufzeit ausführt, verlängert den Deployment-Prozess und riskiert inkonsistente Images. Der Build-Container-Ansatz verschiebt beide Schritte in die Image-Erstellung – mit reproduzierbaren Ergebnissen und deutlich schnelleren Release-Zyklen.

12 Min. Lesezeit setup:di:compile · setup:static-content:deploy · Multi-Stage Build Magento 2.4 · PHP 8.4 · Docker

1. Das Laufzeit-Problem bei Magento-Deployments

In vielen Magento-Projekten läuft setup:di:compile als Teil des Deployment-Skripts direkt auf dem Produktionsserver oder im laufenden Container. Das ist praktisch, wenn man schnell deployen möchte, hat aber fundamentale Nachteile: Der Prozess dauert mehrere Minuten, blockiert oder verlangsamt den laufenden Shop, und das Ergebnis hängt von der exakten PHP-Version und den installierten Extensions auf dem Zielserver ab. Ein Container, der morgen einen anderen PHP-Basis-Image zieht, kann ein anderes Compile-Ergebnis erzeugen – auch wenn der Code unverändert ist.

Das eigentliche Problem liegt in der Philosophie: Im Docker-Umfeld sollte ein Image vollständig und ausführbereit sein, wenn es aus einer Registry gepullt wird. Deployment bedeutet dann nur noch: alten Container stoppen, neuen Container starten. Jeder Build-Schritt, der noch zur Laufzeit stattfindet, widerspricht diesem Prinzip. Der Build-Container-Ansatz für Magento ist die direkte Antwort auf dieses Problem – alle rechenintensiven Kompilierungsschritte finden beim Image-Bau statt, nicht beim Start.

2. Was setup:di:compile wirklich macht

Der Befehl bin/magento setup:di:compile generiert automatisch den kompletten Dependency-Injection-Code für alle Magento-Module. Er analysiert XML-Konfigurationen, Interfaces, Klassen und Plugin-Definitionen, erstellt daraus PHP-Klassen für Proxy-Objekte, Interceptors und Factories – und schreibt das Ergebnis in das Verzeichnis generated/. Ohne diesen Schritt generiert Magento diese Klassen zur Laufzeit einzeln, was jeden ersten Request spürbar verlangsamt. Der Build-Container ermöglicht es, diesen aufwändigen Prozess einmalig beim Bau des Images durchzuführen, statt bei jedem Deployment oder schlimmer: beim ersten Request im Produktionssystem.

Ein kritischer Aspekt: setup:di:compile ist nur dann reproducible, wenn der PHP-Code vollständig ist. Das bedeutet, Composer-Dependencies müssen zuvor mit composer install --no-dev --optimize-autoloader installiert sein. Fehlt ein Modul oder ist der Autoloader nicht optimiert, erzeugt der Compiler entweder fehlerhafte Klassen oder bricht mit einem Fehler ab. Im Build-Container erzwingt man durch die Reihenfolge der Dockerfile-Schichten, dass Composer immer vor DI Compile läuft – und das prüfbar und nachvollziehbar.

3. Static Content Deploy: Umfang und Abhängigkeiten

Der Befehl setup:static-content:deploy ist in Magento-Projekten mit mehreren Themes und Locales einer der zeitintensivsten Build-Schritte überhaupt. Er kopiert, minifiziert und verknüpft alle JavaScript-, CSS- und Bild-Dateien aus den Themes und Modulen in das Verzeichnis pub/static/. Für ein Projekt mit zwei Themes und drei Locales können das leicht 30.000 bis 80.000 Dateien sein, deren Erzeugung mehrere Minuten dauert. Wird dieser Schritt im laufenden Container ausgeführt, ist der Shop für diese Zeit entweder unter erhöhter Last oder – schlimmer – liefert teilweise generierte Dateien aus.

Im Build-Container läuft der Static Content Deploy in einer isolierten Umgebung ohne Live-Traffic. Das Ergebnis wird direkt in das Image gebaut, sodass der neue Container sofort mit allen Static Files starten kann. Ein weiterer Vorteil: Der Deploy-Schritt kann auf mehrere Locales parallelisiert werden. Mit der Option --jobs 4 startet Magento vier parallele Prozesse, was auf Build-Servern mit vielen CPU-Kernen die Laufzeit erheblich reduziert. Der Build-Container nutzt die verfügbare Rechenkapazität des CI-Servers statt die des Produktionssystems.


# Build-stage: Magento DI Compile and Static Content Deploy
# This runs inside the Docker build context, not on the production server

FROM php:8.4-cli AS builder

# Install required PHP extensions for Magento build steps
RUN docker-php-ext-install pdo pdo_mysql bcmath intl soap zip opcache

# Copy application code and run Composer
WORKDIR /var/www/html
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-interaction

COPY . .

# Copy env.php stub — values will be overridden at runtime via secrets
COPY docker/env.php.build app/etc/env.php

# Step 1: Compile DI — generates all interceptors, proxies and factories
RUN php bin/magento setup:di:compile

# Step 2: Deploy static content for all required locales in parallel
RUN php bin/magento setup:static-content:deploy \
    de_DE en_US \
    -t Mironsoft/default \
    --jobs 4 \
    -f

# Remove development artifacts not needed at runtime
RUN rm -rf dev/ app/etc/env.php

4. Der Build-Container-Ansatz im Überblick

Das Konzept des Build-Containers für Magento besteht aus zwei klar getrennten Phasen: einer Build-Stage, die alle rechenintensiven Schritte ausführt, und einer Runtime-Stage, die nur das notwendige Ergebnis enthält. Docker Multi-Stage Builds erlauben es, diese Phasen in einem einzigen Dockerfile zu definieren. Die Build-Stage enthält alle Build-Tools (Composer, Node, NPM), führt DI Compile und Static Content Deploy aus, und gibt nur die Ergebnisverzeichnisse weiter. Die Runtime-Stage enthält nur PHP-FPM mit den nötigen Extensions und die fertigen Anwendungsdateien.

Der entscheidende Vorteil: Das finale Runtime-Image enthält keine Build-Tools, keine Composer-Abhängigkeiten für den Build-Prozess, und keine temporären Dateien. Es ist kleiner, sicherer und schneller zu pullen. Jeder Entwickler und jede CI-Instanz, die dasselbe Dockerfile verwendet, erzeugt identische Images – denn der Build-Container legt die exakte PHP-Version, die Extensions und die Compose-Konfiguration fest, unter der kompiliert wird. Umgebungsunterschiede zwischen Entwicklungs- und Produktionssystem sind damit ausgeschlossen.

5. Das Dockerfile für den Magento Build-Container

Ein vollständiges Dockerfile für den Magento Build-Container folgt einem festen Muster: Composer-Install als erste Schicht, danach Code-Copy, dann DI Compile, dann Static Content Deploy, und schließlich der Wechsel in die Runtime-Stage. Die Reihenfolge ist nicht willkürlich – sie optimiert den Docker Layer-Cache. Ändert sich nur der Anwendungscode, nicht aber die Composer-Abhängigkeiten, verwendet Docker die gecachte Composer-Schicht. Das verkürzt Build-Zeiten in CI-Pipelines erheblich, weil Composer nicht bei jedem Commit vollständig neu installiert wird.

Kritisch ist die Behandlung der env.php: Diese Datei enthält Datenbankpasswörter, Cache-Backend-Konfiguration und andere Laufzeitgeheimnisse. Im Build-Container wird eine Stub-Version verwendet, die nur die Werte enthält, die für DI Compile und Static Content Deploy notwendig sind – in der Regel keine Datenbankverbindung, aber korrekte Module-Konfiguration. Zur Laufzeit wird die echte env.php via Secret-Mount oder Volume eingebunden, ohne je Teil des Images geworden zu sein.


# Full multi-stage Dockerfile for Magento 2 with Build-Container pattern

# ── Stage 1: builder ─────────────────────────────────────────────────────
FROM php:8.4-cli AS builder

ARG MAGENTO_VERSION=2.4.8
ARG DEPLOY_LOCALES="de_DE en_US"
ARG DEPLOY_THEMES="Mironsoft/default"

# Install build-time PHP extensions
RUN apt-get update && apt-get install -y libicu-dev libzip-dev libxml2-dev \
    && docker-php-ext-install intl zip soap bcmath pdo_mysql opcache

# Install Composer 2
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer

WORKDIR /var/www/html

# Layer 1: Composer dependencies — cached unless composer.lock changes
COPY composer.json composer.lock auth.json* ./
RUN composer install --no-dev --optimize-autoloader --no-scripts --no-interaction

# Layer 2: Application code
COPY . .

# Stub env.php — only module list and crypt key, no DB credentials
COPY docker/env.php.stub app/etc/env.php

# Layer 3: DI compile — generates interceptors, factories, proxies
RUN php -d memory_limit=2G bin/magento setup:di:compile

# Layer 4: Static content deploy — parallelized per locale
RUN php -d memory_limit=2G bin/magento setup:static-content:deploy \
    ${DEPLOY_LOCALES} -t ${DEPLOY_THEMES} --jobs 4 -f

# ── Stage 2: runtime ──────────────────────────────────────────────────────
FROM php:8.4-fpm AS runtime

RUN docker-php-ext-install pdo_mysql intl opcache zip bcmath

# Copy only the built application — no composer, no build tools
COPY --from=builder /var/www/html /var/www/html

# Runtime env.php will be mounted as Docker secret at /run/secrets/env_php
RUN rm -f /var/www/html/app/etc/env.php

EXPOSE 9000
CMD ["php-fpm"]

6. Umgebungsvariablen und Konfigurationsstrategie

Die größte Herausforderung beim Build-Container-Ansatz für Magento ist das Handling der Konfiguration. Magento benötigt eine env.php um zu starten, aber diese Datei enthält Geheimnisse, die niemals in ein Image eingebrannt werden dürfen. Die Lösung besteht aus zwei Teilen: einer Stub-env.php für den Build-Prozess und einem Secret-Mounting-Mechanismus für die Laufzeit. Die Stub-Datei enthält nur den Crypt-Key, die Modul-Liste und ggf. Basis-Einstellungen – alles, was DI Compile und Static Content Deploy benötigen, aber keine Datenbankverbindung.

Zur Laufzeit injiziert man die vollständige env.php entweder über Docker Secrets (docker secret create), über ein bind-gemountetes Volume, oder – in Kubernetes-Umgebungen – über ein Secret-Volume. Ein Init-Container oder Entrypoint-Skript kopiert die Datei an die richtige Stelle, bevor PHP-FPM startet. Damit bleibt das Image vollständig portabel und geheim-frei. Der Build-Container muss niemals Zugriff auf Produktionsdatenbanken haben – er kompiliert nur Code, keine Laufzeitdaten.

7. Docker Layer-Cache gezielt nutzen

Der Layer-Cache ist der größte Performance-Hebel beim Build-Container-Ansatz. Docker invalidiert alle nachfolgenden Layer, sobald sich ein Layer ändert. Für Magento bedeutet das: composer.json und composer.lock müssen in einem separaten Layer vor dem restlichen Code-Copy stehen. Ändert sich nur PHP-Code, nicht aber die Dependencies, ist die Composer-Schicht gecacht und der Build beginnt direkt mit dem Code-Copy. Das spart in typischen CI-Pipelines drei bis zehn Minuten pro Build-Run.

Für Static Content Deploy gilt eine ähnliche Logik: Theme-Dateien (PHTML, Tailwind-CSS, JS) ändern sich häufiger als Magento-Kernmodule. Ein zweistufiger Ansatz trennt die Erstellung von Vendor-Assets und Theme-Assets in separate Schichten. Zusätzlich kann man mit BuildKit Remote Cache auf einen Registry-Cache verweisen, sodass auch Cold-Start-Builds auf frischen CI-Agents vom vorherigen Build profitieren. In GitLab CI oder GitHub Actions ist das mit wenigen Zeilen Konfiguration aktiviert und reduziert Build-Zeiten für den Build-Container von zehn auf zwei Minuten bei unveränderten Dependencies.


# CI pipeline script: build and push Magento Build-Container image
# Uses BuildKit registry cache for fast incremental builds

#!/usr/bin/env bash
set -euo pipefail

REGISTRY="registry.mironsoft.de"
IMAGE="${REGISTRY}/magento-app"
BRANCH="${CI_COMMIT_BRANCH:-main}"
SHA="${CI_COMMIT_SHORT_SHA:-local}"

# Enable BuildKit for advanced caching
export DOCKER_BUILDKIT=1

docker build \
  --file docker/Dockerfile \
  --target runtime \
  # Use registry cache from previous builds — avoids full re-compile on composer cache hit
  --cache-from "type=registry,ref=${IMAGE}:cache-${BRANCH}" \
  --cache-to   "type=registry,ref=${IMAGE}:cache-${BRANCH},mode=max" \
  --build-arg DEPLOY_LOCALES="de_DE en_US" \
  --build-arg DEPLOY_THEMES="Mironsoft/default" \
  --tag "${IMAGE}:${SHA}" \
  --tag "${IMAGE}:${BRANCH}-latest" \
  .

docker push "${IMAGE}:${SHA}"
docker push "${IMAGE}:${BRANCH}-latest"

echo "Build-Container image pushed: ${IMAGE}:${SHA}"

8. Integration in CI/CD-Pipelines

Der Build-Container-Ansatz entfaltet seinen vollen Nutzen in einer automatisierten CI/CD-Pipeline. Jeder Push auf den Hauptbranch triggert einen Docker-Build, der DI Compile und Static Content Deploy ausführt. Das Ergebnis ist ein versioniertes Image mit einem konkreten Git-SHA als Tag. Der Deployment-Job zieht dieses Image und startet neue Container – ohne dass auf dem Produktionssystem irgendetwas kompiliert wird. Rollbacks reduzieren sich auf docker service update --image IMAGE:SHA service_name.

Ein wichtiger Aspekt beim Einsatz des Build-Containers in CI ist das Handling von Magento-Modulen mit Node-Build-Schritten. Hyvä Themes benötigen einen Tailwind-CSS-Build, bevor der Static Content Deploy sinnvoll ausgeführt werden kann. Im Dockerfile fügt man dafür eine weitere Builder-Stage ein, die Node installiert, npm-Dependencies installiert und den CSS-Build ausführt. Die kompilierte CSS-Datei wird dann als Artefakt in die PHP-Builder-Stage kopiert, bevor setup:static-content:deploy startet. So bleibt der Build-Container vollständig selbstständig und benötigt kein externes Build-System.

9. Build-Container vs. Laufzeit-Deployment im Vergleich

Die Entscheidung zwischen Build-Container und Laufzeit-Deployment hat weitreichende Konsequenzen für Deployment-Geschwindigkeit, Konsistenz und Betriebskomplexität. Beide Ansätze haben ihre Berechtigung, aber für Produktionssysteme mit regelmäßigen Releases ist der Build-Container-Ansatz in fast allen Dimensionen überlegen.

Kriterium Laufzeit-Deployment Build-Container-Ansatz Gewinner
Deployment-Dauer 5–15 Minuten (inkl. Compile) 30 Sekunden (Image pull + start) Build-Container
Reproduzierbarkeit Abhängig vom Zielserver 100% identisch über alle Umgebungen Build-Container
Rollback Komplexes Re-Deployment nötig Image-Tag wechseln Build-Container
Prod.-Load beim Deploy Hohe CPU-Last auf Produktion Keine Last auf Produktion Build-Container
Ersteinrichtung Einfaches Deployment-Skript Dockerfile + CI-Pipeline nötig Laufzeit

Der einzige echte Vorteil des Laufzeit-Deployments ist die geringere initiale Komplexität. Für Teams, die noch kein CI/CD-System haben oder in einer sehr frühen Projektphase sind, ist das Argument berechtigt. Sobald ein Projekt jedoch regelmäßige Releases hat oder auf mehrere Umgebungen (Staging, Produktion, QA) deployt, zahlt sich der Aufwand für den Build-Container-Ansatz innerhalb weniger Sprints aus.

Mironsoft

Magento Build-Infrastruktur, Docker-Pipelines und Deployment-Automatisierung

Magento-Deployments, die in Sekunden statt Minuten laufen?

Wir bauen den Build-Container-Ansatz für eure Magento-Instanz – mit DI Compile und Static Content Deploy im Image-Build, Registry-Cache-Optimierung und vollständiger CI/CD-Pipeline-Integration.

Dockerfile-Architektur

Multi-Stage Build mit optimiertem Layer-Cache für Magento DI und Static Content

CI/CD-Pipeline

GitLab CI oder GitHub Actions – automatisierte Builds mit Registry-Cache

Secret-Management

Sichere env.php-Injektion ohne Geheimnisse im Image

10. Zusammenfassung

Der Build-Container-Ansatz für Magento verlagert DI Compile und Static Content Deploy vollständig in den Docker Image-Build. Das Ergebnis sind Images, die sofort einsatzbereit sind, ohne Compilierung auf dem Produktionssystem. Deployments reduzieren sich auf das Starten eines neuen Containers. Die Kombination aus Multi-Stage Dockerfile, optimiertem Layer-Caching und CI/CD-Integration macht den Ansatz sowohl schnell als auch wartbar. Eine saubere Trennung zwischen Build-Konfiguration (Stub-env.php) und Laufzeit-Konfiguration (Docker Secrets) stellt sicher, dass keine Geheimnisse in Images eingebrannt werden.

Der wichtigste Schritt ist die Aufstellung der richtigen Layer-Reihenfolge im Dockerfile: Dependencies zuerst, dann Code, dann DI Compile, dann Static Content. Diese Reihenfolge maximiert die Cache-Nutzung und minimiert Build-Zeiten. Teams, die diesen Ansatz konsequent umsetzen, berichten von Deployment-Zeiten unter einer Minute für Magento-Shops – verglichen mit zehn bis zwanzig Minuten beim klassischen Laufzeit-Ansatz.

Magento Build-Container — Das Wichtigste auf einen Blick

DI Compile im Build

setup:di:compile läuft in der Builder-Stage des Dockerfiles. Generierter Code ist Teil des Images – kein Compile auf Produktion mehr nötig.

Static Content Deploy

setup:static-content:deploy mit --jobs 4 parallelisiert über verfügbare CPU-Kerne des CI-Servers – nicht des Produktionssystems.

Layer-Cache-Strategie

composer.json/lock in eigener Schicht vor Code-Copy. BuildKit Registry-Cache für CI-Agents ohne lokalen Cache.

Secret-Management

Stub-env.php im Build, echte env.php via Docker Secret zur Laufzeit. Keine Datenbankpasswörter im Image.

11. FAQ: Magento Static Content & DI Compile im Build-Container

1Warum DI Compile im Build-Container statt auf Produktion?
Reproducible, schneller, kein Produktions-Load. Gleiche PHP-Version und Extensions garantieren identisches Compile-Ergebnis in jeder Umgebung.
2Brauche ich eine Datenbank für den Build?
Nein. Stub-env.php mit Crypt-Key und Modul-Liste reicht. Datenbankpasswörter gehören nicht ins Image – sie werden zur Laufzeit injiziert.
3Wie lange dauert ein Build mit warmem Cache?
3–5 Minuten bei reinen Code-Änderungen. Composer wird nur bei Änderungen an composer.lock neu ausgeführt dank Layer-Caching.
4Was passiert bei einem fehlgeschlagenen DI Compile?
Docker-Build schlägt fehl, kein Image in der Registry. Fehler sichtbar in CI-Logs, bevor er Produktion erreicht – das ist exakt das gewünschte Verhalten.
5Wie deploye ich die env.php sicher?
Docker Secrets für Swarm, Kubernetes Secrets für K8s, bind-mount für Single-Node. Entrypoint kopiert die Datei vor PHP-FPM-Start an den richtigen Ort.
6Funktioniert das mit Hyvä Themes?
Ja. Node-Builder-Stage für Tailwind CSS Build, dann COPY --from=node-builder in die PHP-Stage, dann Static Content Deploy. Vollständig im Dockerfile abbildbar.
7Layer-Cache optimal nutzen?
composer.json/lock zuerst kopieren, dann Code. BuildKit mit --cache-from registry für CI-Agents. Nur geänderte Layer werden neu gebaut.
8Mehrere Themes gleichzeitig deployen?
Ja, mit mehreren -t Parametern und --jobs 4. Parallelisierung nutzt CI-Server-CPUs statt Produktions-CPUs.
9Unterschied builder vs. runtime Stage?
Builder enthält alle Build-Tools. Runtime enthält nur PHP-FPM und die fertigen Anwendungsdateien – kleiner, sicherer, schneller zu pullen.
10Wie integriere ich das in GitLab CI?
Build-Job mit DOCKER_BUILDKIT=1, --cache-from/--cache-to type=registry. Deploy-Job startet neuen Container via Docker API oder SSH. Rollback: Image-Tag wechseln.