Poznajmy Dockerfile

Jak pewnie pamiętasz, kontener powstaje na podstawie obrazu, a obraz jest zbudowany na podstawie pliku Dockerfile. Przyjrzyjmy się bliżej temu niezwykłemu plikowi.
Dockerfile to prosty plik tekstowy opisujący za pomocą poleceń, jak ma być zbudowany nasz docelowy nowy obraz.  Jego instrukcje – polecenia,  są interpretowane po kolei przy budowaniu obrazu.

Na dockerhubie mamy udostępnionych multum gotowych obrazów Dockera. Co więcej, możemy podejrzeć ich szczegóły budowy, w tym jak wygląda plik Dockerfile na podstawie którego powstał dany obraz. Na stronie, w sekcji Supported tags and respective Dockerfile links, gdy klikniemy w wybraną wersję to przeniesie nas do githuba i tam będzie gotowy plik. Warto przeglądać pliki  obrazów z których korzystamy ponieważ daje to nam pełniejszą wiedzę, z czego tak naprawdę korzystamy gdy uruchamiamy kontener z obrazu.
I nic nie stoi na przeszkodzie aby wziąć Dockerfile i przerobić go na nasz własny obraz. 

Tu mamy piękny przykład jak zbudowany jest obraz nginxa
https://github.com/nginxinc/docker-nginx/blob/3591b5e431af710432bd4852d9ee26eb19992776/mainline/debian/Dockerfile

Albo tu przykład Dockerfile dla nextcloud
https://github.com/nextcloud/docker/blob/570ac60ed20c5197eb050c8ec55eae5d9c3e58eb/25/apache/Dockerfile

Jak widać, mamy  w nim typowe polecenie Linuxa zaczynające się od poleceń charakterystycznych dla składni pliku Dockerfile. Pisane są wielkimi literami dla czytelności, bo nie są case-sensitive. Omówimy te  najpopularniejsze polecenia pojawiające się.

FROM – tu określany jest obraz na podstawie, którego będziemy wszystko budować. Zwykle każdy obraz powstaje na bazie innego, bazowego obrazu. 

LABEL – po prostu etykieta. Tu możemy wpisać twórcę danego pliku Dockerfile.

EXPOSE – służy do zadeklarowania portu do komunikacji dla aplikacji w kontenerze. Na przykład dla nginxa, który serwuje usługę HTTP będzie to port 80.

EXPOSE 80

Możemy też doprecyzować czy to będzie protokół TCP czy UDP. Jednak jest to tylko informacja da nas i Dockera. Nie wpływa to na konfigurację naszego nginxa. Taki wyeksponowany port również trzeba będzie przekierować później do naszego hosta. Czyli docker run -P i wtedy Doker ustawi wolny port po stronie hosta do portu, który został właśnie zadeklarowany z expose.
Aby zobaczyć wyexposowane porty wydaj polecenie docker port <idKontenera>
A jeśli nie ma zadeklarowanego tu portu (expose)  to tez możemy użyć forwardowania wskazując konkretnie port na hoście i kontenerze docker run -p 8080:90
Jednak zawsze zalecam by używać dla czytelności wszystkich portów które wystawia do komunikacji dana aplikacja w kontenerze.

ENV  – tu deklarujemy zmienne środowiskowe dla kontenera, wraz z ich wartościami. Właściwie tu wstawiamy każdą wartość którą chcemy aby pojawiła się w zmiennych środowiskowych wewnątrz kontenera. A te listujemy poleceniem printenv
Często też  za pomocą tego polecenia przekazujemy parametry dostępu do bazy danych, API, itp. 

Natomiast jeśli potrzebujemy zmienne która może być użyta podczas budowania obrazu to wtedy korzystamy z polecenia ARG. To polecenie często występuje na początku pliku Dockerfile.  Na przykład

ARG VERSION=24.01
FROM ubuntu:${VERSION}

Istnieje też możliwość przekazania tej wartości w samym poleceniu. Na przykład:

docker build -t myubuntu:1.0 --build-arg VERSION=24.01

RUN – magiczna komenda, po której podajemy wszystko to co chcielibyśmy uruchomić, jak polecenia wydawane w terminalu. Kolejne polecenia łączymy tu połączone są znakiem && lub \
Miej na uwadze, że tu uruchamiamy to czego potrzebujemy do zbudowania środowiska aplikacji w kontenerze a nie do uruchomienia samej aplikacji. Tak więc za pomocą RUN instaluje to czego aplikacji wymaga czyli jej zależności. Warto pamiętać tu gdy instalujemy jakiś pakiet to należy dodać parametr -y by nie przerwać instalacji pytaniem o potwierdzenie. Na przykład:

