0Pricing
PHP Academy · Lekcja

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.lock i 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 --optimize

Montowania 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-dist

Pomiar 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:latest

Oczyszczanie 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-bookworm

Wybieranie 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

  1. Konteneryzacja aplikacji PHP
  2. Kompilacje wieloetapowe i optymalizacja
  3. Docker Compose dla lokalnych stosów
  4. CI/CD z GitHub Actions
← Powrót do PHP Academy