Jak zacząć z Dockerem w administracji systemów: praktyczny przewodnik dla początkujących

0
56
Rate this post

Nawigacja:

Po co administratorowi Docker: kontekst i realne zastosowania

Administrator systemów szuka narzędzi, które skracają czas wdrożeń, upraszczają migracje i zmniejszają ilość „specjalnych” konfiguracji na serwerach. Docker dokładnie to daje: aplikacja z zależnościami zamknięta w kontenerze, którą można łatwo przenosić między środowiskami.

Kontenery nie zastępują klasycznej administracji systemami, ale zmieniają jej akcenty. Zamiast instalować ręcznie pakiety, dopieszczać konfiguracje na każdym serwerze, admin przygotowuje obrazy, definiuje usługi w plikach YAML i zarządza nimi jak kodem. Przy większej liczbie środowisk różnica w nakładzie pracy bywa ogromna.

Docker w codziennych zadaniach administratora

Przy typowym środowisku: kilka serwerów WWW, baza danych, parę usług pomocniczych – Docker pozwala zunifikować sposób wdrażania. Ten sam obraz aplikacji uruchamia się na serwerze deweloperskim, testowym i produkcyjnym. Różnią się tylko zmienne środowiskowe, wolumeny i sieci.

Dla admina oznacza to mniej niespodzianek typu „u mnie działa, na serwerze nie”. Minimalizuje się drobne różnice w wersjach bibliotek, konfiguracji PHP, Java, Python, Node. Kontener ma wszystko w środku, host zapewnia tylko kernel i zasoby.

Duża korzyść pojawia się też przy krótkotrwałych zadaniach: trzeba szybko postawić testowego Redis, chwilowe środowisko do analizy logów, sandbox dla nowej aplikacji. Kontener startuje w sekundy i znika tak samo szybko, nie zostawiając śmieci w systemie.

Kontener jako zabawka vs narzędzie produkcyjne

Wielu adminów poznaje Dockera, odpalając pierwsze „hello-world”, Nginxa albo bazę danych na laptopie. To dobre wejście, ale bardzo odległe od produkcji. Kontener testowy można zabić bez konsekwencji. Produkcyjny obsługuje realny ruch i dane, wymaga backupu, monitoringu, aktualizacji i planu awaryjnego.

Różnica polega głównie na procesach wokół: logowaniu, bezpieczeństwie, wersjonowaniu konfiguracji, automatyzacji. Ten sam Nginx w kontenerze na produkcji powinien mieć:

  • zdefiniowane wolumeny na konfigurację i logi,
  • jasny sposób aktualizacji (tagi obrazów, pipeline),
  • monitoring zasobów i logów,
  • ograniczone uprawnienia i zasoby.

Docker dobrze sprawdza się zarówno do „piaskownicy”, jak i do środowisk krytycznych, ale wymaga innego podejścia. Administrator musi wyjść poza poziom „docker run i działa”.

Typowe scenariusze użycia: małe firmy, projekty wewnętrzne, VPS

W małych firmach i na serwerach VPS Docker bywa prostszą alternatywą dla wielu osobnych konfiguracji. Zamiast walczyć z kilkoma wersjami PHP, Python czy Node na jednym systemie, każda aplikacja ma swoją wersję w kontenerze. Host pozostaje stosunkowo czysty.

Na pojedynczym VPS można uruchomić:

  • kontener z Nginx jako reverse proxy,
  • kontener z aplikacją (np. PHP-FPM, Node, Django),
  • kontener z bazą danych (np. PostgreSQL lub MySQL) – lub korzystać z bazy zarządzanej,
  • dodatkowe usługi: Redis, cron, narzędzia do backupu.

W projektach wewnętrznych dużą zaletą jest możliwość przygotowania tego samego stacku dla deweloperów. Zamiast „instaluj to, to i to”, nowa osoba w zespole odpala docker compose i ma kompletne środowisko.

Praktyczny przykład: migracja prostego serwisu WWW do kontenera

Przykład: na serwerze działa prosty serwis WWW w PHP na Apache, instalowanym z repozytoriów dystrybucji. Konfiguracja jest ręcznie modyfikowana, PHP ma niestandardowe rozszerzenia, a update systemu często coś psuje. Celem jest przeniesienie serwisu do kontenera.

Minimalny plan:

  • wybrać oficjalny obraz PHP z Apache (np. php:8.2-apache),
  • przenieść kod aplikacji do katalogu, który później będzie wolumenem,
  • przenieść konfigurację Apache/PHP (np. vhost, php.ini) do osobnych plików,
  • zdefiniować Dockerfile, który doinstaluje potrzebne rozszerzenia PHP,
  • zbudować obraz i uruchomić kontener z mapowaniem portu 80 na hosta.

Na końcu, zamiast dotykać bezpośrednio konfiguracji na hoście, admin aktualizuje kod i obrazy, a system hosta może być prosty i zbliżony do „gołego” serwera.

Granice sensu: kiedy Docker nie jest dobrym wyborem

Docker nie rozwiązuje wszystkiego. Są sytuacje, gdzie dodatkowa warstwa abstrahująca tylko komplikuje sprawę. Przykłady:

  • silne zależności od specyficznego sprzętu lub sterowników,
  • aplikacje typu „desktop” albo takie, które mają głęboko zakorzenione ścieżki w systemie,
  • bardzo proste środowiska: jeden statyczny serwis WWW bez szczególnych wymagań.

Kontenery wymagają też minimalnej kultury DevOps: wersjonowania konfiguracji, zarządzania obrazami, planu aktualizacji. Bez tego wchodzenie w Dockera kończy się chaosem podobnym do „skryptów shellowych w każdym kącie”.

Podstawowe pojęcia Dockera z perspektywy admina

Obraz, kontener, warstwa – techniczne minimum

Obraz Docker to szablon, niezmienny pakiet zawierający system plików i metadane (entrypoint, komenda startowa, zmienne środowiskowe). Obraz jest zwykle budowany krok po kroku w Dockerfile, gdzie każdy krok tworzy nową warstwę.

