Artykuł przedstawia nowoczesne podejście do budowania obrazów kontenerowych, które pozwala na niemal całkowite wyeliminowanie luk w bezpieczeństwie (CVE). Opisany został proces tworzenia bezpiecznego i zoptymalizowanego obrazu dla narzędzia Grafana, z wykorzystaniem ekosystemu Chainguard Wolfi. Takie podejście stanowi w tej chwili standard w branży finansowej oraz wielkich korporacjach, gdzie nie ma miejsca na kompromisy w kwestii bezpieczeństwa.
Zagadnienia poruszane w tym artykule:
- Problemy wynikające z używania standardowych obrazów Dockerowych.
- Architektura i założenia ekosystemu Chainguard Wolfi.
- Kryptografia jako fundament bezpieczeństwa w dystrybucji obrazów.
- Tworzenie pakietów instalacyjnych dzięki narzędzia Melange.
- Składanie finalnego obrazu OCI przy użyciu narzędzia Apko.
- Praktyczny proces budowy bezpiecznej instancji Grafany krok po kroku.
Dlaczego standardowe pliki Dockerfile bywają problematyczne?
Większość obrazów kontenerowych powstaje w oparciu o popularne instrukcje bazowe, takie jak FROM ubuntu lub FROM alpine, a następnie wykorzystuje polecenia w stylu RUN apt-get install. Takie podejście wiąże się z kilkoma poważnymi wadami.
Po pierwsze, generuje dług technologiczny związany z podatnościami (CVE). Obraz dziedziczy w ten sposób setki bibliotek systemowych, których aplikacja docelowa wcale nie potrzebuje, a które nierzadko posiadają luki bezpieczeństwa. Po drugie, brakuje tu determinizmu. Polecenie aktualizacji pakietów wykonane dzisiaj może pobrać zupełnie inną wersję zależności niż to samo polecenie wykonane za tydzień. Ostatecznie, obrazy niepotrzebnie zwiększają swój rozmiar przez pozostawione pliki tymczasowe (cache), logi czy zbędne narzędzia binarne.
Ekosystem Chainguard Wolfi jako rozwiązanie
Odpowiedzią na powyższe problemy jest przejście na ekosystem Chainguard Wolfi. Opiera się on na wykorzystaniu dwóch głównych narzędzi, które całkowicie zmieniają proces powstawania obrazu.
Pierwszym z nich jest Melange. Służy ono do skompilowania i zbudowania aplikacji jako czystego, wyizolowanego pakietu systemowego z rozszerzeniem .apk. Drugie narzędzie to Apko, które odpowiada za złożenie finalnego obrazu OCI z gotowych już pakietów. Cały proces odbywa się w pełni deklaratywnie, bez użycia instrukcji RUN, COPY czy jakiejkolwiek powłoki systemowej wewnątrz kontenera.
Fundament kryptograficzny: klucze RSA
W opisywanym ekosystemie bezpieczeństwo procesu budowania opiera się na twardej kryptografii. Na samym początku wymagane jest wygenerowanie pary kluczy: prywatnego (np. local-melange.rsa) oraz publicznego (local-melange.rsa.pub).
Narzędzie Melange używa klucza prywatnego, aby cyfrowo podpisać zbudowany pakiet. Daje to gwarancję, iż paczka została przygotowana przez autoryzowane środowisko i nie uległa modyfikacji. Z kolei Apko wykorzystuje klucz publiczny do weryfikacji tego podpisu przed zainstalowaniem pakietu w obrazie. jeżeli podpis się nie zgadza, proces budowy zostaje natychmiast przerwany.
Klucz prywatny należy chronić i nigdy nie umieszczać go w otwartym repozytorium kodu, chyba iż wykorzystywane są mechanizmy szyfrowania (np. jako tzw. secret w systemach CI/CD).
Melange w akcji – deklaratywne budowanie pakietu
Konfiguracja procesu Melange znajduje się w pliku melange.yaml. Narzędzie to nie buduje obrazu kontenera, ale tworzy gotowy do instalacji artefakt.
Proces rozpoczyna się od postawienia środowiska budowania, w którym instalowane są wyłącznie narzędzia niezbędne do kompilacji (np. kompilator Go, narzędzie make, czy git). Zapewnia to absolutną czystość procesu. Po zbudowaniu pakietu, to tymczasowe środowisko jest niszczone, dzięki czemu wspomniane narzędzia nie trafiają do finalnego obrazu produkcyjnego.
W przypadku Grafany proces wymaga specyficznego podejścia. Należy wykorzystać specjalne flagi (tzw. ldflags) podczas kompilacji backendu. Wstrzykują one do pliku binarnego informacje o dokładnej wersji aplikacji, dzięki czemu Grafana rozpoznaje swoją wersję po uruchomieniu. Aby przyspieszyć proces i uniknąć skomplikowanego pobierania zależności frontowych (wymagających środowiska Node.js), stosuje się podejście hybrydowe. Backend jest kompilowany bezpiecznie ze źródeł, natomiast gotowe pliki statyczne frontendu pobierane są ze zweryfikowanego, oficjalnego źródła producenta.
Wskazówka:
Pełne przykłady plików yaml potrzebnych do wdrożenia Grafany w ten sposób można znaleźć w oficjalnym repozytorium projektu.
Apko – błyskawiczne składanie finalnego obrazu OCI
Po zbudowaniu pakietu z rozszerzeniem .apk, do pracy wkracza Apko. Jego konfiguracja opiera się na prostym pliku apko.yaml, stanowiącym jedynie listę składników (wskazanie repozytoriów, kluczy publicznych oraz nazw pakietów).
Takie rozwiązanie oferuje potężne korzyści w zakresie cyberbezpieczeństwa. Apko automatycznie generuje pliki SBOM (Software Bill of Materials). Jest to kompletna i ustrukturyzowana lista wszystkich składników oprogramowania, która jest powszechnie wymagana w nowoczesnych korporacjach podczas audytów. Ponadto w wynikowym obrazie nie ma menedżera pakietów (narzędzie apk jest usuwane). Znacząco utrudnia to ewentualnym atakującym pobieranie do kontenera złośliwego oprogramowania, ponieważ brakuje mechanizmu, który by na to pozwalał.
Proces tworzenia obrazu krok po kroku
Aby samodzielnie zbudować gotowy obraz dla Grafany wolny od podatności, należy wykonać poniższe kroki w odpowiednio przygotowanym katalogu z plikami konfiguracyjnymi.
Krok 1: Wygenerowanie kluczy kryptograficznych Polecenie to wykonuje się jednorazowo w celu zabezpieczenia procesu:
docker run --rm -v "$(pwd):/work" cgr.dev/chainguard/melange keygen local-melange.rsaKrok 2: Zbudowanie paczki .apk Narzędzie Melange pobierze kod, skompiluje go w bezpiecznym kontenerze i podpisze kluczem prywatnym:
docker run --rm --privileged \ -v "$(pwd):/work" \ cgr.dev/chainguard/melange build melange.yaml \ --arch amd64 \ --signing-key local-melange.rsaKrok 3: Złożenie obrazu Dockerowego Apko łączy przygotowaną paczkę z minimalnym systemem operacyjnym Wolfi, pakując całość w format odpowiedni dla Dockera:
docker run --rm -v "$(pwd):/work" \ cgr.dev/chainguard/apko build apko.yaml \ mojagrafana:12.3.2-amd64-wolfi-clean \ grafana-openshift.tar \ --arch amd64Krok 4: Testowanie gotowego rozwiązania Wygenerowane archiwum należy załadować do demona Docker i uruchomić wyizolowaną instancję Grafany:
# Ładowanie obrazu docker load < grafana-openshift.tar # Uruchomienie lokalnego kontenera docker run -d \ --name moja-grafana \ --rm \ --platform linux/amd64 \ -p 3000:3000 \ mojagrafana:12.3.2-amd64-wolfi-cleanPodsumowanie
Zastosowanie ekosystemu Chainguard Wolfi do budowy obrazów kontenerowych pozwala na niemal całkowite wyeliminowanie znanych podatności technologicznych (CVE). Zastąpienie standardowych i proceduralnych plików Dockerfile w pełni deklaratywnym podejściem opartym na narzędziach Melange i Apko daje pełną kontrolę nad zawartością tworzonego obrazu.
Dzięki temu aplikacja zyskuje na bezpieczeństwie, finalny rozmiar kontenera jest znacząco mniejszy, a wygenerowany w procesie plik SBOM bez problemu spełnia rygorystyczne standardy audytowe. Wdrożenie takiej metodyki wymaga zmiany nawyków, jednak w dłuższej perspektywie gwarantuje ono bezpieczne, czyste i w pełni powtarzalne środowisko uruchomieniowe.
Dokładny opis wdrożenia znajduję się w repozytorium:
https://github.com/slowikDevOps/devops-hardcore
W ramach bonusu zachęcamy do sprawdzenia wszystkich branchy. Osoby zajmujące się monitoringiem sklastrowanych środowisk będą usatysfakcjonowane.
Autor

