Młodszy programista zmniejszył obraz Dockera dla Pythona z 1,2 GB do 85 MB, skracając czas trwania potoku CI z 11 minut do 90 sekund. Mniejsze obrazy pobierają się szybciej, kosztują mniej w przechowywaniu i zawierają mniej luk bezpieczeństwa.

Dlaczego rozmiar obrazu ma znaczenie

Za każdym razem, gdy pobierany jest kontener, rejestr przesyła cały obraz. Warstwa o rozmiarze 85 MB dociera w kilka sekund; warstwa 1,2 GB może zajmować minuty przy przeciętnym łączu. Agenci budowania muszą również przesyłać i buforować pełny obraz, co wydłuża czas CI i zwiększa rachunki za przechowywanie w chmurze. Każdy dodatkowy pakiet to potencjalna podatność, więc ograniczenie obrazu bazowego zmniejsza powierzchnię ataku.

Skąd bierze się nadmiar danych

  • Narzędzia budowania, takie jak gcc, pozostają w końcowym obrazie, jeśli zostaną zainstalowane w tym samym etapie, w którym uruchamiana jest aplikacja.
  • Cache menedżerów pakietów (np. apt lub pip) zajmują miejsce na dysku i domyślnie nigdy nie są czyszczone.
  • Każda instrukcja RUN tworzy nową warstwę tylko do odczytu; duplikujące się pliki w różnych warstwach sumują się.
  • Duże obrazy bazowe, takie jak ubuntu:latest, zawierają pełny system operacyjny, co jest znacznie większe niż wymaga minimalne środowisko uruchomieniowe Pythona.

Trzyetapowe zmniejszanie

Krok Obraz bazowy Podejście do budowania Wynikowy rozmiar
1 Standardowy Python (pełny) Pojedynczy etap, wszystkie narzędzia obecne 1,18 GB
2 python:slim Multi-stage: builder z gcc, końcowy etap kopiuje tylko skompilowane pakiety 210 MB
3 python:alpine Multi-stage na Alpine Linux, który sam w sobie jest bardzo mały 85 MB

Krok 1 – punkt odniesienia

Zaczynając od domyślnego obrazu Pythona, programista otrzymał artefakt o rozmiarze 1,18 GB. Obraz zawierał pełny stos Debian, nagłówki deweloperskie oraz cache pip.

Krok 2 – slim z builderem

Przejście na python:slim zmniejszyło ślad systemu operacyjnego, ale narzędzia budowania pozostały. Dodanie etapu builder pozwoliło na zainstalowanie, skompilowanie i usunięcie gcc, make oraz innych zależności wymaganych podczas kompilacji. Końcowy etap użył COPY --from=builder, aby pobrać tylko skompilowane pliki wheel i pliki środowiska uruchomieniowego, co zmniejszyło rozmiar do 210 MB.

Krok 3 – Alpine wygrywa

Alpine Linux opiera się na musl libc i busybox. Powtórzenie wzorca multi-stage na Alpine pozwoliło uzyskać obraz o rozmiarze 85 MB — co stanowi 93% redukcji względem oryginału. Programista zauważył, że Kubernetes pobrał obraz w kilka sekund, a zadanie CI zakończyło się w 90 sekund.

Wpływ w rzeczywistym świecie

  • Niższe koszty przechowywania – rozmiar składowania w rejestrze maleje.
  • Poprawa bezpieczeństwa – mniej pakietów oznacza mniej podatności CVE do śledzenia. Obraz Alpine zawiera tylko środowisko uruchomieniowe Pythona i kod aplikacji.
  • Szybsze CI – czas trwania potoku spadł z 11 minut do 90 sekund.

Praktyczne wskazówki dotyczące optymalizacji Dockerfile

  • Unikaj tagów :latest; wybieraj warianty :slim lub :alpine, które odpowiadają Twoim potrzebom.
  • Stosuj budowanie wieloetapowe (multi-stage builds): dedykowany etap builder do kompilacji oraz etap runtime, który otrzymuje tylko niezbędne artefakty.
  • Kopiuj selektywnie za pomocą COPY --from=builder /path/to/installed /path/in/final.
  • Kolejność poleceń ma znaczenie: instalacja zależności powinna odbywać się przed kopiowaniem kodu źródłowego; to maksymalizuje wykorzystanie buforowania warstw.
  • Czyść cache jawnie, np. rm -rf /var/lib/apt/lists/* ~/.cache/pip.

Jeśli zdecydujesz się na Alpine, dodaj pakiet nss, gdy Twoja aplikacja będzie miała problemy z rozwiązywaniem nazw DNS. Podczas instalacji za pomocą pip, dodaj /root/.local/bin na początku zmiennej PATH, aby skrypty zainstalowane lokalnie były wykrywane w czasie uruchomienia.

Uwagi

Biblioteka musl libc w Alpine może kolidować z binarnymi plikami wheel skompilowanymi pod glibc, co powoduje błędy w czasie uruchomienia. W takich przypadkach należy przebudować pliki wheel wewnątrz buildera Alpine lub wrócić do obrazu bazowego slim. Dodatkowy pakiet nss to niewielka cena za niezawodność DNS.

Co warto sprawdzić w następnej kolejności

  • Przeskanuj istniejące obrazy w poszukiwaniu dużych warstw, które mogą nadawać się do przepisania na model multi-stage.
  • Monitoruj logi CI pod kątem czasu przesyłania i pobierania, aby wykryć nadmierny rozmiar obrazów.
  • Śledź raporty o podatnościach dla wybranej dystrybucji bazowej.

Lekcja jest jasna: uporządkowany Dockerfile, lekka baza i etap budowania (builder) mogą zmniejszyć obraz Pythona o ponad rząd wielkości, przynosząc wymierne korzyści w szybkości i kosztach bez poświęcania funkcjonalności.