Kompilacje wieloetapowe i optymalizacja
Zmniejszaj obrazy i oddzielaj kompilację od środowiska uruchomieniowego
Kompilacje wieloetapowe i optymalizacja to bezpłatna lekcja PHP Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej PHP Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs PHP Academy zawiera 4 lekcji w sumie.
Dlaczego warto używać multi-stage
Obraz jednoetapowy zawiera Composer, zależności potrzebne do budowania, nagłówki deweloperskie oraz katalog tests/, które trafiają do środowiska produkcyjnego. Budowanie wieloetapowe pozwala kompilować i instalować zależności w rozbudowanym etapie builder, a następnie kopiować tylko gotowe artefakty do niewielkiego etapu runtime.
Rezultat: mniejsze obrazy, mniejsza powierzchnia ataku, szybsze pobieranie i brak kompilatorów wdrażanych na produkcję.
Nazwane etapy
Każde FROM ... AS name rozpoczyna nowy etap. Późniejsze etapy mogą kopiować pliki z wcześniejszych za pomocą COPY --from=name. Tylko końcowy etap staje się obrazem; etapy pośrednie są odrzucane, ale pozostają w pamięci podręcznej.
# 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 . .Oddzielanie zależności potrzebnych do budowania
Kompilowanie rozszerzeń wymaga autoconf, gcc i nagłówków deweloperskich — żaden z tych elementów nie należy do środowiska uruchomieniowego. Należy użyć instalatora w etapie builder, a następnie skopiować skompilowane pliki .so oraz odpowiadający im plik ini z katalogu conf.d do czystego etapu runtime.
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/Kolejność buforowania warstw
Docker buforuje warstwy od góry do dołu i unieważnia wszystkie warstwy poniżej zmienionej warstwy. Należy zachować kolejność od najrzadziej do najczęściej zmieniających się elementów:
- Baza i rozszerzenia (rzadko)
composer.locki instalacja (sporadycznie)- Kod aplikacji (przy każdym commicie)
- Generowanie autoload (przy każdym commicie)
Oznacza to, że zmiana dotycząca wyłącznie kodu ponownie wykorzystuje całą buforowaną warstwę vendor.
# 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 --optimizeMontowania pamięci podręcznej BuildKit
Dzięki BuildKit (DOCKER_BUILDKIT=1) można zamontować trwałą pamięć podręczną, która przetrwa kolejne procesy budowania, ale nie trafi do obrazu. Doskonale sprawdza się to w przypadku globalnej pamięci podręcznej Composera, ponieważ kolejne buildy nie muszą ponownie pobierać pakietów.
# 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-distPomiar rozmiaru obrazu
Należy przeanalizować podział na warstwy, aby znaleźć zbędne dane. Polecenie docker history pokazuje rozmiar dodany przez każdą instrukcję, a narzędzia takie jak dive wskazują zmarnowane miejsce. Celem jest etap runtime bez kompilatorów, Composera i zależności deweloperskich.
# 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:latestOczyszczanie końcowego etapu
Etap runtime NIE powinien zawierać Composera, skryptu extension-installer ani zestawu testów. Należy kopiować vendor i kod źródłowy z etapów builder; nigdy nie należy uruchamiać composer w końcowym etapie. Jeśli trzeba uruchomić instalator w tym etapie, po użyciu należy go usunąć.
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"]Ograniczenia distroless / scratch
PHP nie może działać na całkowicie pustym scratch — potrzebuje libc i bibliotek współdzielonych. Najmniejszą praktyczną bazą jest alpine (musl) lub minimalny Debian w stylu distroless. Alpine zajmuje najmniej miejsca, ale należy uważać na biblioteki natywne oczekujące glibc; w przypadku segfaultów związanych z NSS/ICU należy wrócić do 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-bookwormWybieranie etapów docelowych
Jeden Dockerfile może obsługiwać środowisko deweloperskie i produkcyjne za pomocą --target. Należy dodać etap dev nad etapem runtime, ponownie uwzględniający deweloperskie zależności Composera i Xdebug; na produkcji należy budować z użyciem --target=runtime, a lokalnie z użyciem --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 .Analizowanie rozmiarów warstw
Prosty model mentalny pomaga przewidywać zachowanie pamięci podręcznej. Ten fragment CLI symuluje klasyczny błąd nieograniczonego przyrostu warstwy w porównaniu z wariantem z limitem, pokazując, dlaczego połączenie czyszczenia z instalacją w tym samym RUN ma znaczenie.
<?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";
?>Łączenie instalacji i czyszczenia w jednym RUN
Każde RUN tworzy warstwę; usunięcie plików w późniejszej warstwie nie zmniejsza obrazu, ponieważ wcześniejsze warstwy nadal zawierają te dane. Instalowanie, używanie i czyszczenie należy wykonywać w jednym RUN, aby pliki tymczasowe nie zostały zapisane w obrazie.
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/*Szybkie sprawdzenie
Dlaczego zależności potrzebne do budowania trzeba usuwać w tym samym poleceniu RUN, w którym zostały zainstalowane?
Podsumowanie
Budowanie wieloetapowe pozwala trzymać kompilatory i zależności deweloperskie poza produkcją. Należy umieć: nazywać etapy i kopiować artefakty za pomocą COPY --from, układać warstwy od najmniej do najbardziej zmiennych na potrzeby buforowania, używać montowań pamięci podręcznej BuildKit dla Composera, mierzyć rozmiar za pomocą docker history/dive, świadomie wybierać między alpine a glibc, kierować budowanie do etapów dev/prod oraz łączyć instalację i czyszczenie w jednym RUN.
Często zadawane pytania
Czy lekcja „Kompilacje wieloetapowe i optymalizacja” jest bezpłatna?
Tak — pełny tekst „Kompilacje wieloetapowe i optymalizacja” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu PHP Academy, przejdź na CoddyKit PRO. Kurs PHP Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Kompilacje wieloetapowe i optymalizacja”?
Zmniejszaj obrazy i oddzielaj kompilację od środowiska uruchomieniowego Ćwiczysz PHP Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć PHP Academy?
Nie wymagamy żadnego doświadczenia. PHP Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.
Ile czasu zajmuje lekcja „Kompilacje wieloetapowe i optymalizacja”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji PHP Academy?
Tak. Każda lekcja PHP Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Konteneryzacja aplikacji PHP
- Kompilacje wieloetapowe i optymalizacja
- Docker Compose dla lokalnych stosów
- CI/CD z GitHub Actions