Docker + Yocto: Jak przestałem się martwić i pokochałem kontenery
Jeśli kiedykolwiek budowałeś obraz Yocto, to wiesz — to nie jest “kliknij i czekaj”. To jest rytuał. Wymaga konkretnej wersji Pythona, odpowiednich pakietów systemowych, właściwego locale, basha (nie zsh, nie fish — bash). I oczywiście wystarczy, że kolega z zespołu ma Ubuntu 24.04 zamiast 22.04, żeby ten sam bitbake produkował zupełnie inne błędy.
Brzmi znajomo?
W tym artykule pokażę, jak Docker rozwiązuje te problemy na przykładzie repozytorium stm32mp1-basic-yocto-setup — środowiska, które używam m.in. jako bazę na moich kursach STM32MP1 Masterclass i Secure Boot & Chain of Trust. Ale to nie jest artykuł o kursach — to jest artykuł o tym, jak żyć wygodniej z Yocto.
Problem: “U mnie działa”
Yocto Project to potężne narzędzie. Potrafi zbudować od zera kompletny system Linux dostosowany do Twojego hardware’u — kernel, rootfs, bootloader, aplikacje. Ale ma jedną dość irytującą cechę: jest niezwykle wrażliwy na środowisko budowania.
Oficjalna dokumentacja Yocto Scarthgap jasno mówi: wspieramy konkretne dystrybucje hosta. Ubuntu 22.04 — tak. Ubuntu 24.04 — jeszcze nie. Fedora 40 — może. Twoja egzotyczna konfiguracja Archa z patchami z AUR — powodzenia.
W praktyce wygląda to tak:
- Inżynier A ma Ubuntu 22.04 — buduje bez problemu.
- Inżynier B zainstalował sobie nowszego Pythona —
bitbakeodmawia współpracy. - Inżynier C jest na Fedorze — połowa pakietów się nazywa inaczej.
- Nowy członek zespołu spędza pierwszy dzień na konfiguracji środowiska zamiast na kodowaniu.
To jest marnowanie czasu i energii. I to jest dokładnie ten rodzaj problemu, do którego Docker został stworzony.
Rozwiązanie: hermetyczne środowisko budowania
Idea jest prosta: zamiast instalować na hoście kilkanaście pakietów i modlić się, żeby wersje się zgadzały, zamykamy całe środowisko w kontenerze. Raz definiujemy, wszędzie działa.
Zobaczmy, jak to wygląda w praktyce. Cały Dockerfile jest krótki i czytelny:
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
# Pakiety wymagane przez Yocto Scarthgap
RUN apt-get update && apt-get install -y \
build-essential chrpath cpio diffstat file gawk gcc git \
python3 python3-git python3-jinja2 python3-pexpect \
python3-pip python3-subunit socat texinfo unzip wget \
xz-utils zstd curl vim sudo lz4 gosu locales \
&& rm -rf /var/lib/apt/lists/*
# Locale — Yocto tego wymaga
RUN locale-gen en_US.UTF-8
ENV LANG=en_US.UTF-8
# Yocto wymaga bash, nie dash
RUN rm /bin/sh && ln -s /bin/bash /bin/sh
# Narzędzie repo do pobierania manifestów ST
RUN curl -o /usr/local/bin/repo \
https://storage.googleapis.com/git-repo-downloads/repo \
&& chmod a+x /usr/local/bin/repo
Tyle. To jest zamrożone w czasie środowisko — Ubuntu 22.04 z dokładnie tymi pakietami, których Yocto oczekuje. Nie obchodzi go, jaki system masz na hoście. Możesz budować na Ubuntu, Fedorze, macOS, a nawet na CI/CD serverze w chmurze. Kontener jest identyczny.
Trzy sprytne triki, które robią różnicę
Okej, Dockerfile to fundament. Ale żeby Docker i Yocto naprawdę dobrze ze sobą współgrały, trzeba rozwiązać kilka kwestii, o których się nie myśli na początku.
1. Dynamiczne mapowanie UID/GID
Yocto generuje mnóstwo plików — kernel, rootfs, paczki, logi. Jeśli te pliki tworzą się z uprawnieniami użytkownika root wewnątrz kontenera, to na hoście masz problem — nie możesz ich usunąć bez sudo, nie możesz ich edytować w IDE.
Rozwiązanie? entrypoint.sh dynamicznie dopasowuje UID/GID użytkownika builder w kontenerze do użytkownika na hoście:
#!/bin/bash
TARGET_UID=${USER_ID:-1000}
TARGET_GID=${GROUP_ID:-1000}
# Zmiana UID/GID w runtime
groupmod -g "$TARGET_GID" -o builder 2>/dev/null || true
usermod -u "$TARGET_UID" -o builder 2>/dev/null || true
# Uruchomienie polecenia jako builder
exec gosu builder "$@"
Efekt? Pliki tworzone w kontenerze mają na hoście Twojego właściciela. ls -la wygląda normalnie. IDE nie protestuje. Życie jest piękne.
A co najważniejsze — ten sam obraz Dockera działa dla każdego użytkownika bez przebudowy. UID/GID jest ustalany w momencie startu kontenera, nie w momencie budowania obrazu.
2. Inteligentna strategia wolumenów
Nie wszystkie dane w Yocto są sobie równe. docker-compose.yml wyraźnie to rozróżnia:
volumes:
# Bind mount — pliki edytowane na hoście
- ./yocto:/home/builder/yocto
- ./custom-layers:/home/builder/custom-layers
- ./config:/home/builder/config:ro
# Named volumes — persystentny cache
- yocto-downloads:/home/builder/shared-downloads
- yocto-sstate-cache:/home/builder/shared-sstate-cache
To jest kluczowy podział:
Bind mounty to pliki, które chcesz widzieć i edytować na hoście — źródła, wyniki budowania, własne warstwy, konfiguracja. Otwierasz je w VS Code, modyfikujesz Device Tree, zapisujesz — zmiany natychmiast są widoczne w kontenerze.
Named volumes to cache, który Docker zarządza sam.
DL_DIR(pobrane archiwa źródłowe) iSSTATE_DIR(shared state cache) przeżywają restart kontenera, przeżywają nawetdocker compose down. Dzięki temu kolejny build nie pobiera ponownie setek megabajtów z internetu i nie rekompiluje tego, co już było skompilowane.
Kto kiedyś przypadkiem usunął sstate-cache i musiał czekać kilka godzin na ponowne budowanie — ten doceni.
3. Profile konfiguracji zamiast ręcznej edycji local.conf
To akurat jest feature specyficzny dla tego repozytorium, nie ogólna praktyka Dockerowa — ale zbyt dobry, żeby go pominąć.
Zamiast ręcznie edytować conf/local.conf (i zapominać co się zmieniło), profile konfiguracji to wersjonowane w Git fragmenty, które build.sh składa automatycznie:
# Bazowy build
./build.sh
# Secure Boot — podpisany TF-A, Chain of Trust
CONFIG=base,secure-boot DISTRO=openstlinux-weston ./build.sh st-image-weston
# Masterclass — Qt, OpenAMP, debugowanie
CONFIG=base,masterclass DISTRO=openstlinux-weston ./build.sh st-image-weston
Pod spodem build.sh generuje plik conf/auto.conf (BitBake wczytuje go automatycznie obok local.conf), łącząc zawartość wybranych profili. Zmienił profil? Wystarczy ponownie uruchomić build — auto.conf się zregeneruje, bez ręcznego czyszczenia.
Chcesz własny profil? Tworzysz plik config/my-profile.conf.inc:
# config/my-profile.conf.inc
IMAGE_INSTALL:append = " my-custom-app"
EXTRA_IMAGE_FEATURES += "debug-tweaks"
I używasz: CONFIG=base,my-profile ./build.sh. To wszystko.
Workflow z lotu ptaka
Cały proces od zera do zbudowanego obrazu Linux wygląda tak:
# 1. Klonowanie repozytorium
git clone <repo-url>
cd stm32mp1-basic-yocto-setup
# 2. Inicjalizacja — pobiera warstwy OpenSTLinux (jednorazowo)
./setup_repo.sh
# 3. Budowanie obrazu
./build.sh
# 4. Gotowe — obrazy na hoście
ls ./yocto/build-*/tmp-glibc/deploy/images/stm32mp1/
Trzy komendy. Zero konfiguracji środowiska. Zero instalowania pakietów na hoście. Zero “u mnie nie działa”.
A jeśli potrzebujesz wejść do kontenera i pogrzebać ręcznie:
./interactive.sh
Dostajesz pełny shell z zamontowanymi wolumenami, cache’em i środowiskiem gotowym do pracy.
Dlaczego to ma znaczenie w praktyce
Powtarzalność
Każdy w zespole buduje z tym samym środowiskiem. Identyczne pakiety, identyczne wersje, identyczne wyniki. Koniec z debugowaniem różnic między maszynami deweloperów.
Onboarding w minutach, nie w godzinach
Nowy członek zespołu? git clone, ./setup_repo.sh, ./build.sh. Tyle. Nie trzeba mu wysyłać 3-stronicowej instrukcji konfiguracji środowiska, której i tak nikt nie aktualizuje. Nie trzeba też pomagać mu przez Teams przez godzinę, bo ma inną wersję Pythona.
CI/CD
Skoro build odbywa się w kontenerze, to przeniesienie go na serwer CI/CD (Jenkins, GitLab CI, GitHub Actions) jest trywialne. Ten sam Dockerfile, te same skrypty, te same wyniki. Automatyczne nightly buildy? Proszę bardzo.
Izolacja
Yocto wymaga specyficznych pakietów i konfiguracji (choćby ten bash zamiast dash). W kontenerze możesz to robić bez zaśmiecania hosta. Twój system pozostaje czysty, a Yocto ma swoje własne, izolowane królestwo.
Wieloprojektowość
Budujesz jeden projekt na Yocto Scarthgap i drugi na Kirkstone? Dwa różne kontenery, dwa różne środowiska, zero konfliktów. Na hoście byłoby to problematyczne — w Dockerze to naturalny sposób pracy.
Własne warstwy — edycja na hoście, build w kontenerze
Jednym z najciekawszych aspektów tego setupu jest to, że warstwy Yocto edytujesz normalnie na hoście — w swoim ulubionym IDE — a kontener je automatycznie widzi i rejestruje.
Katalog custom-layers/ jest bind-mountem. Wszystko, co tam umieścisz i co ma poprawny conf/layer.conf, zostanie automatycznie wykryte przez build.sh i dodane do bblayers.conf. Nie trzeba nic konfigurować ręcznie.
Przykładowo, żeby zmodyfikować Device Tree (typowy scenariusz przy pracy z koprocesorami STM32MP1):
- Edytujesz plik
.dtswcustom-layers/meta-training/recipes-kernel/linux/files/— w VS Code, na hoście. - Uruchamiasz
./build.sh— kontener widzi zmianę i buduje. - Wynikowy obraz jest na hoście w
./yocto/build-*/tmp-glibc/deploy/images/stm32mp1/.
Bez kopiowania plików, bez docker cp, bez montowania czego nie trzeba. Po prostu działa.
Kilka słów o wydajności
Wiem, co myślisz: “Docker to narzut, build będzie wolniejszy.”
W przypadku Yocto — praktycznie nie. Dlaczego?
- Yocto jest I/O-bound, nie CPU-bound. Kontener Dockera na Linuksie używa natywnego kernela — nie ma warstwy wirtualizacji. Dostęp do dysku i CPU jest identyczny jak na hoście.
- Named volumes na Linuksie mają natywną wydajność (są zwykłymi katalogami w
/var/lib/docker/volumes/). - Bind mounty również — na Linuksie to bezpośrednie mapowanie, bez narzutu.
Jedyny zauważalny narzut to start kontenera (kilkaset milisekund) i mapowanie UID/GID (sekundy). Przy buildzie trwającym godziny — to szum statystyczny.
Uwaga dla użytkowników macOS: Na macOS Docker działa w lekkiej maszynie wirtualnej, więc wydajność bind mountów jest znacznie niższa. Do buildów Yocto na macOS warto rozważyć użycie named volumes również na workspace, albo budować na zdalnym serwerze Linux.
Podsumowanie: Czy warto?
Tak. Jednoznacznie tak.
Docker nie dodaje tu niepotrzebnej złożoności — wręcz przeciwnie, usuwa złożoność, która i tak istnieje (konfiguracja środowiska, zależności, wersje pakietów). Zamienia ją w deklaratywny, powtarzalny, wersjonowany w Git setup.
Patrząc na to repozytorium:
| Bez Dockera | Z Dockerem |
|---|---|
| Instalacja kilkunastu pakietów | docker compose build (jednorazowo) |
| Konfiguracja locale, bash, repo | Już w Dockerfile |
| Problem z prawami dostępu | Automatyczne mapowanie UID/GID |
Ręczna edycja local.conf | Profile konfiguracji (CONFIG=base,secure-boot) |
| Cache ginie przy reinstalacji | Named volumes przeżywają wszystko |
| “U mnie działa” | U wszystkich działa |
Jeśli pracujesz z Yocto profesjonalnie — czy to w ramach kursu, czy w produkcji — konteneryzacja środowiska to nie luksus. To higiena.
Ten setup jest używany jako baza na kursach STM32MP1 Masterclass oraz Secure Boot & Chain of Trust prowadzonych przez wolosewicz.com. Profile masterclass i secure-boot odpowiadają scenariuszom kursowym — ale samo repozytorium jest uniwersalne i można je dostosować do dowolnego projektu opartego na STM32MP1.
Repozytorium: stm32mp1-basic-yocto-setup na GitLab