RUN apt-get update; \
apt-get install -y --no-install-recommends \
libzip-dev \
; \

COPY – kopiuje pliki. Jako parametry podajemy miejsce źródłowe i miejsce docelowe. Na przykład:
COPY . /app 

oznacza to, skopiuj wszystko z aktualnego katalogu gdzie jest plik Dockerfile do katalogu /app na kontenerze.

Warto tu wspomnieć, że istnieje bardzo podobne polecenie ADD, która też służy do kopiowania plików z dysku ale oprócz tego potrafi jeszcze kopiować z URL i z archiwów.

WORKDIR – tu ustawimy katalog początkowy. Jeśli uruchomimy kontener to od razu trafiamy do tego katalogu. Często tu jest katalog aplikacji naszej. Na przykład /app

USER – ustawienie użytkownik na którym będą wykonywane procesy wewnątrz kontenera. Jeśli nie zadeklarujemy żadnego użytkownika to będzie użyty domyślnie użytkownik root. Większość kontenerów właśnie działa na użytkowniku root a na przykład skonteneryzowana aplikacja Jenkins działa na użytkowniku jenkins.  Czasem zmiana użytkowania jest używana do podniesienia bezpieczeństwa aby procesy nie działa na roocie choć to też bardzo łatwo obejść. 

ENTRYPOINT jest to celowy proces/aplikacja który ma być uruchomiony w kontenerze. Zalecane jest aby jeden kontener miał jeden główny proces.

CMD jest bardzo podobna do powyższego, jednak to polecenie jest wykorzystywane do podania argumentu do głównego procesu zadeklarowanego w entrypoint . A dodatkowo ten argument można nadpisać podając inne argument bezpośrednio w poleceniu docker run.

Aby stworzyć, zbudować nowy obraz na postawie naszego pliku Dockerfile potrzebujemy wydać polecenie docker build i oznaczyć jakoś nasze nowy obraz czyli otagować -t podając nazwę i po dwukropku wersje. 

Tagowanie obrazów to inaczej nadawanie im odpowiedniej nazwy. Tag składa się z dwóch części nazwa dwukropek i wersja. Co więcej, możemy dodatków otagować już istniejący obraz z tagiem w celu lepszej organizacji. Na przykład nasz aplikacja w aktualnej wersji ma tag apka:2.0 a dodatkowo możemy dodać tag apka:latest

A na koniec podajemy ścieżkę kontekstu i zwykle to jest dokładnie ta sama lokalizacja, w której jesteśmy, więc w Linuksie taka lokalizacje oznaczmy kropka . Przykład:

docker build -t nowuObraz:1.0 .

Nasze obrazy możemy zobaczyć, wylistować poleceniem docker images

Natomiast jeśli  potrzebujemy poprawić bądź zaktualizować nasz Dockerfile to wtedy musimy stworzyć kolejny nowy obraz, bądź jego nowa wersje. Nie ma możliwości aktualizowania już utworzonego obrazu. Zastępujemy go całkowicie bądź i tworzymy nową wersję po prostu. 

Ważna jest też kolejność kolejnych poleceń w Dockerfile. Przede wszystkim te elementy które są stałe należy dać na początek pliku,  w środku, te które mogą się zmienić a na koniec te które zmieniają się najczęściej. Tak więc, znając dany projekt musisz sam się zastanowić i odpowiedzieć sobie co gdzie najlepiej umieścić. Wpływa to bowiem ja rozmiaru i  szybkość budowania obrazu. Te elementy które nie są zmieniane przy kolejnym budowania są brane z cache’a,  dzięki czemu szybciej tworzymy nowy obraz. Co ma szczególnie  znaczenie w złożonych  procesach CI/CD. 

Kiedy zadeklarujemy instalacje pakietu za pomocą apt-get update to ważne jest aby zasnąć potem skasowane pliki bo nie będą nam potrzebne a niepotrzebnie tylko zwiększają rozmiar obrazu. 

W procesach CI/CD budowaniem obrazów i wysyłaniem (pushowaniem)  ich do repozytorium  zajmuje się Jenkins lub inne tego typu narzędzie. 

 

Tags: