Docker w praktyce na Debianie

by Patryk

W poprzednim wpisie omówiłem podstawy teoretyczne konteneryzacji, które moim zdaniem każdy powinien znać. Jeśli jeszcze go nie czytałeś, zacznij tutaj.

Nadszedł więc czas, aby zademonstrować dockera od strony praktycznej. Pokażę Ci Dockera od strony praktycznej – od instalacji, przez pierwsze komendy, aż po wolumeny, mapowanie portów i spakowanie własnej aplikacji Node.js do obrazu.

Do demonstracji używam maszyny wirtualnej z Debianem (interfejs sieciowy w trybie zmostkowanym), żeby wygodnie wejść na kontener z poziomu przeglądarki na hoście. Jeśli nie wiesz, jak przygotować VM, zerknij do artykułu dotyczącego obsługi Virtualboxa.

Instalacja Dockera i potrzebnych zależności


Najbardziej aktualny sposób na instalacje Dockera na Debianie jak i innych systemach znajdziesz na oficjalnej stronie Dockera https://docs.docker.com/engine/install/debian/.

Poniżej masz sprawdzone przeze mnie kroki, które musisz wykonać, aby mieć gotowego do działania Dockera wraz z pluginem Docker Compose.

Usuń stare paczki, które mogą powodować konflikty

sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-doc podman-docker containerd runc | cut -f1)

Skonfiguruj repozytorium apt Dockera

sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
Components: stable
Signed-By: /etc/apt/keyrings/docker.asc
EOF

sudo apt update

Zainstaluj Dockera

sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Sprawdź czy usługa działa (zwykle startuje automatycznie)

sudo systemctl status docker

Jeśli u Ciebie nie startuje automatycznie, wówczas uruchom go ręcznie

sudo systemctl start docker

Uruchomienie pierwszego kontenera


Okej, mając zainstalowanego już Dockera, możesz przejść do uruchomienia pierwszego kontenera. Wyjaśnię Ci, co się dzieje pod maską i pokaże Ci kilka komend, które będziesz wykorzystywał praktycznie codziennie podczas pracy z kontenerami.

Na początek zademonstruję jak uruchomić najbardziej klasyczną aplikację o nazwie hello-world.

Co się dzieje po uruchomieniu tego polecenia?

  • Jeśli nie masz obrazu hello-world lokalnie, Docker go pobierze z repozytoriów (pull),
  • potem go uruchomi jako kontener (run),
  • kontener wypisze komunikat i… zakończy działanie.

Kontenery nie muszą działać cały czas. Często jest tak, że wykona zadanie i się wyłączy.

Sprawdzenie uruchomionych kontenerów

Możesz łatwo podejrzeć, które kontenery aktualnie działają za pomocą polecenia docker ps. Polecenie to jednak nie wyświetli zatrzymanych kontenerów, aby je wyświetlić należy dodać przełącznik -a do polecenia.

Usuwanie kontenerów

Nieużywane kontenery można w łatwy sposób usunąć za pomocą polecenia docker rm, po którym należy wskazać jego nazwę lub ID. Aby sprawdzić identyfikator możesz skorzystać z polecenia, o którym wspomniałem wyżej. Można również usunąć wszystkie wstrzymane kontenery za jednym zamachem za pomocą polecenia docker system prune.

Wejście do shella kontenera

Może zajść potrzeba, że chcemy zajrzeć do systemu kontenera i podejrzeć co się w nim znajduje, np. poleceniem ls. Do shella można się dostać w poniższy sposób. Aby wyjść z shella, należy skorzystać z kombinacji klawiszy ctrl+D.

Uruchamianie kontenerów na określonym porcie


Uruchamiając kontenery warto wiedzieć jak ustawiać porty. Bez wskazania portów aplikacja uruchomi się tylko wewnątrz Dockera, z Twojej przeglądarki na hoście nie wejdziesz na niego po adresie. Dzieje się tak ponieważ aplikacja uruchomiona w kontenerze ma swoje porty wewnątrz kontenera. Twój komputer (host) ich nie widzi, dopóki mu nie powiesz:

„Hej Docker, wystaw ten port na zewnątrz”.