Kontener to instancja obrazu uruchomiona jako proces na hoście. Ma własną przestrzeń nazw procesów, sieci, systemu plików, ale współdzieli kernel z hostem. Kontener nie jest pełną maszyną wirtualną.

Warstwy to kolejne poziomy systemu plików, sklejone w całość w czasie budowania obrazu. Każda warstwa jest tylko do odczytu; kontener dodaje nad nimi cienką warstwę zapisu. Stąd zmiany w kontenerze znikają po jego usunięciu, jeśli nie użyjemy wolumenów.

Rejestr, Docker Hub i prywatne registry

Obrazy Docker przechowuje się w rejestrach. Najpopularniejszy to publiczny Docker Hub, ale istnieją też inne (GitHub Container Registry, GitLab Container Registry, prywatne registry oparte o Harbor, Sonatype Nexus itp.).

Docker Hub to wygodne źródło oficjalnych obrazów: Nginx, Redis, MySQL, PostgreSQL, Python, Node itd. W środowiskach firmowych często stawia się własny rejestr, aby:

  • mieć kontrolę nad wersjami i dostępem,
  • cache’ować obrazy, by nie ściągać ich ciągle z Internetu,
  • spełnić wymagania bezpieczeństwa i compliance.

Admin musi rozumieć, skąd biorą się obrazy, jak są aktualizowane i kto odpowiada za ich bezpieczeństwo. Ślepe używanie „pierwszego obrazu z listy” z Docker Hub to prosta droga do problemów.

Obraz vs kontener na jednym przykładzie

Załóżmy, że pobierasz obraz: docker pull nginx:stable. W tym momencie na dysku masz szablon. Nic jeszcze nie działa, nie ma procesu, nie ma portu nasłuchującego na hostcie.

Dopiero polecenie:

docker run --name www -p 80:80 nginx:stable

tworzy kontener „www” – to proces Nginxa odpalony w izolacji. Można go zatrzymać (docker stop www), usunąć (docker rm www), ale obraz nginx:stable nadal istnieje i z niego można utworzyć kolejne kontenery.

Mylenie obrazu z kontenerem prowadzi do chaosu przy aktualizacjach. Obraz to wersja aplikacji, kontener to konkretny „egzemplarz” działający tu i teraz.

Stateless vs stateful: co trzymać w kontenerze, a czego nie

Kontener powinien być jak najbliżej stateless – po jego usunięciu nie tracimy ważnych danych. Pliki aplikacji, binaria, konfiguracja w obrazie – to jest OK. Dane biznesowe (baza, pliki użytkowników) powinny leżeć poza kontenerem: w wolumenach, zewnętrznych usługach, systemach plików hosta.

Aplikacje stateful – np. bazy danych – można uruchamiać w kontenerach, ale kluczowe jest poprawne zarządzanie danymi. Same dane muszą trafić do wolumenów lub zewnętrznej bazy (RDS, Cloud SQL). Kontener staje się wtedy nośnikiem binariów i konfiguracji, a nie magazynem danych.

Kontener jako proces z izolacją, nie miniVM

Najzdrowsze podejście administracyjne: myśl o kontenerze jak o procesie z dodatkowymi ograniczeniami (namespaces, cgroups), a nie jak o małej maszynie wirtualnej. Kontener nie ma własnego kernela, nie potrzebuje pełnego init, nie trzeba w środku uruchamiać systemd ani kilku usług na raz.

Typowy kontener powinien uruchamiać jedną główną usługę. Jeśli w środku jest Apache, to nie odpalaj w tym samym kontenerze dodatkowego crona, sysloga i osobnego serwera bazy. Dla admina oznacza to czytelniejsze monitorowanie, logowanie i prostsze aktualizacje.

Przygotowanie środowiska: instalacja i pierwsze komendy

Wybór systemu hosta dla Dockera

Na serwerach produkcyjnych Docker ma najwięcej sensu na Linuksie. Docker Engine współdzieli kernel z hostem – im prostszy i stabilniejszy system, tym mniej problemów. Popularny wybór to Debian lub Ubuntu LTS, ewentualnie CentOS Stream / RHEL / AlmaLinux / Rocky Linux w środowiskach enterprise.

Na stacjach roboczych Windows i macOS Docker wymaga dodatkowej warstwy (WSL2, HyperKit, Colima). To działa dobrze do rozwoju i testowania, ale administracyjnie trzeba pamiętać, że w środku i tak jest Linux. Diagnostyka problemów z siecią czy systemem plików zwykle sprowadza się do tego, co dzieje się w tym linuxowym VM.

Instalacja Docker Engine na Debian/Ubuntu: kroki i pułapki

Bezpieczny schemat instalacji na Debian/Ubuntu:

  1. Odinstaluj stare pakiety typu docker.io, containerd, jeśli są zainstalowane z repozytorium dystrybucji.
  2. Dodaj oficjalne repozytorium Dockera (klucz GPG, wpis do /etc/apt/sources.list.d).
  3. Zainstaluj pakiety: docker-ce, docker-ce-cli, containerd.io.
  4. Sprawdź status usługi: systemctl status docker.

Najczęstsze problemy:

  • stare wersje pakietów z repozytorium systemowego,
  • konflikty portów (np. coś już nasłuchuje na porcie używanym przez przyszłe kontenery),
  • nietypowe ustawienia iptables/ufw, blokujące ruch sieciowy do kontenerów.

Warto od początku korzystać z dokumentacji Dockera dla konkretnej dystrybucji. Kopiowanie losowych skryptów instalacyjnych z blogów potrafi skończyć się dziwnymi konfiguracjami sieci lub nieczytelnym sources.list.

Grupa docker, root i uprawnienia

Domyślnie Docker wymaga uprawnień roota. Można go używać przez sudo docker ... lub dodać użytkownika do grupy docker. Po dodaniu użytkownika do tej grupy, komendy Dockera można wywoływać bez sudo, ale ceną jest to, że każdy członek grupy docker ma w praktyce uprawnienia zbliżone do roota.

