0Pricing
PHP Academy · Урок

Многоэтапная сборка и оптимизация

Уменьшайте образы и отделяйте сборку от среды выполнения

«Многоэтапная сборка и оптимизация» — бесплатный урок 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 — локальная установка не требуется.

Все уроки этого курса

  1. Контейнеризация PHP-приложения
  2. Многоэтапная сборка и оптимизация
  3. Docker Compose для локальных стеков
  4. CI/CD с GitHub Actions
← Назад к PHP Academy