Do tego służy mapowanie portów. Najważniejsze abyś zapamiętał, że:

czyli: port na Twoim komputerze → port w kontenerze.

Przykład działania mapowania portów

Do zademonstrowania mapowania portów wykorzystam popularny serwer www o nazwie Nginx. Uruchomię go w kontenerze na maszynie wirtualnej i spróbuje wejść na niego w przeglądarce na hoście.

docker run -d --name web nginx

Zanim odpalisz przeglądarkę, musisz dowiedzieć się jaki adres IP dostała wirtualna maszyna. Możesz to sprawdzić poleceniem ip a. Pod podanym adresem powinien działać serwer nginx. Na ten moment jednak otrzymasz błąd ze względu na to, że nie zdefiniowano mapowania portu dla hosta.

Po testach zatrzymaj i usuń kontener poniższymi poleceniami. Do zatrzymywania kontenera służy polecenie docker stop. Polecenie to w bezpieczny sposób zatrzymuje kontener. Istnieje również polecenie docker kill, które robi to natychmiast.

Aby naprawić ten problem, wystarczy zmapować port, na którym działa nginx w kontenerze, na przykładowo port 8080 w systemie hosta

docker run -d --name web -p 8080:80 nginx

Teraz wejdź w przeglądarce na adres swojego serwera (u mnie 192.168.0.110). Dopisz także port po dwukropku. Powinna pojawić się strona powitalna serwera nginx.

Przywracanie nieaktywnych kontenerów

Wspominałem o możliwości zatrzymywania kontenerów za pomocą polecenia docker stop. Możemy również przywracać zatrzymany w ten sposób kontenery za pomocą polecenia docker start.

Wolumeny w dockerze


Kontener ma swój własny system plików, ale jest on traktowany jako nietrwały. Gdy usuniesz kontener, usuwasz też jego „dysk”. Jeśli masz jakieś dane generowane i wykorzystywane przez kontenery Dockera to znikają one po usunięciu kontenera. Istnieje jednak sposób na zachowanie tych danych i do tego właśnie służą wolumeny. Omówiłem je już częściowo w artykule teoretycznym, dlatego od razu przejdę do praktyki.

Bind mount

Montowanie typu „bind” mapuje katalog lub plik z systemu plików hosta do kontenera. Umożliwia to bezpośredni dostęp do systemu plików hosta, co czyni je idealnym rozwiązaniem w sytuacjach, w których dane muszą być współdzielone między hostem a kontenerem.

Na początku utwórz plik index.html i wypełnij go dowolnym tekstem.

mkdir -p www/nginx-demo 
echo "<h1>Hej! To jest plik z hosta</h1>" > www/nginx-demo/index.html

Następnie uruchamiamy kontener wskazując folder z hosta i miejsce gdzie ma zostać zamontowany w kontenerze. Parametr ro oznacza read-only. Nginx ma korzystać z plików, nie zmieniać ich. Należy określić również ścieżke hosta i ścieżkę kontenera.

docker run -d --name website -p 8081:80 -v /root/www/nginx-demo:/usr/share/nginx/html:ro nginx

Do przetestowania działania wystarczy zmienić plik na hoście i odświeżyć stronę. Zmiana będzie od razu widoczna.

Jeśli usuniesz kontener i uruchomisz go na nowo, zauważysz, że wciąż będzie miał dostęp do tego katalogu, ponieważ istnieje On poza kontenerem i nie znika wraz z usunięciem kontenera.

Named Volume

Wolumeny nazwane to wolumeny zdefiniowane przez użytkownika, do których można łatwo odwoływać się po nazwie i które można ponownie wykorzystywać w wielu kontenerach. Są one przechowywane w wewnętrznym magazynie woluminów Dockera.

1) Utwórz named volume

docker volume create webdata

2) Zapisz index.html do volume (trik z kontenerem pomocniczym)

Użyjemy lekkiego kontenera (busybox), żeby wrzucić plik do wolumenu:

docker run --rm -v webdata:/data busybox sh -c 'echo "<h1>Strona z DOCKER VOLUME</h1>" > /data/index.html

