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.
aptlubpip) zajmują miejsce na dysku i domyślnie nigdy nie są czyszczone. - Każda instrukcja
RUNtworzy 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:slimlub: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.
