O co w ogóle chodzi z Dockerem? Proste wyjaśnienie po ludzku
Docker w jednym zdaniu: o co tu chodzi
Docker to narzędzie, które pozwala uruchamiać aplikacje w kontenerach – czyli w lekkich, odizolowanych „pudełkach”, które mają w sobie wszystko, czego dana aplikacja potrzebuje: odpowiednią wersję języka, bibliotek, narzędzi i konfiguracji. Dzięki temu aplikacja działa tak samo na każdym komputerze, na którym działa Docker.
Najprościej: zamiast instalować na swoim systemie milion bibliotek, serwerów i zależności, tworzysz lub pobierasz kontener i mówisz: „uruchom to”. Docker zajmuje się resztą. Ty widzisz działającą aplikację, system operacyjny pozostaje względnie czysty, a większość problemów typu „u mnie działa, u ciebie nie” znika.
Kontener jako pudełko z aplikacją i jej środowiskiem
Intuicyjna metafora: kontener to pudełko z gotowym środowiskiem. W środku masz aplikację oraz wszystkie składniki, których potrzebuje:
- konkretną wersję języka (np. Python 3.11 zamiast 3.8),
- konkretne biblioteki systemowe,
- konfigurację serwera (np. Nginx, Apache),
- podstawowe pliki systemowe niezbędne do działania.
Z zewnątrz (z punktu widzenia twojego komputera) kontener jest po prostu procesem, który izoluje się od reszty systemu. Jeśli ktoś przygotuje dla ciebie obraz Dockera z aplikacją, to uruchamiasz ją jednym poleceniem, bez ręcznego ustawiania wszystkiego krok po kroku.
„Zainstaluj wszystko” vs „odpal kontener”
Klasyczne podejście: chcesz przetestować nową wersję bazy danych lub serwera WWW. Co robisz?
- szukasz paczki instalacyjnej dla swojego systemu,
- instalujesz, konfigurujesz, walczysz z zależnościami,
- otwierasz porty, sprawdzasz logi,
- po zakończeniu testów próbujesz to wszystko odinstalować, a część rzeczy i tak zostaje w systemie.
Podejście z Dockerem: uruchamiasz:
docker run --name moja-baza -e POSTGRES_PASSWORD=haslo -p 5432:5432 -d postgres:16i masz gotową bazę PostgreSQL w kontenerze. Gdy skończysz:
docker stop moja-baza
docker rm moja-bazai baza znika. System pozostaje praktycznie nietknięty – nie zmieniły się globalne pakiety, usługi, katalogi systemowe.
Problem „u mnie działa” i jak Docker go rozwiązuje
Najbardziej odczuwalna korzyść: spójność środowiska. Bez Dockera każdy developer, tester i serwer produkcyjny może mieć:
- inną wersję systemu operacyjnego,
- inne biblioteki,
- inaczej ustawione zmienne środowiskowe,
- zainstalowane różne wersje tego samego języka (np. PHP, Node.js).
Skutek: programista mówi, że wszystko działa, tester widzi błędy, na produkcji pojawiają się dziwne problemy. Docker pozwala zapakować całą aplikację w jedno powtarzalne środowisko. Ten sam obraz kontenera uruchamiasz:
- na swoim laptopie,
- na maszynie testowej,
- na serwerze produkcyjnym w chmurze.
Jeśli wszystkim trzem miejscom podasz ten sam obraz Dockera, aplikacja zachowa się identycznie. Różnice środowiskowe sprowadzają się do minimum, a debugowanie staje się prostsze, bo mniej rzeczy jest „magicznie” zależnych od konkretnego systemu.
Kontener, obraz, host – najważniejsze pojęcia bez żargonu
Host: gdzie to wszystko się uruchamia
Host to po prostu system operacyjny, na którym działa Docker. Może to być:
- Linux (Ubuntu, Debian, Fedora itd.),
- Windows z Docker Desktop,
- macOS z Docker Desktop.
Host udostępnia zasoby: procesor, pamięć RAM, dysk i sieć. Docker korzysta z tych zasobów i uruchamia na nich kontenery. W praktyce: jeśli twój komputer jest hostem, kontenery nigdy nie są „mocniejsze” od niego, zawsze dzielą się ograniczonymi zasobami komputera lub serwera.
Obraz Dockera – szablon, z którego powstaje kontener
Obraz Dockera (image) to szablon, z którego tworzone są kontenery. Można to porównać do:
- pliku ISO z systemem operacyjnym (ale lżejszego),
- „zamrożonego” stanu systemu z zainstalowaną aplikacją,
- przepisu kulinarnego – kontener to gotowe danie, obraz to przepis + składniki.
Obraz jest niezmienny (ang. immutable): po zbudowaniu nie wprowadza się w nim zmian „na żywo”. Zmiany powstają przez zbudowanie nowego obrazu z aktualnym kodem lub konfiguracją. Typowe przykłady obrazów:
nginx:latest– serwer WWW Nginx,postgres:16– baza danych PostgreSQL,node:20-alpine– Node.js na lekkim systemie Alpine,python:3.12– Python z podstawowym systemem.
Kontener – działająca instancja obrazu
Kontener to działająca instancja obrazu. Jeśli obraz jest szablonem, to kontener jest jego uruchomionym egzemplarzem. Z jednego obrazu możesz uruchomić:
- jeden kontener (np. jedną bazę danych do testów),
- kilkanaście kontenerów (np. kilkanaście identycznych instancji serwera WWW za load balancerem).
Przykład:
docker run --name serwer-www -p 8080:80 -d nginx:latest
Tutaj obraz to nginx:latest, a kontener ma nazwę serwer-www. Obraz pozostaje niezmienny, kontener żyje swoim życiem: ma własne logi, bieżące procesy, tymczasowe pliki i stan działania. Po zatrzymaniu i usunięciu kontenera obraz nadal jest dostępny i możesz z niego zrobić kolejny kontener.
Rejestry obrazów: Docker Hub i prywatne repozytoria
Rejestr (registry) to miejsce, w którym przechowywane są obrazy Dockera. Najpopularniejszy jest:
- Docker Hub – publiczny serwis z tysiącami gotowych obrazów.
Firmy często korzystają też z własnych, prywatnych rejestrów:
- GitHub Container Registry,
- GitLab Container Registry,
- Amazon ECR, Google Container Registry, Azure Container Registry.
Obrazy pobierasz poleceniem:
docker pull nazwa-obrazu:tag
albo Docker robi to za ciebie przy pierwszym docker run, jeśli obraz nie jest jeszcze obecny lokalnie.
Kontener a zwykły proces – na czym polega izolacja
Z perspektywy systemu operacyjnego kontener jest procesem (lub zestawem procesów) z dodatkowymi ograniczeniami i izolacją. Pod spodem Docker wykorzystuje mechanizmy jądra Linux (namespaces, cgroups, chroot i inne), by:
- odizolować przestrzeń plików (kontener „widzi” swój system plików),
- odizolować procesy (procesy w kontenerze „nie widzą” innych procesów systemu),
- ograniczyć zasoby (RAM, CPU, dysk),
- odseparować sieć logicznie (kontener ma własny „interfejs sieciowy”).
Dzięki temu można na jednym hoście uruchomić dziesiątki, a nawet setki kontenerów, z których każdy „myśli”, że działa w swoim osobnym środowisku, mimo że współdzielą jądro systemu.
Krótki przykład: obraz Nginx → działający serwer WWW
Praktyczny, minimalny przykład:
docker pull nginx:latest
docker run --name moj-nginx -p 8080:80 -d nginx:latestCo się dzieje:
docker pullpobiera obraz serwera WWW Nginx z Docker Hub,docker runtworzy kontenermoj-nginxi uruchamia go w tle,- przekierowanie
-p 8080:80oznacza: port 8080 na twoim komputerze trafia na port 80 w kontenerze, - w przeglądarce wpisujesz
http://localhost:8080i widzisz stronę powitalną Nginx.
Nie musisz instalować Nginx bezpośrednio w systemie, nie zalegasz potem z konfiguracją i plikami w katalogach systemowych.
Kontenery kontra maszyny wirtualne – gdzie jest zysk?
Maszyna wirtualna – mini-komputer w komputerze
Maszyna wirtualna (VM) to pełny wirtualny komputer działający wewnątrz twojego sprzętu. Ma:
- własny system operacyjny,
- własne sterowniki,
- własne procesy,
- własną przestrzeń dyskową (plik obrazu dysku).
Do uruchamiania maszyn wirtualnych używa się np. VirtualBox, VMware, Hyper-V, KVM. Uruchomienie VM zwykle trwa od kilkunastu sekund do kilku minut, zużywa sporo RAM i CPU, a każda VM duplikuje cały system operacyjny.
Kontener – skupienie na aplikacji, nie na całym systemie
Kontener działa inaczej: zamiast uruchamiać pełny system operacyjny, współdzieli jądro hosta i dodaje tylko to, co niezbędne do działania aplikacji:
- kilka plików systemowych,
- oprogramowanie potrzebne aplikacji,
- samą aplikację i jej konfigurację.
Efekt:
- kontener startuje w sekundę lub ułamki sekundy,
- zużywa mniej RAM i CPU niż pełna VM,
- obrazy zajmują mniej miejsca niż pełne systemy.
Porównanie: szybkość, zasoby, granice izolacji
Uproszczone zestawienie cech kontenerów i maszyn wirtualnych:
| Cecha | Kontenery (Docker) | Maszyny wirtualne (VM) |
|---|---|---|
| Zakres | Izolowana aplikacja + jej środowisko | Pełny system operacyjny + aplikacje |
| Czas uruchomienia | Ułamki sekundy – kilka sekund | Kilkanaście sekund – kilka minut |
| Zużycie RAM/CPU | Niskie – współdzieli jądro hosta | Wyższe – osobny system operacyjny |
| Obraz / dysk | Typowo setki MB – kilka GB | Często kilka – kilkanaście GB |
| Poziom izolacji | Dobra izolacja procesów, współdzielone jądro | Silna izolacja – własne jądro systemu |
| Typowe zastosowanie | Uruchamianie i skalowanie usług i aplikacji | Symulacja pełnego systemu, testy OS |
Kontenery nie zastępują maszyn wirtualnych w każdej sytuacji. To raczej uzupełnienie: VM dla całych systemów, kontenery dla aplikacji.
Kiedy lepszy Docker, a kiedy klasyczna wirtualizacja
Kontenery mają przewagę, gdy:
- uruchamiasz wiele usług/aplikacji na jednym serwerze,
- często i szybko wdrażasz nowe wersje,
- skalujesz serwisy w górę i w dół (więcej/mniej kontenerów),
- potrzebujesz powtarzalnego środowiska dla dev/test/prod.
Maszyny wirtualne sprawdzą się lepiej, jeśli:
- musisz uruchomić zupełnie inny system operacyjny (np. Windows na Linuxie),
- potrzebujesz maksymalnej izolacji poziomu OS (np. wymagania bezpieczeństwa),
- testujesz upgrade całego systemu, sterowników, kernela.
Częsta praktyka w firmach: na jednym serwerze (fizycznym) działa kilka VM, a na każdej VM działają kontenery Dockera z właściwymi aplikacjami. To łączy elastyczność dwóch podejść.
Po co w ogóle używać Dockera? Konkretne korzyści w praktyce
Spójne środowisko od developera do produkcji
Największy zysk z Dockera to spójność środowiska. Ten sam obraz:
Jedno źródło prawdy dla konfiguracji i zależności
Gdy aplikacja działa w Dockerze, jej zależności i konfiguracja lądują w jednym miejscu – w obrazie i Dockerfile. Znika problem:
- „u mnie działa na Pythonie 3.10, a na serwerze jest 3.8”,
- różnych wersji bibliotek systemowych (OpenSSL, glibc itd.),
- ręcznych zmian w konfiguracji serwera, o których nikt nie pamięta.
Jeśli obraz przechodzi testy na CI i działa na stagingu, to ten sam artefakt trafia na produkcję. Konfiguracja zmienia się tylko przez:
- nowy obraz (np. zaktualizowana wersja aplikacji),
- zmienne środowiskowe i zewnętrzne pliki konfiguracyjne (volumes, ConfigMapy w Kubernetesa itd.).
Nie ma już sytuacji, że administrator „podstroił coś na serwerze”, a potem nikt nie wie co i gdzie.
Mniej konfliktów między projektami na jednym komputerze
Na typowym komputerze developera często działa kilka projektów jednocześnie:
- jeden wymaga Node 18, drugi Node 20,
- jeden używa PostgreSQL 13, drugi 16,
- różne wersje Javy, Pythona, PHP.
Bez kontenerów kończy się to żonglowaniem wersjami, przełączaniem środowisk, narzędziami typu pyenv, nvm i podobnymi. Docker rozcina ten problem:
- każdy projekt ma swój obraz z dokładnie taką wersją runtime, jakiej potrzebuje,
- lokalne środowisko pozostaje względnie czyste – wystarczy Docker i edytor kodu.
Jeśli jeden projekt „zasyfi” kontener zależnościami, inny na tym nie traci – ma swój obraz i swój zestaw pakietów.
Szybkie wdrażanie i cofanie zmian
Aplikacja w Dockerze wdraża się jako nowy obraz. Typowy scenariusz:
- budujesz nowy obraz z wersją
1.2.0, - wypychasz go do rejestru,
- na serwerze podmieniasz kontenery, żeby używały nowego tagu obrazu.
Jeśli coś pójdzie źle, cofnięcie zmian to:
- zatrzymanie kontenerów z nową wersją,
- uruchomienie kontenerów ze starym obrazem (np. tag
1.1.5).
Nie ma cofania paczek w systemie, przywracania backupu całego serwera czy „naprawiania” środowiska na żywym organizmie. Zmiana polega na zastąpieniu jednego obrazu drugim.
Lepsza skalowalność i automatyzacja
Kontenery są stworzone do tego, żeby je:
- automatycznie uruchamiać i wyłączać,
- przenosić między serwerami,
- monitorować i restartować, jeśli coś padnie.
Na jednym hoście można:
- uruchomić kilka instancji tej samej aplikacji (np. 5 kontenerów z backendem),
- w prosty sposób dodać kolejną instancję przy wzroście ruchu,
- rozproszyć kontenery po klastrze (Kubernetes, Docker Swarm, ECS).
Z punktu widzenia automatyzacji, kontener to precyzyjnie opisany „klocek”, którym zarządza się łatwiej niż ręcznie konfigurowanym serwerem.
Łatwiejsze testowanie i eksperymenty
Docker dobrze nadaje się do krótkich, izolowanych eksperymentów:
- test nowej bazy danych bez ingerencji w system (np.
docker run postgres), - sprawdzenie innej wersji biblioteki bez psucia działającego projektu,
- odpalanie całych stosów technologicznych (np. ELK stack) na lokalnym laptopie.
Po testach kontener można po prostu usunąć:
docker rm -f nazwa-konteneraŚlady po eksperymencie znikają, a system hosta pozostaje w takim samym stanie jak wcześniej.
Standardowy sposób pakowania aplikacji
Obraz Dockera staje się standardowym formatem „paczki” aplikacji. To:
- ułatwia wdrażanie na różnych platformach (serwer bare metal, VM, chmura),
- upraszcza integrację z narzędziami CI/CD (GitHub Actions, GitLab CI, Jenkins),
- obniża próg wejścia dla nowych członków zespołu (wystarczy znać kilka komend Dockera).
Jeśli aplikacja jest spakowana jako kontener, coraz więcej usług w chmurze potrafi ją przyjąć „wprost”, bez długiej listy dodatkowych wymogów.

