Docker Multi-Stage Builds
Also Known As
multi-stage build
Docker builder pattern
minimal Docker image
TL;DR
Dockerfile builds using multiple FROM stages — build dependencies (Composer, Node, test tools) in earlier stages, copy only production artifacts to the final minimal image.
Explanation
Multi-stage builds eliminate the need for separate Dockerfiles for build and production. The builder stage installs Composer and dev dependencies, runs tests, and compiles assets. The final stage copies only the production files — resulting in an image without Composer, dev packages, Xdebug, or build tools. A typical PHP app goes from a 800MB single-stage image to a 80MB multi-stage image. Smaller images: faster CI pulls, smaller attack surface, and lower registry storage costs.
Diagram
flowchart TD
subgraph Builder Stage
BASE1[php:8.3-fpm-alpine]
COMP[Composer install<br/>dev dependencies]
TEST[Run PHPUnit tests]
BASE1 --> COMP --> TEST
end
subgraph Production Stage
BASE2[php:8.3-fpm-alpine]
COPY[COPY only src + vendor<br/>no Composer no dev deps]
BASE2 --> COPY
end
TEST -->|copy artifacts| COPY
COPY --> IMG[Final image<br/>80MB vs 800MB]
style IMG fill:#238636,color:#fff
Common Misconception
✗ Multi-stage builds are only for compiled languages — PHP apps benefit just as much: removing Composer, Xdebug, and dev dependencies from production images reduces size by 80% and attack surface significantly.
Why It Matters
A production PHP image with Xdebug, Composer, and dev dependencies installed is both slower to deploy and has a larger security attack surface — multi-stage builds solve both problems.
Common Mistakes
- COPY --from=builder . . — copies everything including dev files; be specific about what to copy.
- Not using .dockerignore — the build context includes node_modules, .git, and test files without it.
- Running tests in the production stage — tests belong in the builder stage.
- Not pinning base image versions — FROM php:latest changes unexpectedly.
Code Examples
✗ Vulnerable
# Single stage — dev deps in production:
FROM php:8.3-fpm
RUN apt-get install -y git zip unzip
COPY composer.json composer.lock ./
RUN curl -sS https://getcomposer.org/installer | php
RUN php composer.phar install # Installs dev deps including Xdebug!
COPY . .
# Image: ~800MB with all dev tools
✓ Fixed
# Multi-stage — minimal production image:
FROM php:8.3-fpm-alpine AS builder
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader
COPY . .
RUN vendor/bin/phpunit # Tests in build stage
# Production — no Composer, no dev deps:
FROM php:8.3-fpm-alpine
WORKDIR /app
COPY --from=builder /app/public ./public
COPY --from=builder /app/src ./src
COPY --from=builder /app/vendor ./vendor
# Image: ~80MB
Tags
🤝 Adopt this term
£79/year · your link shown here
Added
16 Mar 2026
Edited
22 Mar 2026
Views
27
🤖 AI Guestbook educational data only
|
|
Last 30 days
Agents 0
No pings yet today
Perplexity 8
Amazonbot 6
Unknown AI 2
Ahrefs 2
SEMrush 2
Majestic 1
Google 1
Also referenced
How they use it
crawler 22
⚡
DEV INTEL
Tools & Severity
🟡 Medium
⚙ Fix effort: Medium
⚡ Quick Fix
Use a multi-stage Dockerfile: first stage runs composer install, second stage copies only vendor/ and src/ without build tools — keeps production image small and secure
📦 Applies To
PHP 5.0+
web
cli
🔗 Prerequisites
🔍 Detection Hints
Single-stage Dockerfile with composer and build tools in production image; image >1GB; no .dockerignore file
Auto-detectable:
✓ Yes
docker
hadolint
trivy
dive
⚠ Related Problems
🤖 AI Agent
Confidence: Medium
False Positives: Low
✗ Manual fix
Fix: Medium
Context: File