← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Docker Multi-Stage Builds

DevOps PHP 5.0+ Intermediate
debt(d5/e5/b5/t5)
d5 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'specialist tool catches it' (d5) — hadolint, trivy, and dive (all listed in detection_hints.tools) can identify single-stage Dockerfiles with bloated images, missing .dockerignore, or dev dependencies in production. These are specialist DevOps tools, not default linters.

e5 Effort Remediation debt — work required to fix once spotted

Closest to 'touches multiple files / significant refactor in one component' (e5) — the quick_fix describes creating a multi-stage Dockerfile, which requires restructuring the Dockerfile, potentially creating/updating .dockerignore, and adjusting CI/CD pipelines. It's not a one-liner but stays within the deployment component.

b5 Burden Structural debt — long-term weight of choosing wrong

Closest to 'persistent productivity tax' (b5) — applies to web and cli contexts per applies_to, affecting all containerized deployments. Once established, the multi-stage pattern becomes a template that shapes how all future services are built, but doesn't reach the 'defines system shape' level of b7+.

t5 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'notable trap' (t5) — the misconception explicitly states developers believe 'Multi-stage builds are only for compiled languages' when PHP benefits equally. This is a documented gotcha that most DevOps-oriented developers eventually learn, but it contradicts intuition from compiled-language contexts.

About DEBT scoring →

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

Added 16 Mar 2026
Edited 22 Mar 2026
Views 64
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
1 ping T 1 ping W 2 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 2 pings W 2 pings T 3 pings F 3 pings S 1 ping S 3 pings M 2 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 2 pings T 0 pings W 0 pings T 1 ping F 0 pings S 0 pings S 2 pings M 0 pings T 0 pings W
No pings yet today
No pings yesterday
Scrapy 15 Perplexity 9 Amazonbot 7 Ahrefs 4 SEMrush 4 Google 3 ChatGPT 3 Unknown AI 2 Claude 2 Bing 2 Majestic 1 Meta AI 1 PetalBot 1
crawler 51 crawler_json 3
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


✓ schema.org compliant