Z punktu widzenia bezpieczeństwa graczem jest tutaj demon dockerd. Możliwość sterowania nim pozwala m.in. podmontować system plików hosta do kontenera i zrobić w nim dowolne operacje. Dlatego grupa docker powinna być traktowana jak „półoficjalny root”.

Na serwerach produkcyjnych często zostawia się użycie Dockera przez sudo, ograniczając liczbę osób z dostępem. Dobrą praktyką jest też centralny system logowania poleceń (np. przez sudo, journald, syslog), żeby dało się odtworzyć, kto co uruchamiał.

Pierwsze komendy administracyjne Dockera

Podstawowy zestaw poleceń, który admin powinien mieć w palcach:

  • docker version – wersja klienta i serwera,
  • docker info – informacje o środowisku (ilość kontenerów, obrazów, użyty driver storage, sieć),
  • docker ps / docker ps -a – lista działających i wszystkich kontenerów,
  • docker images – lista dostępnych obrazów,
  • docker logs – logi z pojedynczego kontenera.

Te kilka komend pozwala szybko zorientować się, co dzieje się na serwerze: ile usług działa w kontenerach, czy są jakieś stare, zatrzymane kontenery, jak wygląda obciążenie systemu i zużycie miejsca na dysku przez obrazy.

Prosty test: nginx / busybox i logi

Dla testu środowiska warto zrobić dwa krótkie eksperymenty.

1. Uruchomienie Nginxa:

docker run --name test-nginx -p 8080:80 -d nginx:stable
curl http://localhost:8080
docker logs test-nginx
docker stop test-nginx
docker rm test-nginx

Po uruchomieniu kontenera sprawdzasz, czy port działa (curl lub przeglądarka), a potem zaglądasz do logów. W logach zobaczysz standardowe access logi Nginxa, co pokazuje, że stdout/stderr kontenera spina się z mechanizmem logowania Dockera.

2. Krótki kontener narzędziowy busybox:

Dla osób, które dopiero wchodzą w świat administracji, Docker jest dobrym ćwiczeniem z myślenia o usługach jako oddzielnych blokach. Uspójnia podejście do konfiguracji również w kontekście tematów takich jak więcej o informatyka czy zarządzanie licencjami, backupem i siecią.

docker run --rm -it busybox sh
# w środku:
uname -a
ls /
ping 8.8.8.8

Taki jednorazowy kontener jest dobry do szybkiego debugowania sieci lub systemu plików z perspektywy innego namespaca. Po wyjściu z powłoki kontener znika (flaga --rm), więc nie zostawia śmieci.

Po kilku takich prostych próbach Docker przestaje być „magiczny”, a zaczyna wyglądać jak zestaw przewidywalnych narzędzi, które da się wciągnąć w normalną administrację serwerami. Od tego miejsca łatwiej przejść do zarządzania danymi, siecią i bardziej złożonymi usługami w kontenerach.

Informatyk porządkuje poplątane kable przy biurku w biurze
Źródło: Pexels | Autor: cottonbro studio

Obrazy Docker: skąd je brać i jak nimi zarządzać

Dobór obrazów bazowych dla usług

Dla admina obraz bazowy to decyzja utrzymaniowa, nie „techniczna ciekawostka”. Od niej zależy rozmiar, bezpieczeństwo i wygoda debugowania.

Typowe warianty oficjalnych obrazów:

  • debian / ubuntu – wygodne do debugowania (pełniejszy system, apt), większe,
  • alpine – małe, szybkie w pobieraniu, ale z musl i mniejszym zestawem narzędzi,
  • specjalizowane (np. python:3.12-slim, node:20-alpine) – kompromis między wagą a wygodą.

Na serwerach, gdzie liczy się przepustowość sieci i miejsce na dysku, sensowne są obrazy „slim” lub alpine. Tam, gdzie priorytetem jest łatwy troubleshooting, lepszy bywa Debian/Ubuntu z bash, iproute2, curl w środku.

Tagi: latest, wersje i polityka aktualizacji

Tag latest bywa wygodny, ale dla admina to mina. Oznacza „domyślny”, a nie „stabilny”.

Bezpieczniejsza praktyka:

  • w środowisku produkcyjnym używać tagów wersji (np. nginx:1.24 albo nawet 1.24.0),
  • testować nowe wersje w preprodzie, a dopiero potem podmieniać tag w produkcji,
  • prowadzić prostą tabelę: usługa → obraz → wersja, choćby w README w repo.

Jeśli zespół developerski buduje własne obrazy, dobrze narzucić minimalną konwencję tagów, np. aplikacja:1.3.4 oraz alias aplikacja:1.3 do mniej precyzyjnego wyboru.

Podstawowe komendy do pracy z obrazami

Do codziennej pracy zwykle wystarcza kilka poleceń:

# pobranie obrazu
docker pull nginx:stable

# lista obrazów
docker images

# szczegóły obrazu (warstwy, rozmiary)
docker inspect nginx:stable

# usunięcie nieużywanego obrazu
docker rmi nginx:stable

Przy większej liczbie usług obrazy szybko zjadają miejsce. Wtedy wchodzi docker image prune, ale z rozwagą – najpierw sprawdź, czy jakieś zatrzymane kontenery nie używają „starych” obrazów do ewentualnego rollbacku.

Budowanie własnych obrazów z Dockerfile

Prędzej czy później trzeba przejść z „gotowych obrazów” na swoje. Tu wchodzi Dockerfile.

Minimalny przykład:

FROM nginx:stable
COPY default.conf /etc/nginx/conf.d/default.conf

Budowanie i tagowanie:

docker build -t moja-firma/nginx:1.0 .

