cleanly inside the Docker build container
Running DI compile and static content deploy at runtime slows down every deployment and risks inconsistent images. The build container approach moves both steps into image creation, giving you reproducible results and much faster release cycles.
Table of Contents
- 1. The runtime problem in Magento deployments
- 2. What setup:di:compile actually does
- 3. Static content deploy: scope and dependencies
- 4. The build container approach at a glance
- 5. The Dockerfile for the Magento build container
- 6. Environment variables and configuration strategy
- 7. Using the Docker layer cache deliberately
- 8. Integration into CI/CD pipelines
- 9. Build container vs. runtime deployment compared
- 10. Summary
- 11. FAQ
1. The runtime problem in Magento deployments
In many Magento projects, setup:di:compile runs as part of the deployment script directly on the production server or inside the running container. That is convenient when you want to deploy quickly, but it has fundamental downsides: the process takes several minutes, it blocks or slows down the live shop, and the outcome depends on the exact PHP version and installed extensions on the target server. A container that pulls a different PHP base image tomorrow can produce a different compile result, even if the code itself has not changed.
The real problem lies in the philosophy: in a Docker environment, an image should be complete and ready to run the moment it is pulled from a registry. Deployment should then mean nothing more than stopping the old container and starting the new one. Every build step that still happens at runtime works against that principle. The build container approach for Magento is the direct answer to this problem: every compute-heavy compilation step happens when the image is built, not when it starts.
2. What setup:di:compile actually does
The command bin/magento setup:di:compile automatically generates the complete dependency injection code for all Magento modules. It analyzes XML configuration, interfaces, classes and plugin definitions, and turns them into PHP classes for proxy objects, interceptors and factories, writing the result into the generated/ directory. Without this step, Magento generates these classes one by one at runtime, which noticeably slows down every first request. The build container lets you run this expensive process once, when the image is built, instead of on every deployment or, worse, on the first request in production.
A critical detail: setup:di:compile is only reproducible if the PHP code is complete. That means Composer dependencies must already be installed with composer install --no-dev --optimize-autoloader. If a module is missing or the autoloader is not optimized, the compiler either produces broken classes or fails outright. In the build container, the order of the Dockerfile layers guarantees that Composer always runs before DI compile, in a way that is verifiable and traceable.
3. Static content deploy: scope and dependencies
The command setup:static-content:deploy is one of the most time-consuming build steps in any Magento project with multiple themes and locales. It copies, minifies and links all JavaScript, CSS and image files from themes and modules into the pub/static/ directory. For a project with two themes and three locales, that can easily mean 30,000 to 80,000 files, and generating them takes several minutes. If this step runs inside the live container, the shop either suffers increased load during that time or, worse, serves partially generated files.
Inside the build container, static content deploy runs in an isolated environment with no live traffic. The result is built straight into the image, so the new container can start immediately with all static files in place. Another advantage: the deploy step can be parallelized across locales. With the --jobs 4 option, Magento starts four parallel processes, which cuts runtime significantly on build servers with many CPU cores. The build container uses the available compute capacity of the CI server instead of that of the production system.
# 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. The build container approach at a glance
The concept of the build container for Magento consists of two clearly separated phases: a build stage that runs every compute-heavy step, and a runtime stage that contains only the necessary result. Docker multi-stage builds let you define both phases in a single Dockerfile. The build stage contains all build tools (Composer, Node, npm), runs DI compile and static content deploy, and passes on only the resulting directories. The runtime stage contains only PHP-FPM with the required extensions and the finished application files.
The decisive advantage: the final runtime image contains no build tools, no Composer dependencies needed only for the build process, and no temporary files. It is smaller, safer and faster to pull. Every developer and every CI instance using the same Dockerfile produces identical images, because the build container pins the exact PHP version, extensions and compose configuration under which compilation happens. Environment differences between development and production are ruled out.
5. The Dockerfile for the Magento build container
A complete Dockerfile for the Magento build container follows a fixed pattern: Composer install as the first layer, then a code copy, then DI compile, then static content deploy, and finally the switch into the runtime stage. This order is not arbitrary, it optimizes the Docker layer cache. If only the application code changes and the Composer dependencies stay the same, Docker reuses the cached Composer layer. That shortens build times in CI pipelines considerably, because Composer does not need a full reinstall on every commit.
Handling env.php is critical: this file holds database passwords, cache backend configuration and other runtime secrets. In the build container, a stub version is used that only contains the values needed for DI compile and static content deploy, typically no database connection, but a correct module configuration. At runtime, the real env.php is mounted in via a secret or volume, without ever becoming part of the image.
# 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. Environment variables and configuration strategy
The biggest challenge in the build container approach for Magento is handling configuration. Magento needs an env.php to start, but this file contains secrets that must never be baked into an image. The solution has two parts: a stub env.php for the build process, and a secret-mounting mechanism for runtime. The stub file contains only the crypt key, the module list and possibly base settings, everything DI compile and static content deploy need, but no database connection.
At runtime you inject the complete env.php either through Docker secrets (docker secret create), through a bind-mounted volume, or, in Kubernetes environments, through a secret volume. An init container or entrypoint script copies the file to the right place before PHP-FPM starts. That keeps the image fully portable and free of secrets. The build container never needs access to production databases, it only compiles code, never runtime data.
7. Using the Docker layer cache deliberately
The layer cache is the biggest performance lever in the build container approach. Docker invalidates every layer that follows as soon as one layer changes. For Magento that means composer.json and composer.lock must sit in a separate layer before the rest of the code copy. If only PHP code changes and dependencies stay the same, the Composer layer stays cached and the build starts directly with the code copy. That saves three to ten minutes per build run in typical CI pipelines.
A similar logic applies to static content deploy: theme files (PHTML, Tailwind CSS, JS) change more often than Magento's core modules. A two-stage approach separates the creation of vendor assets and theme assets into distinct layers. You can also point BuildKit's remote cache at a registry cache, so that even cold-start builds on fresh CI agents benefit from the previous build. In GitLab CI or GitHub Actions this takes only a few lines of configuration and reduces build times for the build container from ten minutes down to two when dependencies have not changed.
# 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 a full recompile on a 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 into CI/CD pipelines
The build container approach shows its full value in an automated CI/CD pipeline. Every push to the main branch triggers a Docker build that runs DI compile and static content deploy. The result is a versioned image tagged with a concrete Git SHA. The deployment job pulls this image and starts new containers, without compiling anything on the production system. Rollbacks reduce to docker service update --image IMAGE:SHA service_name.
An important aspect when using the build container in CI is handling Magento modules with Node build steps. Hyva Themes need a Tailwind CSS build before static content deploy can run meaningfully. In the Dockerfile you add another builder stage that installs Node, installs npm dependencies and runs the CSS build. The compiled CSS file is then copied as an artifact into the PHP builder stage before setup:static-content:deploy starts. That keeps the build container fully self-contained and free of any external build system.
9. Build container vs. runtime deployment compared
The choice between a build container and runtime deployment has far-reaching consequences for deployment speed, consistency and operational complexity. Both approaches have their place, but for production systems with regular releases, the build container approach wins in almost every dimension.
| Criterion | Runtime deployment | Build container approach | Winner |
|---|---|---|---|
| Deployment duration | 5 to 15 minutes (incl. compile) | 30 seconds (image pull + start) | Build container |
| Reproducibility | Depends on the target server | 100% identical across all environments | Build container |
| Rollback | Complex re-deployment required | Switch the image tag | Build container |
| Prod. load during deploy | High CPU load on production | No load on production | Build container |
| Initial setup | Simple deployment script | Dockerfile plus CI pipeline required | Runtime |
The only real advantage of runtime deployment is lower initial complexity. For teams that do not yet have a CI/CD system, or that are still in a very early project phase, that argument is fair. But once a project has regular releases or deploys to multiple environments (staging, production, QA), the effort of adopting the build container approach pays off within a few sprints.
Mironsoft
Magento build infrastructure, Docker pipelines and deployment automation
Want Magento deployments that take seconds instead of minutes?
We build the build container approach for your Magento instance, with DI compile and static content deploy baked into the image build, registry cache optimization and full CI/CD pipeline integration.
Dockerfile architecture
Multi-stage build with an optimized layer cache for Magento DI and static content
CI/CD pipeline
GitLab CI or GitHub Actions, automated builds with a registry cache
Secret management
Secure env.php injection with no secrets in the image
10. Summary
The build container approach for Magento moves DI compile and static content deploy entirely into the Docker image build. The result is images that are ready to run immediately, with no compilation on the production system. Deployments reduce to starting a new container. The combination of a multi-stage Dockerfile, optimized layer caching and CI/CD integration makes the approach both fast and maintainable. A clean separation between build configuration (stub env.php) and runtime configuration (Docker secrets) ensures that no secrets ever get baked into an image.
The most important step is getting the layer order right in the Dockerfile: dependencies first, then code, then DI compile, then static content. This order maximizes cache usage and minimizes build times. Teams that adopt this approach consistently report deployment times under a minute for Magento shops, compared to ten to twenty minutes with the classic runtime approach.
Magento Build Container: the essentials at a glance
DI compile in the build
setup:di:compile runs in the builder stage of the Dockerfile. The generated code is part of the image, no more compiling on production.
Static content deploy
setup:static-content:deploy with --jobs 4 parallelized across the available CPU cores of the CI server, not the production system.
Layer cache strategy
composer.json/lock in its own layer before the code copy. BuildKit registry cache for CI agents without a local cache.
Secret management
Stub env.php during the build, real env.php via Docker secret at runtime. No database passwords in the image.