Многоэтапная сборка и оптимизация
Уменьшайте образы и отделяйте сборку от среды выполнения
«Многоэтапная сборка и оптимизация» — бесплатный урок PHP Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения PHP Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс PHP Academy содержит 4 уроков всего.
Зачем нужны несколько этапов
Одноэтапный образ переносит Composer, зависимости для сборки, заголовочные файлы для разработки и папку tests/ в рабочую среду. Многоэтапная сборка позволяет компилировать и устанавливать зависимости в большом этапе сборки, а затем копировать только готовые артефакты в компактный этап выполнения.
Результат: меньшие образы, меньшая поверхность атаки, более быстрая загрузка и отсутствие компиляторов в рабочей среде.
Именованные этапы
Каждая инструкция FROM ... AS name начинает новый этап. Последующие этапы могут копировать файлы из предыдущих с помощью COPY --from=name. Только заключительный этап становится вашим образом; промежуточные этапы удаляются, но сохраняются в кэше.
# Stage 1: dependencies
FROM composer:2 AS vendor
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --prefer-dist --ignore-platform-reqs
# Stage 2: runtime
FROM php:8.3-fpm-alpine AS runtime
WORKDIR /app
COPY --from=vendor /app/vendor ./vendor
COPY . .Отделение зависимостей для сборки
Для компиляции расширений нужны autoconf, gcc и заголовочные файлы для разработки — им не место в среде выполнения. Используйте установщик на этапе сборки, а затем скопируйте скомпилированные файлы .so и соответствующий файл ini из conf.d в чистый этап выполнения.
FROM php:8.3-fpm-alpine AS ext-builder
ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
RUN chmod +x /usr/local/bin/install-php-extensions && \
install-php-extensions redis igbinary opcache intl
FROM php:8.3-fpm-alpine AS runtime
# Copy compiled extensions + their enable configs
COPY --from=ext-builder /usr/local/lib/php/extensions/ /usr/local/lib/php/extensions/
COPY --from=ext-builder /usr/local/etc/php/conf.d/ /usr/local/etc/php/conf.d/Порядок кэширования слоёв
Docker кэширует слои сверху вниз и делает недействительными все слои ниже изменённого. Располагайте инструкции от изменяющихся реже всего к изменяющимся чаще всего:
- Базовый образ и расширения (редко)
composer.lockи установка (время от времени)- Исходный код приложения (при каждом коммите)
- Генерация автозагрузчика (при каждом коммите)
Поэтому изменение только кода полностью повторно использует кэшированный слой зависимостей.
# BAD: copying all source before composer install
# busts the vendor layer on every code change
COPY . .
RUN composer install
# GOOD: lock first, then source
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-autoloader
COPY . .
RUN composer dump-autoload --optimizeМонтирование кэша BuildKit
С BuildKit (DOCKER_BUILDKIT=1) можно подключить постоянный кэш, который сохраняется между сборками, но не попадает в образ. Это особенно удобно для глобального кэша Composer: при повторных сборках пакеты не придётся загружать заново.
# syntax=docker/dockerfile:1
FROM composer:2 AS vendor
WORKDIR /app
COPY composer.json composer.lock ./
RUN --mount=type=cache,target=/tmp/composer-cache \
COMPOSER_CACHE_DIR=/tmp/composer-cache \
composer install --no-dev --prefer-distИзмерение размера образа
Изучите состав слоёв, чтобы найти избыточные данные. docker history показывает размер, добавленный каждой инструкцией, а такие инструменты, как dive, показывают неиспользуемое пространство. Цель — получить этап выполнения без компиляторов, Composer и зависимостей для разработки.
# Compare sizes
docker images myapp
# Per-layer contribution
docker history --no-trunc --format '{{.Size}}\t{{.CreatedBy}}' myapp:latest
# Deep inspection of wasted bytes
dive myapp:latestОчистка заключительного этапа
Этап выполнения не должен (NOT) содержать Composer, сценарий установки расширений или набор тестов. Копируйте vendor и исходный код с этапов сборки; никогда не запускайте composer на заключительном этапе. Если необходимо запустить установщик там, удалите его после использования.
FROM php:8.3-fpm-alpine AS runtime
WORKDIR /app
# bring extensions + vendor in from builders — no Composer here
COPY --from=ext-builder /usr/local/lib/php/extensions/ /usr/local/lib/php/extensions/
COPY --from=ext-builder /usr/local/etc/php/conf.d/ /usr/local/etc/php/conf.d/
COPY --from=vendor /app/vendor ./vendor
COPY . .
USER www-data
CMD ["php-fpm"]Ограничения Distroless и Scratch
PHP не может работать в действительно пустом scratch — ему нужны libc и общие библиотеки. Практический минимум — alpine (musl) или минимальный Debian в стиле distroless. Alpine занимает меньше места, но следите за собственными библиотеками, ожидающими glibc; если столкнётесь с аварийным завершением при работе с NSS/ICU, перейдите на php:8.3-fpm-bookworm.
# Smallest practical PHP runtime
FROM php:8.3-fpm-alpine
# If musl causes native-lib issues (e.g., some ICU edge cases),
# the glibc Debian slim variant is the safe fallback:
# FROM php:8.3-fpm-bookwormВыбор этапа сборки
Один Dockerfile может обслуживать разработку и рабочую среду с помощью --target. Добавьте этап dev поверх этапа выполнения, вернув зависимости Composer для разработки и Xdebug; для рабочей среды собирайте с --target=runtime, а локально — с --target=dev.
FROM runtime AS dev
ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
RUN chmod +x /usr/local/bin/install-php-extensions && \
install-php-extensions xdebug @composer
USER root
RUN composer install # includes dev deps
# Build prod: docker build --target runtime -t app:prod .
# Build dev: docker build --target dev -t app:dev .Рассуждение о размерах слоёв
Простая мысленная модель помогает предсказать поведение кэша. Этот фрагмент CLI имитирует классическую ошибку неограниченного роста слоя и сравнивает её с ограниченным вариантом, показывая, почему очистку важно объединять с установкой в одной инструкции RUN.
<?php
// Simulate layer sizes (MB) for two strategies
$installSteps = [120, 8, 8, 8];
// Separate RUN per step keeps temp files in layers
$separate = array_sum($installSteps);
// Single RUN with cleanup removes temp files before commit
$combined = max($installSteps); // peak, then cleaned
echo "Separate layers total: {$separate} MB\n";
echo "Combined+cleanup: {$combined} MB\n";
echo 'Saved: ' . ($separate - $combined) . " MB\n";
?>Объединяйте установку и очистку в одном RUN
Каждый RUN — это слой; удаление файлов в последующем слое не уменьшает образ, потому что предыдущие слои по-прежнему содержат эти байты. Устанавливайте зависимости, используйте их и очищайте временные файлы в одной инструкции RUN, чтобы временные файлы не были зафиксированы в образе.
RUN apk add --no-cache --virtual .build-deps $PHPIZE_DEPS && \
pecl install redis && \
docker-php-ext-enable redis && \
apk del .build-deps && \
rm -rf /tmp/pear /var/cache/apk/*Быстрая проверка
Почему зависимости для сборки необходимо удалять в той же инструкции RUN, в которой они были установлены?
Итоги
Многоэтапные сборки не позволяют компиляторам и зависимостям для разработки попасть в рабочую среду. Вы научились: давать этапам имена и копировать артефакты с помощью COPY --from, располагать слои от наименее изменчивых к наиболее изменчивым для эффективного кэширования, использовать монтирование кэша BuildKit для Composer, измерять размер с помощью docker history/dive, осознанно выбирать alpine или glibc, выбирать этапы для разработки и рабочей среды и объединять установку с очисткой в одной инструкции RUN.
Часто задаваемые вопросы
Урок «Многоэтапная сборка и оптимизация» бесплатный?
Да — полный текст урока «Многоэтапная сборка и оптимизация» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс PHP Academy, подпишись на CoddyKit PRO. Курс PHP Academy содержит 4 уроков всего.
Чему я научусь в уроке «Многоэтапная сборка и оптимизация»?
Уменьшайте образы и отделяйте сборку от среды выполнения Ты практикуешь PHP Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать PHP Academy?
Предыдущий опыт не требуется. PHP Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Многоэтапная сборка и оптимизация»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке PHP Academy?
Да. Каждый урок PHP Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Контейнеризация PHP-приложения
- Многоэтапная сборка и оптимизация
- Docker Compose для локальных стеков
- CI/CD с GitHub Actions