Przy własnych obrazach istotne są trzy zasady:

  • czytelny, prosty Dockerfile – jedna główna usługa, bez „kuchni świata” w środku,
  • cache warstw – łączenie komend RUN w rozsądny sposób, aby przy zmianie konfiguracji nie przebudowywać wszystkiego od zera,
  • minimalna liczba pakietów – tylko to, co potrzebne do działania i utrzymania.

Praca z prywatnym rejestrem

W firmach standardem jest własny registry. Z administacyjnego punktu widzenia ważne są:

  • autoryzacja (login, tokeny, integracja z LDAP/SSO),
  • retencja obrazów (usuwanie starych tagów, cleanup),
  • mirror do Docker Hub/ innych rejestrów w razie awarii internetu lub limitów.

Logowanie do rejestru po stronie hosta:

docker login registry.moja-firma.local
docker pull registry.moja-firma.local/aplikacja:1.3.4

W praktyce przydaje się prosty proces: nowy obraz → skan bezpieczeństwa → test → dopiero potem „promocja” do rejestru produkcyjnego.

Pierwsze kontenery: uruchamianie, zatrzymywanie, debugowanie

Tryby uruchamiania: interaktywnie i w tle

Do testów wygodne jest uruchamianie interaktywne:

docker run --rm -it alpine sh

W produkcji większość usług chodzi „w tle”:

docker run -d --name www -p 80:80 nginx:stable

Flaga -d odłącza proces od terminala, ale logi nadal można czytać przez docker logs. Zmiana sposobu uruchamiania NIC nie zmienia w samym kontenerze – to tylko kwestia, jak klient dockera zarządza połączeniem.

Mapowanie portów i podstawowe wzorce

Standardowy schemat wprost na hosta:

docker run -d --name redis -p 6379:6379 redis:7

Dla kilku usług na jednym hoście częściej mapuje się porty na wyższe zakresy lub używa reverse proxy (np. Nginx/Traefik) na porcie 80/443, które kieruje ruch do sieci dockera:

  • -p 8080:80 dla wewnętrznego panelu,
  • -p 127.0.0.1:3306:3306 dla bazy dostępnej tylko lokalnie (binding do localhost).

Przy usługach bazodanowych pierwszą obroną jest ograniczenie adresu nasłuchu do 127.0.0.1 lub sieci VPN, zamiast wystawiania ich na światy porty.

Zatrzymywanie i usuwanie kontenerów bez bałaganu

Standardowy cykl życia:

docker stop www
docker rm www

Do jednorazowych testów opłaca się używać --rm, aby kontener był sprzątany automatycznie po zakończeniu:

docker run --rm -it alpine sh

Przy dłużej działających usługach lepiej świadomie zarządzać rm – zatrzymany kontener bywa przydatny jako źródło logów lub plików do analizy po awarii.

Podgląd i filtrowanie kontenerów

Podstawowe listowanie:

docker ps          # tylko działające
docker ps -a       # wszystkie, łącznie z zatrzymanymi

Z filtrem po nazwie/obrazie:

docker ps -a --filter "name=nginx"
docker ps --filter "ancestor=nginx:stable"

W większych środowiskach bez filtrów robi się chaos. Nazwy kontenerów powinny mieć prostą konwencję, np. www-prod-1, www-preprod-1, żeby docker ps | grep prod faktycznie miało sens.

Podstawowe techniki debugowania kontenera

Pierwsze narzędzia to logi i exec:

# logi z kontenera
docker logs www
docker logs -f www     # „tail -f”

# wejście do działającego kontenera
docker exec -it www sh

Przy obrazach opartych o Debiana/Ubuntu w środku zwykle jest bash. W alpine – sh. Wejście do kontenera to czasem jedyny szybki sposób, by sprawdzić np. pliki konfiguracyjne wygenerowane dynamicznie przy starcie.

Dla problemów z siecią lub DNS dobrze sprawdza się krótka sesja w narzędziowym kontenerze w tej samej sieci dockerowej, np.:

docker run --rm -it --network container:www nicolaka/netshoot

To odpala „diagnostykę” w tym samym namespaces sieci, co kontener www, bez modyfikowania go.

Jeśli interesują Cię konkrety i przykłady, rzuć okiem na: Dokumenty licencyjne, których potrzebujesz: faktury, klucze, COA i umowy.

Przechowywanie danych: wolumeny, bind mounty, backup

Czemu dane w kontenerze to zły pomysł

Dane zapisane „w środku” kontenera znikają z chwilą jego usunięcia. Dla logów tymczasowych czy cache – w porządku. Dla bazy czy plików użytkowników – katastrofa.

Bez wolumenów każda aktualizacja obrazu (czyli stworzenie nowego kontenera) oznacza nową, pustą przestrzeń danych. Stąd pierwsze zadanie admina przy wdrażaniu aplikacji stateful: wydzielenie miejsca na dane poza warstwą kontenera.

Wolumeny nazwane: podstawowy mechanizm Dockera

Wolumen nazwany tworzy Docker i trzyma go zwykle w /var/lib/docker/volumes (zależnie od konfiguracji).

# tworzenie wolumenu
docker volume create db-data

# użycie w kontenerze
docker run -d 
  --name db 
  -v db-data:/var/lib/postgresql/data 
  postgres:16

Wolumen jest niezależny od cyklu życia kontenera. Można bazę zaktualizować, tworząc nowy kontener z tym samym wolumenem. Dla admina kluczowy jest mapping: jaki wolumen odpowiada jakiej usłudze.

Bind mount: bezpośrednie podpięcie katalogu z hosta

Bind mount wiąże katalog/plik z hosta do kontenera:

docker run -d 
  --name www 
  -v /srv/www/config/nginx.conf:/etc/nginx/nginx.conf:ro 
  -p 80:80 
  nginx:stable

Plusy: łatwy podgląd i edycja plików bez dotykania Dockera. Minusy: zależność od konkretnej struktury katalogów na hoście, konieczność dbania o prawa dostępu i SELinux/AppArmor.

