Parę miesięcy temu zaproponowałem by Composer dostarczany był w obrazach zawierających jedynie plik wykonywalny. Wczoraj mój pull request został zmerdżowany, więc już można używać tego typu obrazów podczas budowania swoich własnych, co jest najprostszą metodą instalacji Composera 😁
💥 I'm happy to announce that my PR to #Composer was merged, so soon it'll be possible to improve #Docker build and copy Composer's binary from low-size (~2.5MB), binary-only images 🥳
— Greg Korba 🛠️🛹 Codito (@_Codito_) October 31, 2022
ℹ️ Short `composer` alias is not supported.https://t.co/wIxFefWaVP
Happy #PHP building! 😁 pic.twitter.com/0SX1tg5e3R
Wyjaśnienie
Gdy istnieje potrzeba dołączenia Composera do budowanego obrazu Dockera, można to zrobić na kilka sposobów. Jednak wcześniej, chcąc skorzystać z podejścia COPY --from=composer
, trzeba było pobrać obraz Composera ważący ~190MB, tylko po to, by z niego przekopiować jeden plik o wadze 2.5MB. Oczywistym jest, że nie było to optymalne, nawet jeśli weźmiemy pod uwagę build cache Dockera.
Po moich zmianach, każdy dockerowy obraz Composera będzie miał swój lekki odpowiednik zawierający jedynie plik wykonywalny. Są jednak 3 różnice, na które należy zwrócić uwagę:
- należy używać obrazów
composer/composer
(szczegóły poniżej) - do tagu należy dodać sufiks
-bin
- plik wykonywalny zlokalizowany jest w głównym katalogu (
/composer
), czyli gdzie indziej niż w pełnych obrazach (/usr/bin/composer
)
Na przykład, aby zainstalować najnowszą wersję Composera z gałęzi v2, wystarczy zrobić:
FROM php:8-alpine
COPY --from=composer/composer:2-bin /composer /usr/bin/composer
Kiedy tego używać (a kiedy nie)
Obrazy z plikiem wykonywalnym są przydatne w trakcie budowania własnych obrazów opartych o PHP, gdy istnieje potrzeba dostarczenia ich wraz z Composerem. Zamiast instalować go programowo, można użyć dockerowego obrazu i wyekstrahować z niego pojedynczy, gotowy do użytku plik.
W dość oczywisty sposób wynika z tego, że obrazy binary-only nie nadają się do uruchamiania czegokolwiek z ich użyciem (w sposób bezpośredni). Uruchomienie docker run -it --rm composer/composer:2-bin <cokolwiek>
nie zadziała, ponieważ obrazy te nie zawierają w sobie niczego poza plikiem wykonywalnym Composera — nie ma w nich ani środowiska uruchomieniowego PHP, ani powłoki (shell).
Szczegóły implementacyjne
We wspomnianym Pull Requeście zauważyć można dwa rodzaje zmian:
- pliki
Dockerfile
zostały rozwinięte o tzw. build targety, by możliwe było budowanie obu wersji obrazów - definicje GitHub Actions zostały zmodyfikowane tak, by każdy pipeline budował zarówno pełne, jak i lekkie wersje obrazów (dodatkowo są tagowane w dedykowany sposób)
Ta pierwsza zmiana jest bardziej istotna, zatem przyjrzyjmy się jej na przykładzie zmian dla gałęzi 2.4
:
diff --git a/2.4/Dockerfile b/2.4/Dockerfile
index c0b4ca7..866be1c 100644
--- a/2.4/Dockerfile
+++ b/2.4/Dockerfile
@@ -1,4 +1,4 @@
-FROM php:8-alpine
+FROM php:8-alpine AS binary-with-runtime
RUN set -eux ; \
apk add --no-cache --virtual .composer-rundeps \
@@ -89,3 +89,10 @@ WORKDIR /app
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["composer"]
+
+FROM scratch AS standalone-binary
+
+COPY --from=binary-with-runtime /usr/bin/composer /composer
+
+# This is defined as last target to be backward compatible with build without explicit --target option
+FROM binary-with-runtime AS default
Na samym początku pliku, do już istniejącej definicji budowania pełnego obrazu, dodany został target binary-with-runtime
. Jest on później wykorzystywany na dwa sposoby: do budowania pełnego obrazu (docker build --target binary-with-runtime
) oraz jako baza do budowania wersji binary-only (COPY --from=binary-with-runtime /usr/bin/composer /composer
). Ten drugi proces nazywamy multi-stage buildem i jest to metoda polegająca na dzieleniu procesu budowania na mniejsze, dedykowane etapy, między którymi mogą istnieć relacje (dziedziczenie warstw, przenoszenie pojedynczych plików). Takie podejście jest przydatne szczególnie w kontekście optymalizacji rozmiaru wynikowych obrazów, ponieważ m.in. można w ten sposób łatwo pominąć pliki tymczasowe.
Ostatnia linijka jest ciekawa — zapewnia wsteczną kompatybilność poprzez zdefiniowanie aliasu default
dla targetu binary-with-runtime
. Ponieważ jest to ostatni target w pliku, zostanie on użyty automatycznie, gdy opcja --target
nie zostanie zdefiniowana w komendzie wyzwalającej build.
Podsumowanie
To niewielka, ale ważna i pomocna zmiana, dzięki której będzie możliwe zoptymalizowanie wielu istniejących procesów budowania obrazów. Wypróbuj to w swoim procesie i daj znać co myślisz 🙂