Szymon Słowicki – doświadczony DevOps Engineer z wieloletnim stażem w branży IT, w tej chwili związany z firmą Pentacomp Systemy Informatyczne. Na co dzień zajmuje się automatyzacją procesów dostarczania oprogramowania, zarządzaniem infrastrukturą cloud-native oraz wdrażaniem najwyższych standardów bezpieczeństwa.
Szymon jest postacią świetnie znaną w polskiej społeczności IT, m.in. jako aktywny członek inicjatywy #wKontenerachArmy. Pełnił funkcję członka Rady Programowej podczas Warszawskich Dni Informatyki (WDI) 2024, gdzie z sukcesem odpowiadał za kształt merytoryczny ścieżki Cloud & DevOps. Występował również jako prelegent na festiwalu KubeDevSec, podczas którego na żywo demonstrował, jak w niecałą godzinę zainstalować od zera i udostępnić w internecie w pełni funkcjonalny klaster Kubernetes w środowisku on-premise, wraz z wdrożeniem aplikacji opartej o Vaulta i MetalLB.
Chętnie dzieli się swoją wiedzą i doświadczeniem z osobami stawiającymi pierwsze kroki w branży. Jego gościnny występ w niezwykle popularnym podcaście o tym, jak przebiegała jego droga do roli Junior DevOpsa (od przysłowiowego „gościa od naprawiania komputerów”), stanowi ogromne źródło inspiracji dla wielu początkujących inżynierów.
Prywatnie mąż, tata i dumny właściciel czterech kotów. Prawdziwy pasjonat automatyzacji, który potrafi wdrożyć ją choćby w domowym zaciszu – zbudował oparty na Raspberry Pi karmnik, którym może sterować z dowolnego miejsca na Ziemi.