Dobry schemat to połączenie: kod/konfiguracje przez bind mounty (bo łatwiej je wersjonować w git), dane aplikacyjne przez wolumeny nazwane (bo są „przenośne” między hostami z tym samym storage z tyłu).

Uprawnienia i właściciele plików

Klasyczny problem: baza w kontenerze nie startuje, bo nie może zapisać do katalogu z hosta.

Typowe źródła problemu:

  • UID/GID wewnątrz kontenera nie zgadza się z właścicielem katalogu na hoście,
  • SELinux blokuje dostęp (szczególnie na RHEL/Alma/Rocky),
  • podpięte NFS/Gluster/ceph z restrykcyjną konfiguracją.

Przy bind mountach pomaga prosta zasada: przed odpaleniem kontenera utworzyć katalog z odpowiednim UID/GID (czasem trzeba podejrzeć dokumentację obrazu, jakiego użytkownika używa). Na systemach z SELinux – używać kontekstu :z lub :Z przy wolumenie, jeśli to zgodne z polityką bezpieczeństwa.

Backup danych z wolumenów

Wolumeny również muszą być objęte backupem. Jest kilka strategii.

Najprostsza – snapshoty filesystemu/VM (LVM, ZFS, ceph, snapshoty cloudowe). Wtedy Backup jest „pod spodem”, Docker nawet o nim nie wie.

Drugie podejście – backup na poziomie kontenera:

# przykład prostego dumpa bazy z użyciem wolumenu
docker run --rm 
  --volumes-from db 
  -v /backup:/backup 
  postgres:16 
  bash -c "pg_dump -h db -U postgres -F c -b -v -f /backup/db.dump"

W praktyce sensowne jest powiązanie backupu z orkiestracją (cron na hoście, system backupowy, narzędzie typu restic/borg), ale niezależnie od metody admin musi mieć odpowiedź na pytanie: skąd i jak odtwarzamy dane dla każdego kluczowego kontenera.

Dane tymczasowe: tmpfs i cache

Dla katalogów tymczasowych lub cache można użyć tmpfs zamiast dysku:

docker run -d 
  --name app 
  --tmpfs /tmp:rw,size=64m 
  moja-firma/app:1.0

To odciąża dysk i przyspiesza operacje IO, ale po restarcie kontenera wszystko z /tmp znika. Dobre do /tmp, buforów, socketów, nie dla danych trwale potrzebnych aplikacji.

Mężczyzna w czarnej bluzie przy komputerze analizuje bezpieczeństwo systemu
Źródło: Pexels | Autor: Tima Miroshnichenko

Sieci w Dockerze: od podstaw do typowych konfiguracji

Domyślna sieć bridge

Po instalacji Dockera powstaje sieć bridge. Kontenery domyślnie lądują właśnie tam, chyba że podasz inną sieć.

Sprawdzenie podstaw:

docker network ls
docker network inspect bridge

Kontenery w sieci bridge widzą się przez adresy IP, ale bez ręcznej konfiguracji nie mają sensownych nazw DNS (poza pewnymi wyjątkami przy użyciu --link, którego lepiej już nie ruszać). Do bardziej czytelnych konfiguracji sprawdza się sieć własna.

Własne sieci bridge i DNS między kontenerami

Tworzenie swojej sieci:

docker network create app-net

Uruchomienie usług w tej sieci:

docker run -d --name db --network app-net postgres:16
docker run -d --name api --network app-net moja-firma/api:1.0

W tej konfiguracji kontener api może łączyć się z db po hostname db na porcie 5432. DNS zapewnia sam Docker, nie trzeba pamiętać IP.

Publikacja portów a sieć wewnętrzna

Wewnętrzne usługi (bazy, kolejki) zazwyczaj nie muszą wystawiać portów na hosta. Wystarczy sieć dockerowa wspólna z aplikacją.

Jeśli baza i aplikacja są w tej samej sieci, ale sam host nie potrzebuje bezpośredniego dostępu, portu nie wystawiaj w ogóle. Mniej otwartych portów to prostsze reguły firewalli i mniejsza powierzchnia ataku.

Joby administracyjne (backup, migracje, jednorazowe skrypty) wygodnie uruchamiać jako dodatkowe kontenery w tej samej sieci. Przykładowo: narzędzie do migracji łączy się z db po DNS-ie kontenera, a nie po IP z hosta. Znika problem „co się stanie po restarcie, gdy zmieni się adres”.

Przy aplikacjach webowych typowy wzorzec to: baza i cache tylko w sieci wewnętrznej, backend w tej samej sieci plus publikacja HTTP/HTTPS na hosta, ewentualnie osobny reverse proxy przed tym wszystkim. Host widzi tylko te porty, które faktycznie muszą być dostępne z zewnątrz.

Łącząc to z poprzednimi sekcjami: kontenery traktuj jak procesy, nie jak pełne serwery; dane trzymaj poza nimi, w wolumenach lub na hostach; sieć buduj w oparciu o proste, powtarzalne wzorce. Z taką bazą dalsza automatyzacja (Compose, orkiestratory, CI/CD) staje się naturalnym kolejnym krokiem, a nie skokiem na głęboką wodę.

Sieć host i macvlan: kiedy kontener ma być „pełnoprawnym” członkiem sieci

Czasem kontener ma zachowywać się jak osobna maszyna w sieci LAN – z własnym adresem IP z tej samej puli co host. Typowe dla starszych aplikacji, licencji „per IP” albo integracji z siecią, gdzie firewall czy monitoring opierają się o konkretne adresy.

Tryb --network host po prostu dzieli stack sieciowy z hostem:

docker run -d 
  --name prometheus 
  --network host 
  prom/prometheus

Tu nie ma izolacji portów: wszystko, co słucha w kontenerze na porcie 9090, widoczne jest na hoście na 0.0.0.0:9090. Proste, ale zdejmuje istotną warstwę oddzielenia między usługą a systemem.

Dla osobnego adresu IP przydaje się macvlan, zwykle na serwerach bare-metal:

