Docker to narzędzie, które pakuje aplikację razem z jej zależnościami w obraz i uruchamia ją w odizolowanym kontenerze, dzięki czemu działa tak samo na każdym komputerze. Kontenery współdzielą jądro systemu, więc są lżejsze od maszyn wirtualnych, a dane w nich znikają razem z kontenerem, jeśli nie zapiszesz ich w wolumenie. Najczęstsze problemy to aplikacja niedostępna z zewnątrz (zły adres nasłuchu lub brak mapowania portu), utrata danych i localhost, który w kontenerze oznacza sam kontener.„U mnie działa” to jedno z najstarszych zdań w programowaniu: kod działa na Twoim komputerze, a na serwerze lub u kolegi z zespołu nie. Docker powstał, żeby to ograniczyć, i dziś jest jednym z podstawowych narzędzi programisty. Poniżej wyjaśniamy, czym jest, czym różni się od maszyny wirtualnej i jak uruchomić pierwszą aplikację. Przykłady uruchomiliśmy na własnym komputerze, a w drugiej połowie artykułu pokazujemy najczęstsze problemy, które można w ten sposób odtworzyć, wraz z ich rozwiązaniami.
Czym jest Docker i do czego służy?
Docker to narzędzie do konteneryzacji (containerization): pakowania aplikacji razem ze wszystkim, czego potrzebuje do działania, czyli środowiskiem uruchomieniowym, bibliotekami i konfiguracją, w jedną jednostkę zwaną obrazem. Z tego obrazu uruchamiasz kontener, czyli odizolowany proces, który działa tak samo na laptopie, w zespole i na serwerze. Dlatego Docker służy przede wszystkim do:
- ujednolicenia środowiska w zespole (każdy ma te same wersje narzędzi),
- uruchamiania usług pomocniczych bez instalowania ich w systemie (baza danych, kolejka, cache),
- wdrażania aplikacji na serwerze w przewidywalny sposób.
Warto też pamiętać, że Docker to jedna z implementacji kontenerów, a nie jedyna. Istnieją inne rozwiązania, np. Podman czy LXC, ale w praktyce o kontenerach mówi się dziś najczęściej „Docker”. Według Stack Overflow Developer Survey 2025 Docker używa 73,8% zawodowych programistów, więc to narzędzie, na które trafisz w większości zespołów.
Czym są obraz, kontener i Dockerfile?
Te pojęcia wracają na początku każdego kursu, a pierwsze trzy mylone są najczęściej:
| Pojęcie | Co to jest | Analogia |
|---|---|---|
| Obraz (image) | niezmienny „zapis” aplikacji i jej środowiska | przepis lub szablon |
| Kontener (container) | uruchomiona instancja obrazu | potrawa ugotowana według przepisu |
| Dockerfile | plik tekstowy z instrukcją budowy obrazu | spis kroków przepisu |
| Rejestr (np. Docker Hub) | miejsce, z którego pobierasz i gdzie wysyłasz obrazy | księgarnia przepisów |
| Wolumen (volume) | miejsce na dane, które przeżywa usunięcie kontenera | lodówka poza kuchnią |
| Docker Compose | plik opisujący kilka kontenerów, które działają razem | menu z kilku dań naraz |
Z jednego obrazu możesz uruchomić dowolnie wiele kontenerów. Obrazów nie piszesz od zera, tylko budujesz je na gotowych: u nas bazą był node:22-alpine (235 MB), a dla porównania obraz hello-world ma 22,6 kB, alpine 26,6 MB, a redis:7-alpine 58,7 MB.
Czym różni się kontener od maszyny wirtualnej?
Maszyna wirtualna udaje cały komputer: ma własny system operacyjny razem z jądrem. Kontener nie ma własnego jądra, tylko współdzieli jądro systemu z komputerem, na którym działa, i izoluje jedynie procesy i pliki. Dlatego kontenery są mniejsze i startują szybciej niż maszyny wirtualne.
| Cecha | Maszyna wirtualna | Kontener |
|---|---|---|
| System operacyjny | własny, razem z jądrem | współdzieli jądro hosta |
| Rozmiar | zwykle gigabajty | od kilkudziesięciu kilobajtów do setek megabajtów (u nas 22,6 kB – 254 MB) |
| Izolacja | pełna | na poziomie procesów i plików |
| Typowe zastosowanie | uruchamianie całych systemów | uruchamianie pojedynczych aplikacji i usług |
Ciekawostka z naszych testów: na komputerze Mac kontenery wciąż potrzebują Linuksa, bo to jego jądro współdzielą. Polecenie uname -sm uruchomione w kontenerze pokazało Linux aarch64, a na samym komputerze Darwin arm64:
docker run --rm alpine uname -sm
# Linux aarch64
uname -sm
# Darwin arm64
Na macOS i Windowsie Docker uruchamia więc w tle lekką maszynę wirtualną z Linuksem, a na samym Linuksie kontenery działają bezpośrednio. Różnice wirtualizacji i kontenerów dobrze tłumaczy w rozmowie senior DevOps Wojciech Lepczyński:
Jak zainstalować Dockera?
Najprostsza droga na Windowsie i macOS to Docker Desktop. Według dokumentacji na Windowsie potrzebuje 8 GB RAM, 64-bitowego procesora z SLAT i włączonej wirtualizacji sprzętowej w BIOS/UEFI oraz WSL 2 w wersji 2.1.5 lub nowszej, a na macOS co najmniej 4 GB RAM. Na Linuksie instalujesz Docker Engine z repozytorium swojej dystrybucji lub według dokumentacji Dockera. Wymagania sprzętowe Dockera i innych narzędzi zestawiamy w artykule Jaki komputer dla programisty, a o różnicach między systemami przy pracy z kontenerami (WSL 2, architektura procesora) piszemy w tekście System operacyjny dla programisty.
My testowaliśmy Docker Engine 29.9.3 w Colima (maszyna wirtualna z Linuksem uruchamiana z terminala) razem z oficjalnym klientem docker, a nie Docker Desktop. Polecenia i zachowanie silnika są te same, ale okna Docker Desktop i jego ustawień nie sprawdzaliśmy. Po instalacji sprawdź, czy wszystko działa:
docker --version
# Docker version 29.9.3, build 5457199aa5
docker run --rm hello-world
# Hello from Docker!
# This message shows that your installation appears to be working correctly.
Za pierwszym razem Docker pobiera obraz hello-world z rejestru (to widać w wyniku jako „Pulling from library/hello-world”), a kolejne uruchomienia korzystają z kopii zapisanej na dysku.
Jak uruchomić pierwszy kontener i jak nim zarządzać?
Podstawowy cykl pracy to uruchomienie, podgląd i zatrzymanie. Najważniejsze polecenia, które sprawdziliśmy:
| Polecenie | Co robi |
|---|---|
docker run <obraz> | pobiera obraz (jeśli go nie ma) i uruchamia z niego kontener |
docker run -d --name <nazwa> -p 8080:3000 <obraz> | uruchamia w tle, nadaje nazwę i mapuje port 8080 komputera na 3000 kontenera |
docker ps | pokazuje działające kontenery (docker ps -a także zatrzymane) |
docker logs <nazwa> | pokazuje wyjście aplikacji, czyli pierwsze miejsce do szukania błędów |
docker stop <nazwa> | zatrzymuje kontener |
docker rm <nazwa> | usuwa kontener (opcja --rm robi to automatycznie po zatrzymaniu) |
docker images | pokazuje obrazy pobrane na komputer |
Jak napisać Dockerfile i zbudować własny obraz?
Zbudujmy obraz z prostej aplikacji w Node.js, która odpowiada na zapytania HTTP. Plik server.js:
const http = require("http");
const host = process.env.HOST || "0.0.0.0";
http.createServer((req, res) => {
res.end("Cześć z kontenera! PID " + process.pid + "\n");
}).listen(3000, host, () => console.log("Nasłuchuję na " + host + ":3000"));
Oraz Dockerfile, czyli instrukcja budowy obrazu:
FROM node:22-alpine
WORKDIR /app
COPY package.json ./
RUN npm install --omit=dev
COPY server.js ./
CMD ["node", "server.js"]
Każda linia ma swoją rolę: FROM wskazuje obraz bazowy, WORKDIR ustawia katalog roboczy w obrazie, COPY kopiuje pliki z komputera, RUN wykonuje polecenie podczas budowania (tu instaluje zależności), a CMD mówi, co uruchomić, gdy powstaje kontener. Budujemy i uruchamiamy:
docker build -t demo:1 .
docker run -d --rm --name demo -p 8080:3000 demo:1
curl localhost:8080
# Cześć z kontenera! PID 1
docker ps --format '{{.Names}} | {{.Image}} | {{.Ports}}'
# demo | demo:1 | 0.0.0.0:8080->3000/tcp, [::]:8080->3000/tcp
Wynik obrazu demo:1 to 254 MB, z czego 235 MB to obraz bazowy. Zwróć uwagę na PID 1: aplikacja w kontenerze jest pierwszym i jedynym procesem, bo kontener nie startuje całego systemu.
Dlaczego kolejne budowanie obrazu trwa krócej?
Docker buduje obraz z warstw (layers), po jednej na instrukcję, i zapamiętuje je. Jeśli instrukcja i wszystko przed nią się nie zmieniły, używa zapamiętanej warstwy. U nas pierwsze budowanie zajęło 6,4 s (razem z instalacją pakietów), a po dopisaniu jednej linijki do server.js kolejne budowanie 0,47 s, bo warstwa z npm install wzięła się z pamięci podręcznej (w logu: „Using cache”). Dlatego w Dockerfile najpierw kopiuje się plik z zależnościami (package.json), instaluje je, i dopiero potem kopiuje resztę kodu: zmiana kodu nie wymusza wtedy ponownej instalacji pakietów.
Czym są wolumeny i dlaczego dane w kontenerze znikają?
Kontener domyślnie nie przechowuje danych na stałe. Zobacz, co się stało w naszym teście: zapisaliśmy plik w kontenerze, usunęliśmy kontener i uruchomiliśmy nowy:
docker run --name nov alpine sh -c 'echo dane > /data.txt; cat /data.txt'
# dane
docker rm nov
docker run --rm alpine cat /data.txt
# cat: can't open '/data.txt': No such file or directory
Plik zniknął razem z kontenerem. Rozwiązaniem jest wolumen (volume): miejsce zarządzane przez Dockera, które istnieje niezależnie od kontenera. Ten sam test z wolumenem:
docker volume create dane
docker run --rm -v dane:/data alpine sh -c 'echo dane > /data/plik.txt'
docker run --rm -v dane:/data alpine cat /data/plik.txt
# dane
Plik przetrwał, bo drugi, nowy kontener podłączył ten sam wolumen. Tak działa każda baza danych w kontenerze: katalog z danymi bazy montuje się jako wolumen, inaczej po usunięciu kontenera tracisz całą zawartość. To jeden z najpopularniejszych problemów: pytania o to, jak zachować dane bazy lub kolejki w kontenerze, mają na Stack Overflow dziesiątki tysięcy wyświetleń.
Czym jest Docker Compose i kiedy z niego korzystać?
Gdy aplikacja potrzebuje kilku usług naraz, np. serwera i bazy lub cache, nie uruchamiasz każdej ręcznie. Docker Compose pozwala opisać je w jednym pliku compose.yaml i wystartować jednym poleceniem. Poniżej minimalny przykład: Redis z wolumenem oraz drugi kontener, który sprawdza, czy widzi usługę Redis:
services:
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
app:
image: alpine
depends_on:
- redis
command: sh -c "nc -zv redis 6379"
volumes:
redis-data:
docker compose up
# app-1 | redis (172.18.0.2:6379) open
docker compose down -v
Dwie rzeczy warto zapamiętać. Po pierwsze, usługi w Compose widzą się po nazwie usługi (tu redis), a nie po adresie localhost. Po drugie, docker compose down zatrzymuje i usuwa kontenery oraz sieć, a dodane -v usuwa także wolumeny, więc użyj go świadomie. (W naszej instalacji polecenie nazywało się docker-compose, bo wtyczka Compose była zainstalowana osobno; w Docker Desktop działa jako docker compose.) Konteneryzację i Docker Compose ćwiczymy w praktyce w module Konteneryzacja: Docker.
Jakie są najczęstsze problemy z Dockerem i jak je rozwiązać?
Poniższe problemy odtworzyliśmy na własnym komputerze (macOS, Colima). Dla każdego podajemy objaw, przyczynę i rozwiązanie.
Naszym zdaniem warto wyrobić sobie jeden nawyk: przy każdym problemie zacznij od docker ps -a (czy kontener w ogóle działa i na jakich portach) oraz docker logs <nazwa> (co mówi aplikacja), a dopiero potem zgaduj przyczynę. W naszych testach log często od razu wskazywał winowajcę: komunikat „Nasłuchuję na 127.0.0.1:3000” wyjaśnił, dlaczego aplikacja jest niedostępna z zewnątrz, zanim zdążyliśmy zmieniać cokolwiek innego.
| Objaw | Najczęstsza przyczyna | Rozwiązanie |
|---|---|---|
Strona z kontenera się nie otwiera, curl kończy się błędem | aplikacja słucha na 127.0.0.1 w kontenerze albo brakuje -p | nasłuchuj na 0.0.0.0 i mapuj port: -p 8080:3000 |
Kontener nie łączy się z inną usługą po localhost | localhost w kontenerze to sam kontener | użyj nazwy usługi z Compose (np. redis) |
| Dane po usunięciu kontenera zniknęły | brak wolumenu | zamontuj wolumen: -v dane:/data |
curl odpowiada, ale inną aplikacją niż kontener | port na komputerze był już zajęty | sprawdź, co zajmuje port, i wybierz inny |
exec format error | obraz zbudowany pod inną architekturę procesora | --platform lub obraz wieloplatformowy |
| Brakuje miejsca na dysku | zbierają się stare obrazy i kontenery | docker system df, docker system prune |
Dlaczego aplikacja w kontenerze jest niedostępna z zewnątrz?
Najczęstsza przyczyna to adres nasłuchu. Gdy ustawiliśmy HOST=127.0.0.1, aplikacja w kontenerze nasłuchiwała tylko wewnątrz niego, a curl z komputera zakończył się błędem z kodem 52 (pusta odpowiedź), mimo że port był zmapowany:
docker run -d --rm --name loc -e HOST=127.0.0.1 -p 8081:3000 demo:1
curl -s -m 5 localhost:8081
# curl exit 52
docker logs loc
# Nasłuchuję na 127.0.0.1:3000
Wskazówka była w logach, więc zaczynaj diagnozę od docker logs. Rozwiązanie: aplikacja w kontenerze ma nasłuchiwać na 0.0.0.0, a port musi być zmapowany przez -p. To odpowiedź na jedno z najczęstszych pytań o Dockera (pytania o przekierowanie portów w Compose mają na Stack Overflow kilkanaście tysięcy wyświetleń).
Co znaczy localhost w kontenerze?
Każdy kontener ma własną sieć, więc localhost oznacza w nim ten sam kontener, a nie Twój komputer ani inną usługę. W naszym teście nc -z localhost 6379 w kontenerze zakończyło się kodem 1 (brak połączenia), a to samo zapytanie do nazwy usługi redis w Compose działało. Gdy jedna usługa łączy się z drugą, używaj nazwy usługi z compose.yaml.
Dlaczego port jest zajęty, a Docker nic nie mówi?
U nas na komputerze port 3000 zajmowała lokalna aplikacja Next.js. Polecenie docker run -p 3000:3000 wystartowało bez błędu, a curl localhost:3000 odpowiedział stroną aplikacji z komputera, nie kontenerem. Tak zachowała się nasza konfiguracja (Colima na macOS); w Docker Desktop lub na Linuksie możesz zobaczyć komunikat o zajętym porcie, bo tego nie testowaliśmy. Wniosek jest ogólny: sprawdzaj odpowiedź, nie tylko to, że kontener się uruchomił. Co zajmuje port, sprawdzisz na macOS i Linuksie poleceniem:
lsof -nP -iTCP:3000 -sTCP:LISTEN
Co to jest „exec format error” i jak ją naprawić?
Kontenery współdzielą jądro hosta, więc obraz zbudowany dla procesorów amd64 nie uruchomi się na komputerze z procesorem arm64 (np. nowy Mac z procesorem Apple) bez emulacji. Typowy objaw to błąd exec format error (pytania o niego mają na Stack Overflow ponad 50 tys. wyświetleń). Rozwiązania: wybrać obraz dla swojej architektury, zbudować obraz wieloplatformowy albo wymusić emulację flagą --platform. U nas działało to tak:
docker run --rm --platform linux/amd64 alpine uname -m
# x86_64
Emulacja działa, ale według dokumentacji Dockera bywa „much slower than native”, więc to rozwiązanie awaryjne.
Co zrobić, gdy Docker zajmuje za dużo miejsca na dysku?
Obrazy, zatrzymane kontenery i wolumeny zbierają się z czasem. Zobacz, ile miejsca zajmują, i posprzątaj:
docker system df
# Images 6 0 326.2MB 60.07MB (18%)
docker system prune -f
# Total reclaimed space: 45.34kB
W naszym teście docker system prune nie usunął wolumenu (musieliśmy usunąć go ręcznie: docker volume rm dane), więc dane z wolumenów są bezpieczne przed zwykłym porządkowaniem. Uważaj jednak na opcje, które usuwają więcej, np. docker compose down -v, bo kasują wolumeny.
Czy znajomość Dockera pomaga w pracy?
Tak, bo Docker jest jednym z narzędzi, które pojawiają się w ofertach pracy i w codziennej pracy zespołów (Docker używa 73,8% zawodowych programistów w Stack Overflow Developer Survey 2025). Nie musisz znać go od pierwszego dnia nauki. Zacznij od uruchomienia gotowego obrazu, potem zbuduj własny z prostej aplikacji, a na końcu poznaj Compose.
Kiedy warto zacząć uczyć się Dockera?
Naszym zdaniem na początku nauki programowania nie musisz instalować Dockera: edytor, język i Git zajmą Ci wystarczająco dużo uwagi, a każde nowe narzędzie to kolejne źródło błędów. Sięgnij po niego wtedy, gdy projekt dostaje bazę danych lub inną usługę pomocniczą albo gdy chcesz, żeby cały zespół miał to samo środowisko. To moment, w którym Docker przestaje być dodatkową nauką, a zaczyna oszczędzać czas.