3) Uruchom Nginx z podpiętym wolumenem

Po wydaniu poniższego polecenia widzimy, że strona działa. Teraz, gdybyś usunął kontener i utworzył go na nowo, strona wyglądałaby tak samo. Wolumen wszak jest poza kontenerem.

Za pomocą polecenia docker volume inspect można sprawdzić wolumen i wyświetlić jego szczegóły. Polecenie docker volume ls zaś możemy wylistować wszystkie wolumeny.

Kiedy stosujemy bind mount, a kiedy named volume?

  • Bind mount wybierz wtedy, gdy pracujesz lokalnie i chcesz szybko edytować pliki na swoim komputerze (np. HTML/CSS/JS, konfiguracje). To najlepsza opcja do developmentu, bo zmiany widzisz od razu po odświeżeniu strony. Minusy: częściej trafiają się problemy z uprawnieniami (szczególnie na Linuxie) i różnice w ścieżkach między systemami.
  • Named volume wybierz wtedy, gdy zależy Ci na trwałości danych i stabilności. Najczęściej używa się go do baz danych (Postgres/MySQL/Mongo), uploadów, cache’y -czyli wszystkiego, co ma przetrwać restart/usunięcie kontenera. Jest bardziej „dockerowe”, bo Docker sam zarządza miejscem na dane, a Ty nie musisz pilnować katalogów na hoście.

Przykład prostej aplikacji z Node.js


Do tej pory pokazywałem podstawy na bazie gotowego obrazu nginx. Teraz zademonstruję Ci jak spakować własną aplikację do obrazu Dockera, żeby uruchamiała się tak samo na każdym komputerze.

Najpierw utwórz poniższą strukturę projektu:

mkdir my-app
cd my-app
touch package.json
touch app.js
touch Dockerfile
touch .dockerignore

Poniżej umieszczam zawartość poszczególnych plików wraz z wyjaśnieniem zawartości. Zacznijmy od pliku package.json.

Plik ten opisuje konfiguracje projektu Node.js. Zawiera nazwę i wersje projektu, a także wskazuję główny plik ze skryptem. W skrócie plik ten mówi między innymi npm-owi jak uruchomić aplikację i jakie biblioteki ma zainstalować. Dzięki temu później w pliku Dockerfile wystarczy, że wpiszemy komendę npm install i npm będział, że ma zainstalować expressa.

Kolejnym plikiem, który warto utworzyć jest app.js. W pliku tym będzie znajdował się kod aplikacji uruchamianej w kontenerze. Nie wchodząc w szczegóły programowania jest to prosty serwer HTTP z Node.js z użyciem Expressa. Kod ten sprawi, że gdy ktoś wejdzie na hoście na adres serwera z portem 3000, wówczas dostanie odpowiedź “Hello from Docker!”.

Teraz najważniejsza część czyli plik Dockerfile. Jak wspominałem w części teoretycznej jest to skrypt będący instrukcją dotyczącą tworzenia obrazu Dockera.

  • FROM określa obraz bazowy, z którego będzie korzystała aplikacja.
  • WORKDIR ustawia katalog roboczy w kontenerze. Od tej pory polecenia typu COPY, RUN działają względem /usr/app.
  • COPY kopiuje pliki z hosta do kontenera (do /usr/app).
  • RUN wykonuje polecenia w kontenerze. W tym przypadku Instaluje zależności z package.json (np. Express).
  • CMD określa polecenie do uruchomienia. Odpala skrypt start z package.json, czyli w tym przypadku node app.js

Ostatnim plikiem w projekcie jest plik .dockerignore. Mówi Dockerowi, których plików/katalogów nie kopiować do obrazu podczas docker build. Dzięki temu build obraziu trwa szybciej. Nie wszystkie pliki znajdują się w projekcie, wypisałem je jednak aby pokazać Ci, które pliki mogą znaleźć się w dockerignore. Plik .env często zawiera sekrety/zmienne środowiskowe, bezpieczniej jest nie kopiować go do obrazu.

Uruchamianie projektu

Przejdź do katalogu projektu a następnie wykonaj poniższe polecenie.

docker build -t my-app:1.0 .