docker network create -d macvlan 
  --subnet=192.168.10.0/24 
  --gateway=192.168.10.1 
  -o parent=eth0 
  macvlan-net

docker run -d 
  --name legacy-app 
  --network macvlan-net 
  --ip 192.168.10.50 
  moja-firma/legacy-app:1.0

Kontener jest wtedy widoczny w sieci jak osobny host. Trzeba tylko uwzględnić to w switchach, VLAN-ach, politykach DHCP i monitoringu – od strony sieciowej niczym się nie wyróżnia.

Łączenie się z usługami na hoście

Kontener często musi gadać z usługą działającą „po staremu” na hoście: baza, agent backupu, serwer syslog. Nie zawsze wygodnie jest wystawiać to na zewnątrz tylko po to, by użył tego kontener.

Na Linuksie adres hosta dostępny z kontenera to zwykle adres bridge, np. 172.17.0.1. Można go podejrzeć przez ip addr na hoście i wpisać w konfigurację aplikacji. To jednak słaby pomysł na większą skalę.

Lepsze podejście to dedykowany DNS lub alias host.docker.internal (natywny na Windows/macOS, na Linuksie można to odtworzyć lokalnym DNS lub wpisem w /etc/hosts w obrazie). Daje to stabilny „cel” niezależny od tego, jaki adres ma bridge w danej instalacji.

Docker Compose jako narzędzie do zarządzania usługami

Dlaczego Compose ułatwia życie adminowi

Pojedynczy kontener da się ogarnąć z linii komend. Trzy powiązane usługi z kilkoma wolumenami i siecią – nadal jakoś. Dziesięć kontenerów z różnymi portami i zmiennymi środowiskowymi zaczyna być loterią błędów.

Plik docker-compose.yml staje się punktem odniesienia: opisuje całą usługę w jednym miejscu. W praktyce to odpowiednik lekkiego „manifestu wdrożeniowego”, który można wersjonować w git.

Podstawowy plik docker-compose.yml

Przykład prostej aplikacji web + baza:

version: "3.9"

services:
  db:
    image: postgres:16
    container_name: db
    environment:
      POSTGRES_PASSWORD: example
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      - app-net

  web:
    image: nginx:stable
    container_name: web
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - db
    networks:
      - app-net

volumes:
  db-data:

networks:
  app-net:

Podstawowe operacje:

docker compose up -d        # start całego zestawu
docker compose ps           # lista kontenerów z tego projektu
docker compose logs -f web  # logi konkretnej usługi
docker compose down         # zatrzymanie i usunięcie kontenerów

Plik Compose staje się jednocześnie dokumentacją: gdzie są dane, jak nazywają się sieci, jakie są porty. Przy awarii albo migracji na nowy serwer nie trzeba odtwarzać wszystkiego z pamięci.

Konfiguracja przez zmienne środowiskowe i pliki .env

Trzymanie haseł i adresów w samym docker-compose.yml szybko prowadzi do bałaganu, szczególnie gdy ten plik ląduje w repozytorium.

Minimalne rozdzielenie konfiguracji to plik .env obok Compose:

# .env
POSTGRES_PASSWORD=super-tajne-haslo
WEB_PORT=8080
# docker-compose.yml (fragment)
services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

  web:
    image: nginx:stable
    ports:
      - "${WEB_PORT}:80"

Plik .env można lokalnie nadpisywać per środowisko (dev/preprod/prod), a sam Compose zostaje identyczny. Dla danych krytycznych (klucze, tokeny) lepsze są tajne magazyny (Vault, AWS/GCP/Azure Secrets), ale to już integracja z zewnętrznymi narzędziami.

Skalowanie usług i osobne projekty

Compose pozwala na podstawowe skalowanie poziome:

docker compose up -d --scale web=3

Powstanie kilka kontenerów web, a ruch można rozłożyć przez reverse proxy albo load balancer przed hostem. Działa to dobrze przy stateless backendach, które korzystają z jednej bazy i wspólnych wolumenów na dane współdzielone.

Dla porządku warto oddzielać projekty Compose nazwami lub katalogami. Osobny katalog na każdą aplikację, wewnątrz docker-compose.yml oraz .env. Na hoście wszystko można spiąć prostymi jednostkami systemd, które uruchamiają docker compose up -d przy starcie systemu.

Bezpieczeństwo Dockera na serwerze

Root a Docker: kto ma dostęp do demona

Socket Dockera (/var/run/docker.sock) to w praktyce pełny dostęp administracyjny do hosta. Użytkownik, który może wykonywać docker run, może też eskalować uprawnienia do roota.