Jak działa Docker pod spodem – tylko tyle, ile trzeba wiedzieć na start
Architektura klient–serwer: Docker CLI i Docker Engine
Docker składa się z dwóch głównych elementów:
- Docker CLI – program, który wywołujesz w terminalu (
docker run,docker builditd.), - Docker Engine (demon
dockerd) – usługa działająca w tle, która faktycznie tworzy obrazy i kontenery.
Gdy wpisujesz komendę:
docker psCLI wysyła żądanie do Dockera Engine (po lokalnym API), a ten zwraca listę kontenerów. Taki podział pozwala:
- sterować zdalnym serwerem Dockera z własnego komputera,
- oddzielić interfejs użytkownika od części, która zarządza kontenerami.
Warstwowe obrazy i współdzielenie plików
Obraz Dockera składa się z wielu warstw (layers). Każda warstwa to:
- zestaw plików powstałych na danym etapie budowania obrazu,
- „różnica” względem poprzedniej warstwy.
Przykład uproszczonego Dockerfile:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]Tu powstaną m.in. warstwy:
- bazowy obraz
python:3.12-slim, - ustawienie katalogu roboczego,
- skopiowany plik
requirements.txt, - zainstalowane biblioteki Pythona,
- skopiowany kod aplikacji.
Gdy zbudujesz nową wersję obrazu po poprawce w kodzie, Docker:
- użyje ponownie warstw bazowych (system, Python, biblioteki),
- przebuduje tylko warstwy od momentu, w którym pliki się zmieniły.
To skraca czas budowania i oszczędza miejsce na dysku – wiele obrazów może współdzielić te same warstwy.
System plików kontenera – copy-on-write
Kontener używa systemu plików obrazu, ale nie modyfikuje go bezpośrednio. Zastosowana jest technika copy-on-write:
- obraz jest traktowany jako tylko do odczytu,
- kontener dostaje nad nim cienką warstwę zapisu.
Jeśli proces w kontenerze zmienia plik:
- oryginalny plik z obrazu zostaje w spokoju,
- modyfikacja trafia do warstwy zapisu kontenera.
Dzięki temu kilka kontenerów może korzystać z tego samego obrazu bez konfliktów. Każdy ma swoją własną przestrzeń na zmiany.
Sieć dokera w skrócie
Docker tworzy własne sieci wirtualne. Domyślnie nowe kontenery lądują w sieci typu bridge:
- każdy kontener dostaje własny adres IP w tej sieci,
- kontenery mogą się widzieć po nazwach (DNS wbudowany w Dockera),
- dostęp z zewnątrz odbywa się zwykle przez mapowanie portów (
-p 8080:80).
Można też tworzyć inne typy sieci (np. host, overlay), ale na początek wystarczy zrozumienie, że:
- kontenery mają logicznie odseparowaną sieć,
- mapowanie portów decyduje, jakie usługi są dostępne z hosta i z internetu.
Wolumeny – trwałe dane poza kontenerem
Domyślnie dane zapisane w kontenerze znikają, gdy kontener zostanie usunięty. Do trzymania trwałych danych używa się wolumenów (volumes):
- wolumen to kawałek przestrzeni dyskowej zarządzany przez Dockera,
- jest podpinany do kontenerów jako katalog (np.
/var/lib/postgresql/data), - przetrwa restart i usunięcie kontenera.
Tworzenie wolumenu i użycie w kontenerze:
docker volume create pgdata
docker run -d --name baza
-v pgdata:/var/lib/postgresql/data
postgres:16
Tu dane bazy trafiają do wolumenu pgdata, a kontener można wymienić bez utraty informacji.
Pierwsze kroki: instalacja Dockera i test „Hello World”
Instalacja Dockera na popularnych systemach
Najwygodniej korzystać z oficjalnych pakietów:
- Windows / macOS – aplikacja Docker Desktop (graficzny panel + Docker Engine),
- Linux – pakiety
docker-cez oficjalnego repozytorium Dockera.
Na Windows Docker Desktop domyślnie używa wirtualnej maszyny (WSL2 lub Hyper-V), bo kontenery wymagają jądra podobnego do Linuksa. Dla użytkownika wygląda to jednak jak zwykły Docker.
Sprawdzenie, czy Docker działa
Po instalacji pierwszym krokiem jest sprawdzenie wersji:
docker versionJeśli pojawią się informacje o kliencie i serwerze, Docker jest uruchomiony. Komenda:
docker infozwraca szczegółowe dane o konfiguracji – ile masz obrazów, ile kontenerów, jaki driver sieciowy i storage są w użyciu.
Pierwszy test: oficjalny „hello-world”
Klasyczny test:
docker run hello-worldDocker zrobi wtedy:
- spróbuje znaleźć lokalny obraz
hello-world, - jeśli go nie ma – pobierze go z Docker Hub,
- uruchomi kontener, który wypisze krótką wiadomość i się zakończy.
Sam komunikat wyjaśnia, co dokładnie zaszło – to pierwszy dowód, że:
- Docker potrafi pobierać obrazy z rejestru,
- kontenery się uruchamiają,
- logi kontenera trafiają na standardowe wyjście (widzisz je w terminalu).
Prosty serwer HTTP w kontenerze
Kolejny krok to serwer WWW. Najprościej:
docker run --name moj-nginx -p 8080:80 -d nginx:latestPo chwili:
- wejdź w przeglądarce na
http://localhost:8080, - zobaczysz domyślną stronę Nginx.
Zatrzymanie i usunięcie kontenera:
docker stop moj-nginx
docker rm moj-nginxPort 8080 przestaje nasłuchiwać, a lokalny system pozostaje bez śladów po Nginx (poza obrazem Dockera).
Podstawowe komendy Dockera, które wystarczą na początek
Praca z obrazami: listowanie, pobieranie, usuwanie
Kilka komend dotyczących obrazów:
docker images– lista obrazów lokalnie,docker pull nazwa:tag– pobranie obrazu z rejestru,docker rmi nazwa:tag– usunięcie obrazu.
Przykład:
docker pull nginx:latest
docker images
docker rmi nginx:latestUruchamianie, zatrzymywanie i podgląd kontenerów
Najważniejsze komendy dotyczące kontenerów:
docker run– stworzenie i uruchomienie nowego kontenera,docker ps– lista działających kontenerów,docker ps -a– lista wszystkich (także zatrzymanych),docker stop nazwa– łagodne zatrzymanie,docker kill nazwa– wymuszone zatrzymanie,docker rm nazwa– usunięcie kontenera.
Przykładowy cykl życia:
Interaktywna praca z kontenerem
Do szybkiego debugowania albo eksploracji obrazu przydaje się tryb interaktywny. Zamiast uruchamiać proces w tle, podłączasz się do powłoki w kontenerze:
docker run -it --name test-bash ubuntu:22.04 bashDzieje się kilka rzeczy:
- Docker pobiera obraz
ubuntu:22.04(jeśli go nie masz), - tworzy kontener o nazwie
test-bash, - uruchamia w nim proces
bash, - łączy Twój terminal z tym procesem (
-it).
Po wyjściu z powłoki (np. komendą exit) kontener się zatrzyma, ale nadal będzie istniał. Można go skasować:
docker rm test-bashPodgląd logów i szczegółów kontenera
Większość aplikacji loguje na standardowe wyjście. Docker przechwytuje te logi i udostępnia prostą komendę:
docker logs nazwa-konteneraPrzykład z Nginx:
docker logs moj-nginx
docker logs -f moj-nginx
Przełącznik -f działa podobnie jak tail -f – śledzi nowe wpisy w logach.
Do diagnostyki przydaje się też:
docker inspect moj-nginxZwraca duży obiekt JSON z pełną konfiguracją i bieżącym stanem kontenera: zmienne środowiskowe, mapowania portów, wolumeny, sieć itd. Jeśli trzeba tylko adres IP:
docker inspect -f '{{ .NetworkSettings.IPAddress }}' moj-nginxUsuwanie „śmieci”: obrazy, kontenery, wolumeny
Praca z Dockerem generuje sporo obiektów: zatrzymane kontenery, nieużywane obrazy, stare wolumeny. Co jakiś czas dobrze jest zrobić porządek.
Podstawowe narzędzia:
docker rm– usuwa kontenery,docker rmi– usuwa obrazy,docker volume rm– usuwa wolumeny.
Do automatycznego sprzątania:
docker system pruneUsunie zatrzymane kontenery, nieużywane sieci, „osierocone” warstwy obrazów. Z przełącznikiem:
docker system prune -aznikną także wszystkie obrazy, których nie używa żaden kontener. Na środowisku developerskim to wygodne, na produkcji – trzeba uważać.
Obrazy i Dockerfile – jak zbudować własny kontener z aplikacją
Obraz jako szablon kontenera
Obraz Dockera to „zamrożony” stan systemu plików + informacja, jaki proces uruchomić domyślnie. Z jednego obrazu można odpalić dowolną liczbę kontenerów:
docker run --name app1 moj-obraz:latest
docker run --name app2 moj-obraz:latestOba kontenery działają niezależnie, choć mają ten sam punkt wyjścia.
Co to jest Dockerfile
Dockerfile to tekstowy plik z instrukcjami, jak zbudować obraz. Każda linia opisuje krok: skąd zaczynamy, jakie pliki kopiujemy, co instalujemy, jak uruchamiamy aplikację.
Prosty szkielet:
FROM <bazowy-obraz>
WORKDIR /app
COPY . .
RUN <komendy instalacyjne>
CMD [<program>, <arg1>, ...]Minimalny przykład: aplikacja w Pythonie
Załóżmy katalog projektu:
app/
├─ main.py
└─ Dockerfile
Plik main.py:
from http.server import HTTPServer, BaseHTTPRequestHandler
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.end_headers()
self.wfile.write(b"Hello from Docker!")
if __name__ == "__main__":
server = HTTPServer(("", 8000), Handler)
print("Listening on :8000")
server.serve_forever()
Prosty Dockerfile:
FROM python:3.12-slim
WORKDIR /app
COPY main.py .
EXPOSE 8000
CMD ["python", "main.py"]Budowanie obrazu i uruchomienie:
cd app
docker build -t python-hello .
docker run --name python-hello-container -p 8000:8000 python-hello
W przeglądarce pod http://localhost:8000 pojawi się odpowiedź z kontenera.
Typowe instrukcje w Dockerfile
Podstawowy zestaw wystarcza na większość prostych projektów:
FROM– wybór obrazu bazowego (np.node:22-alpine,golang:1.22),WORKDIR– ustawienie katalogu roboczego wewnątrz obrazu,COPY/ADD– kopiowanie plików z hosta do obrazu,RUN– wykonanie komendy na etapie budowania (instalacja pakietów, kompilacja),EXPOSE– deklaracja portu, na którym nasłuchuje aplikacja (informacyjnie),ENV– ustawianie zmiennych środowiskowych,CMD– domyślna komenda, która uruchomi się w kontenerze.
CMD zwykle zapisuje się w formie tablicy (tzw. exec form), jak w przykładzie z Pythonem. Dzięki temu Docker nie odpala powłoki pośredniej, a sygnały (np. SIGTERM) trafiają poprawnie do procesu.
.dockerignore – jak przyspieszyć budowanie obrazu
Domyślnie instrukcja COPY . . pakuje cały katalog kontekstu (bieżący katalog) i wysyła go do Demona Dockera. Jeśli w projekcie leżą katalogi typu node_modules, venv czy ciężkie pliki tymczasowe, budowanie mocno zwalnia.
Rozwiązaniem jest plik .dockerignore w katalogu projektu, np.:
venv
__pycache__
*.pyc
.git
node_modules
dist
build
Działa podobnie do .gitignore: wymienione pliki i foldery nie trafią do kontekstu budowania.
Obrazy „ciężkie” i „lekkie” – dobór bazowego systemu
Ten sam kod aplikacji można spakować na wiele sposobów. Wybór obrazu bazowego wpływa na rozmiar i bezpieczeństwo:
- obrazy pełne (np.
ubuntu,debian) – wygodne do debugowania, ale duże, - obrazy „slim” (np.
python:3.12-slim) – kompromis między rozmiarem a funkcjonalnością, - obrazy „alpine” (np.
node:22-alpine) – bardzo małe, ale czasem trzeba dostosować zależności (inne glibc/musl).
Przy typowej aplikacji webowej sensowna strategia to:
- na etapie developmentu korzystać z obrazu wygodnego do pracy (np.
python:3.12), - do obrazu produkcyjnego użyć lżejszej bazy (np.
python:3.12-slimalbo obraz typu „distroless”).
Multi-stage build – jeden Dockerfile, różne etapy
Gdy aplikacja wymaga kompilacji (Go, Java, Rust, frontend), dobrym rozwiązaniem są multi-stage builds. Jedna definicja obrazu może mieć kilka etapów:
- etap „builder” – kompilacja, instalacja narzędzi, testy,
- etap „runtime” – minimalny obraz z gotowym artefaktem.
Przykład dla aplikacji Go:
FROM golang:1.22 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o app
FROM gcr.io/distroless/base-debian12
WORKDIR /app
COPY --from=builder /app/app .
USER nonroot
CMD ["./app"]Gotowy obraz nie zawiera kompilatora ani narzędzi developerskich – tylko skompilowany binarny program i niezbędne biblioteki.
Parametryzacja obrazu zmiennymi środowiskowymi
Aplikacja zwykle potrzebuje danych konfiguracyjnych: adres bazy, klucze API, tryb debugowania. Można je przekazywać:
- domyślnie w
Dockerfileza pomocąENV, - dynamicznie przy uruchamianiu kontenera przez
-elub plik--env-file.
Przykład:
docker run -d --name app
-e APP_ENV=production
-e DB_HOST=db
-p 8000:8000
moj-obraz:latest
W Dockerfile można definiować rozsądne wartości domyślne:
ENV APP_ENV=development
ENV APP_PORT=8000Na produkcji te wartości da się nadpisać bez budowania nowego obrazu.
Mapowanie katalogów z hosta – bind mounts
Oprócz wolumenów Dockera można podpiąć do kontenera dowolny katalog z hosta. Przydaje się to w developmentcie, gdy kod ma być widoczny natychmiast:
docker run --name node-dev
-v $(pwd):/app
-w /app
-p 3000:3000
node:22-alpine
npm run devTu:
-v $(pwd):/app– katalog bieżący z hosta jest widoczny w kontenerze jako/app,-w /app– ustawia katalog roboczy,- wszystkie zmiany w plikach lokalnych są od razu widoczne wewnątrz kontenera.
Tagowanie i wersjonowanie obrazów
Obraz ma nazwę i tag. Bez tagu Docker użyje :latest, ale w praktyce lepiej być bardziej precyzyjnym:
docker build -t moja-aplikacja:1.0.0 .
docker build -t moja-aplikacja:1.0.1 .W projektach spotyka się m.in. strategie:
- tagowanie numerem wersji aplikacji (semver),
- tagowanie skrótem commita (
moja-aplikacja:git-sha), - utrzymywanie ruchomego taga
:latestdla najnowszego builda.
W CI/CD często buduje się obraz z dwoma tagami naraz, np. :1.4.2 i :latest, co ułatwia zarówno odtwarzalność, jak i szybkie wdrażanie najnowszej wersji.
Wysyłanie obrazu do rejestru (Docker Hub, GitHub Container Registry, prywatny)
Aby użyć własnego obrazu na innym serwerze, trzeba go wysłać do rejestru. Przykład z Docker Hub:
- Załóż konto na Docker Hub i zaloguj się:
docker login - Otaguj lokalny obraz z prefiksem nazwy użytkownika:
docker tag moja-aplikacja:1.0.0 <uzytkownik>/moja-aplikacja:1.0.0 - Wyślij obraz:
docker push <uzytkownik>/moja-aplikacja:1.0.0
Na docelowej maszynie wystarczy:
docker pull <uzytkownik>/moja-aplikacja:1.0.0
docker run -d -p 8000:8000 <uzytkownik>/moja-aplikacja:1.0.0
Zamiast publicznego Docker Hub można użyć prywatnego rejestru (np. GitHub Container Registry, GitLab Container Registry, ECR w AWS). Z punktu widzenia Dockera różni się tylko adres, np. ghcr.io/<org>/moja-aplikacja:tag.
Przykład: mała aplikacja webowa z bazą, cała w Dockerze
Przybliżony scenariusz z codziennej praktyki:
- aplikacja Node.js,
- baza PostgreSQL,
- developer chce szybko uruchomić pełne środowisko lokalnie.
Obraz aplikacji (uproszczony Dockerfile):
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
ENV PORT=3000
EXPOSE 3000
CMD ["npm", "start"]W tym momencie można:
- zbudować obraz:
docker build -t moja-node-app:dev . - uruchomić bazę:
docker run -d --name db -e POSTGRES_PASSWORD=haslo -e POSTGRES_USER=app -e POSTGRES_DB=appdb -v pgdata:/var/lib/postgresql/data postgres:16
Bibliografia i źródła
- Docker Overview. Docker, Inc. – Oficjalne wprowadzenie do Dockera, kontenerów, obrazów i rejestrów
- Docker Engine Overview. Docker, Inc. – Opis działania silnika Docker, hosta, kontenerów i obrazów
- PostgreSQL on Docker Official Image Reference. PostgreSQL Global Development Group – Opis oficjalnego obrazu postgres, zmienne środowiskowe, przykłady użycia
- Nginx Docker Official Image Documentation. F5 NGINX, Inc. – Opis obrazu nginx:latest, podstawowe przykłady docker run i mapowanie portów
- Kubernetes Documentation – Containers. Cloud Native Computing Foundation – Wyjaśnienie pojęcia kontenera, obrazu i różnic względem VM w kontekście orkiestracji
- Linux Containers and Docker. Red Hat – Artykuł wyjaśniający kontenery, obrazy, hosta oraz izolację procesów w Linuksie
- Linux kernel documentation – Control Groups (cgroups). The Linux Foundation – Techniczne podstawy ograniczania zasobów procesów i kontenerów
- What is Docker?. Microsoft Learn – Wprowadzenie do Dockera dla początkujących, definicje kontenerów i obrazów