Polecenie to buduje obraz z pliku Dockerfile w bieżącym katalogu. Aby aplikacja była dostępna w przeglądarce należy wykonać poniższe polecenie.

docker run -p 4000:3000 my-app:1.0

Po uruchomieniu kontenera aplikacja będzie dostępna na hoście pod adresem maszyny wirtualnej na porcie 4000.

Przyspieszenie buildów: cache warstw Dockera

Docker buduje obraz „warstwami”. Jeśli jakaś warstwa się nie zmieniła, Docker może ją wziąć z cache i nie robić jej od nowa.

Dlatego:

  • najpierw kopiuj pliki, które zmieniają się rzadko (package.json / package-lock.json),
  • rób npm install,
  • dopiero potem resztę kodu.

✅ Dobrze:

COPY package*.json ./ 
RUN npm install 
COPY . .

❌ Słabo:

COPY . . 
RUN npm install

W tej wersji jeśli zmienisz coś w Dockerfile, to wszystkie kroki poniżej też polecą od nowa.

Docker Compose


W realnych projektach aplikacja rzadko działa sama. Najczęściej potrzebujesz też bazy danych (Postgres/MySQL), cache (Redis) lub gotowej aplikacji (Nextcloud) . Docker Compose działa w oparciu o zestaw deklaracji zapisanych w jednym pliku konfiguracyjnym – najczęściej compose.yaml. Ten plik w formacie YAML opisuje cały „stos” aplikacji jakie mają powstać, jak mają się ze sobą komunikować, jakie porty wystawić i gdzie trzymać dane.

Największa zaleta? Zamiast odpalać kilka długich komend docker run i pilnować kolejności uruchamiania, wystarczy jedna:

docker compose up

Compose zrobi za nas całą „robotę pod maską”: utworzy sieć, uruchomi kontenery, podepnie woluminy, ustawi zmienne środowiskowe i resztę konfiguracji. Dzięki temu nie trzeba kleić własnych skryptów w Bashu ani pamiętać dziesiątek parametrów.

Zademonstruję działanie Compose na przykładzie aplikacji Nextcloud. To jeden z najprostszych, a jednocześnie bardzo praktycznych przykładów użycia Docker Compose: uruchamiamy Nextcloud (web) oraz PostgreSQL (baza danych) jako dwa serwisy.

Struktura projektu

Wystarczą dwa pliki:

.
├── compose.yaml
└── README.md   (opcjonalnie)

Poniżej przykład bazowej konfiguracji. Nextcloud działa na Apache, a baza to Postgres. Dodałem też wolumeny, żeby dane przetrwały restart kontenerów.

services:
  db:
    image: postgres:alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: nextcloud
      POSTGRES_USER: nextcloud
      POSTGRES_PASSWORD: change_me
    volumes:
      - db_data:/var/lib/postgresql/data

  nc:
    image: nextcloud:apache
    restart: unless-stopped
    ports:
      - "80:80"
    depends_on:
      - db
    volumes:
      - nc_data:/var/www/html

volumes:
  db_data:
  nc_data:

o tu jest najważniejsze?

  • ports: "80:80"port 80 kontenera Nextcloud jest mapowany na port 80 hosta, więc wejdziesz w przeglądarce na http://adres_serwera:80.
  • depends_on → Nextcloud wystartuje po bazie.
  • volumes → dane Nextclouda i Postgresa są trwałe.

Uruchomienie projektu

W katalogu projektu odpal poniższe polecenie, które uruchomi wszystkie aplikacje z projektu
docker compose up -d

Po zakończeniu sprawdź wynik polecenia docker ps aby potwierdzić czy kontenery są uruchomione, powinieneś zobaczyć poniższy wynik.

Finalnie wchodząc w przeglądarce na adres serwera na porcie 80 ujrzysz stronę początkową Nextclouda. Przy pierwszym uruchomieniu Nextcloud pokaże kreator instalacji. Wybierz bazę PostgreSQL i podaj dane zgodne z compose.yaml:

  • nazwa bazy: nextcloud
  • host bazy: db
  • użytkownik: nextcloud
  • hasło: change_me

You may also like

Leave a Comment