Stąd parę praktycznych zasad:

  • używaj grupy docker tylko dla zaufanych adminów,
  • nie wystawiaj API Dockera na sieć (-H tcp://0.0.0.0:2375) bez solidnego uwierzytelniania,
  • monitoruj, kto wchodzi do grupy docker (auditd, logi systemowe).

W środowiskach z wieloma zespołami rozsądniej czasem jest postawić registry i udostępniać tylko mechanizmy CI/CD, zamiast dawać wszystkim ssh na hosta z Dockerem.

Na koniec warto zerknąć również na: Wireshark od zera: jak czytać pakiety i namierzyć opóźnienia w sieci — to dobre domknięcie tematu.

Uruchamianie kontenerów z ograniczonymi uprawnieniami

Większość oficjalnych obrazów potrafi działać jako nie-root. Często wymaga to jedynie ustawienia odpowiedniego UID/GID albo skorzystania z gotowego użytkownika zdefiniowanego w obrazie.

docker run -d 
  --name app 
  --user 1000:1000 
  -v app-data:/var/lib/app 
  moja-firma/app:1.0

Do tego kilka flag, które przydają się szczególnie na serwerach publicznych:

  • --read-only – system plików kontenera tylko-do-odczytu, z wyjątkami w postaci wolumenów,
  • --cap-drop ALL + ewentualnie --cap-add dla koniecznych capabilities,
  • --pids-limit i --memory – ograniczenia zużycia zasobów.
docker run -d 
  --name api 
  --read-only 
  --pids-limit=256 
  --memory=512m 
  --cap-drop=ALL 
  --cap-add=NET_BIND_SERVICE 
  moja-firma/api:1.2

Takie ustawienia nie naprawią dziurawej aplikacji, ale utrudnią jej wykorzystanie do przejęcia hosta lub „zarżnięcia” go zasobami.

Profile AppArmor/SELinux i ograniczenia systemowe

Na dystrybucjach z AppArmor/SELinux da się dodatkowo „uszczelnić” kontenery. Docker ma domyślne profile, które w wielu przypadkach wystarczają, ale przy krytycznych usługach można dołożyć własne polityki.

Przykład użycia niestandardowego profilu AppArmor:

docker run -d 
  --name secure-app 
  --security-opt apparmor=profile-secure-app 
  moja-firma/app:2.0

Podobnie z SELinux: dopuszczalne jest wymuszanie konkretnych kontekstów przy wolumenach (:Z) lub użycie predefiniowanych typów. Dla admina ważne jest, żeby ten poziom nie był „magiczny”: brak uprawnień z powodu SELinux lub AppArmor jest najczęstszą przyczyną „działało w dev, nie działa w prod”.

Aktualizacje obrazów i podatności

Obraz kontenera starzeje się tak samo jak goły system. Pakiety w środku mają swoje CVE, biblioteki wymagają łat, a stare runtime’y Javy czy Pythona potrafią ciągnąć za sobą całe łańcuchy podatności.

Minimalne minimum:

  • regularnie przebudowywać własne obrazy (np. raz w tygodniu) z aktualną bazą,
  • śledzić zmiany w oficjalnych obrazach (tagi alpine, debian, nginx itd.),
  • skanować registry narzędziem typu Trivy, Clair, Grype albo funkcjami wbudowanymi w rejestr chmurowy.

Same skanery nie wystarczą. Część podatności będzie akceptowalna, część wymusi szybszy roll-out nowego obrazu. Bez takiego „radaru” admin dowiaduje się o problemie z serwerem aplikacji z alertu z zewnątrz, nie z własnych narzędzi.

Aktualizacje, roll-out, rollback: jak utrzymać stabilność

Nieaktualizowanie w miejscu: nowy kontener zamiast „patchowania”

Największa przewaga kontenerów nad tradycyjną administracją to prosty cykl: budowa nowego obrazu, uruchomienie nowego kontenera, przełączenie ruchu, usunięcie starego.

Aktualizowanie wewnątrz działającego kontenera (apt upgrade, wget nowej aplikacji) zaciera granicę między wersjami i uniemożliwia powtarzalne wdrożenia. Każda zmiana powinna kończyć się nowym tagiem obrazu.

Przykład „ręcznego” podejścia dla pojedynczej usługi:

# nowa wersja aplikacji
docker pull moja-firma/app:1.1

# start nowego kontenera na innym porcie
docker run -d 
  --name app-v1-1 
  -p 8081:80 
  -v app-data:/var/lib/app 
  moja-firma/app:1.1

# test na porcie 8081, potem przełączenie ruchu w proxy
# a na końcu usunięcie starego kontenera
docker stop app-v1-0 && docker rm app-v1-0

To proste blue/green nawet bez narzędzi orkiestracyjnych. Na hostach, gdzie ruch idzie przez nginx/HAProxy, przełączasz backend z jednego portu/hosta na drugi i dopiero wtedy „sprzątasz” starą wersję.

Compose i aktualizacja całej aplikacji

Przy Compose schemat jest podobny, tylko wszystko opisane jest deklaratywnie. Zmiana wersji sprowadza się do podmiany taga obrazu w docker-compose.yml lub pliku .env.

# zmiana wersji w docker-compose.yml
services:
  app:
    image: moja-firma/app:1.2
# pobranie nowych obrazów i restart
docker compose pull
docker compose up -d

Compose sam wykryje, że obraz się zmienił, i zrestartuje tylko te usługi, które tego wymagają. Dobrą praktyką jest trzymanie „kandydata do prod” jako osobnego brancha lub katalogu i testowanie go w środowisku preprod z tym samym plikiem Compose.

Rollback: szybki powrót do poprzedniej wersji

Nie każda aktualizacja się powiedzie. Dlatego tak ważne są:

  • oznaczone tagi obrazów (nie latest, tylko np. 1.0.3),
  • konfiguracja w repozytorium, z historią zmian,
  • kopie zapasowe danych zgodne z wersją aplikacji (szczególnie przy migracjach schematu bazy).

Rollback w Compose to często podmiana taga na poprzedni i powtórne docker compose up -d:

# powrót do wcześniejszej wersji
services:
  app:
    image: moja-firma/app:1.1
docker compose up -d

Gorzej bywa z bazą. Jeśli nowa wersja aplikacji wykonała nieodwracalną migrację schematu, powrót do starej może wymagać przywrócenia backupu. Dobrą praktyką jest oddzielenie wdrożenia kodu od migracji danych i trzymanie procedur odtwarzania przetestowanych, a nie tylko „na papierze”.

Mały, ale działający pipeline CI/CD

Nawet bez Kubernetesa i rozbudowanych platform da się spiąć prosty przepływ z użyciem git + registry + Docker.

Typowy scenariusz:

  1. commit do brancha main w repozytorium,
  2. CI buduje obraz moja-firma/app:<commit-sha> i puszcza testy,
  3. po sukcesie taguje go jako moja-firma/app:1.3.0 i wrzuca do registry,
  4. na serwerze Compose zaciąga nowy tag i restartuje usługę.

Po stronie serwera wystarczy skrypt typu:

#!/bin/bash
set -e

APP_DIR=/opt/app
cd "$APP_DIR"

# zaciągnięcie najnowszych zmian konfiguracji
git pull --rebase

# pobranie nowych obrazów
docker compose pull

# krótki health-check przed przełączeniem ruchu
docker compose up -d
docker compose ps

# opcjonalnie: wywołanie endpointu /health z load balancera

Taki skrypt można odpalać ręcznie po wydaniu lub z webhooka (np. GitLab, GitHub), ale kontrolę zostawiać po stronie admina. Na początek wystarczy prosty model: CI buduje i publikuje obraz, a wdrożenie na serwerze jest świadomą decyzją, a nie automatycznym triggerem.

Jeżeli środowisk jest kilka (test, preprod, prod), dobrze jest trzymać dla nich osobne pliki Compose lub przynajmniej osobne pliki .env. Wtedy ten sam pipeline buduje raz, a admin lub operator decyduje, które środowisko ma dostać nowy tag. Unika się w ten sposób sytuacji, w której prod przypadkiem dostaje „wersję deweloperską”.

Przy większej liczbie usług przydaje się prosty rejestr wersji: choćby plik YAML z informacją, jakie tagi są aktualnie na którym środowisku. Z perspektywy dyżuru nocnego ważne jest, aby w razie alarmu w kilka minut dojść, jaka wersja obrazu, jakiej aplikacji i z jaką konfiguracją właśnie działa.

Po kilku takich iteracjach Docker przestaje być „magiczny”, a zaczyna być po prostu kolejnym narzędziem w warsztacie admina. Jeśli obrazy są powtarzalne, kontenery opisane w Compose, a aktualizacje przechodzą przez prosty, ale konsekwentnie używany pipeline, codzienna administracja staje się przewidywalna i łatwiejsza do zautomatyzowania.

Najczęściej zadawane pytania (FAQ)

Po co administratorowi systemów Docker i co realnie ułatwia?

Docker upraszcza wdrażanie i migrację usług. Zamiast ręcznie konfigurować każdy serwer, przygotowujesz obraz z aplikacją i zależnościami, a potem uruchamiasz go w identyczny sposób na dev, test i produkcji.

Kontenery zmniejszają liczbę „specjalnych” konfiguracji na hostach. Host dostarcza kernel i zasoby, a cała logika aplikacji, biblioteki i konfiguracja siedzą w obrazie.

Czym Docker różni się od klasycznej administracji bez kontenerów?

W klasycznym podejściu instalujesz pakiety bezpośrednio na systemie, pilnujesz wersji bibliotek, konfiguracji PHP/Java/Python na każdym serwerze osobno. Z Dockerem budujesz obrazy, opisujesz usługi w plikach (np. docker-compose.yml) i traktujesz infrastrukturę jak kod.

Efekt: mniej różnic między środowiskami, szybsze odtwarzanie usług i łatwiejsze testowanie zmian. Zmienia się nacisk z „klikania” i ręcznej konfiguracji na automatyzację i wersjonowanie.

Czy Docker nadaje się do środowisk produkcyjnych, czy tylko do testów?

Docker nadaje się do produkcji, ale wymaga innych procesów niż „piaskownica” na laptopie. Produkcyjny kontener potrzebuje backupu, monitoringu, sensownego planu aktualizacji i procedur awaryjnych.

Przykładowo, nawet prosty Nginx w produkcji powinien mieć:

  • wolumeny na konfigurację i logi,
  • jasno opisany sposób aktualizacji obrazu i tagów,
  • monitoring zasobów i logów,
  • ograniczenia uprawnień i zasobów kontenera.

Bez tego konteneryzacja kończy się chaosem, mimo że „u mnie działa”.

Kiedy Docker na VPS lub w małej firmie ma sens, a kiedy nie?

Docker dobrze sprawdza się na pojedynczym VPS czy w małej firmie, gdy na jednym hoście trzeba uruchomić kilka różnych aplikacji i wersji środowisk (PHP, Node, Python). Host zostaje prosty, a każda aplikacja niesie swoje zależności w obrazie.

Nie ma większego sensu w bardzo prostych scenariuszach, np. jeden statyczny serwis WWW bez dodatkowych usług, albo gdy aplikacja jest mocno przywiązana do konkretnego sprzętu czy sterowników. Wtedy dodatkowa warstwa tylko komplikuje utrzymanie.

Jak bezboleśnie przenieść istniejący serwis WWW do Dockera?

Typowy scenariusz: masz serwis PHP na Apache z ręcznie dłubanym configiem. Minimalny plan migracji to:

  • wybrać oficjalny obraz, np. php:8.2-apache,
  • przenieść kod aplikacji do katalogu, który będzie montowany jako wolumen,
  • wydzielić konfiguracje (vhost, php.ini) do osobnych plików,
  • zdefiniować Dockerfile, który doinstaluje rozszerzenia PHP,
  • zbudować obraz i uruchomić kontener z mapowaniem portu 80.

Dalsze zmiany robisz przez aktualizację kodu i obrazu, zamiast grzebać w konfiguracji systemu hosta.

Jaka jest różnica między obrazem a kontenerem w Dockerze?

Obraz to niezmienny szablon z systemem plików i metadanymi (entrypoint, komenda startowa). Sama komenda docker pull nginx:stable tylko ściąga obraz na dysk, nic się jeszcze nie uruchamia.

Kontener to działający egzemplarz obrazu, czyli proces z izolacją. Tworzysz go np. przez docker run --name www -p 80:80 nginx:stable. Obraz możesz wykorzystywać wielokrotnie, uruchamiając z niego wiele kontenerów o różnych konfiguracjach.

Czy w kontenerach trzymać dane (bazy, pliki użytkowników)?

Kod i konfigurację aplikacji można spokojnie trzymać w obrazie, ale dane biznesowe powinny lądować poza samym kontenerem: w wolumenach, na zewnętrznych systemach plików albo w zarządzanych bazach danych.

Baza danych w kontenerze jest możliwa, pod warunkiem że katalog z danymi jest zmapowany na wolumen. Kontener traktujesz wtedy jako „opakowanie” dla binariów i konfiguracji, a nie magazyn danych, który znika po docker rm.