Po co w ogóle używać Dockera i kiedy to ma sens
„Działa u mnie” kontra „działa wszędzie tak samo”
Programista uruchamia aplikację na swoim laptopie i wszystko śmiga. Tester próbuje to samo na swoim komputerze i dostaje błąd, bo ma inną wersję Pythona, Node.js albo brakującą bibliotekę. Administrator wrzuca aplikację na serwer i debugowanie zaczyna się od zera. Ten klasyczny problem streszcza się w jednym zdaniu: „u mnie działa”.
Docker rozwiązuje to w prosty sposób: aplikacja jest spakowana razem z całym swoim środowiskiem. Zamiast instalować na każdym komputerze odpowiednie języki, biblioteki i narzędzia, uruchamiasz gotowy obraz. Ten obraz zawiera wszystko, czego aplikacja potrzebuje – system bazowy, zależności, konfigurację startową. Dzięki temu to samo środowisko działa na Windowsie, macOS i Linuksie, o ile tylko jest dostępny Docker.
Intuicja jest prosta: Docker to sposób na powtarzalność. Jeśli zbudujesz obraz raz i udostępnisz go zespołowi, każdy będzie widział aplikację w takiej samej wersji, z tymi samymi bibliotekami i ustawieniami.
Tradycyjna instalacja vs. kontener – lekka „wirtualka”, ale inna
Klasyczny sposób: instalujesz Pythona, Nginxa, bazę danych, biblioteki systemowe bezpośrednio w systemie operacyjnym. Po kilku miesiącach w systemie panuje chaos – nie wiesz, która aplikacja korzysta z której biblioteki, bo wszystko miesza się globalnie. Konflikty wersji to norma.
Kontener Dockera zachowuje się trochę jak lekka wirtualna maszyna, ale nie jest pełną wirtualką. Najważniejsze różnice:
- Wirtualna maszyna ma własny system operacyjny (np. pełny Linux), jądro, sterowniki – jest ciężka w uruchomieniu i zużywa sporo zasobów.
- Kontener współdzieli jądro systemu z gospodarzem. Korzysta z separacji na poziomie procesów i przestrzeni nazw, dzięki czemu jest lekki i startuje w ułamku sekundy.
W praktyce oznacza to, że możesz uruchomić na jednym serwerze dziesiątki kontenerów, a każdy będzie od siebie odizolowany: osobne procesy, sieć, system plików. Łatwiej też je usunąć – kasujesz kontener i po środowisku nie zostaje ślad.
Gdzie Docker naprawdę błyszczy: dev, testy, mikroserwisy, nauka
Najczęstsze, praktyczne zastosowania Dockera wyglądają tak:
- Środowiska deweloperskie – każdy członek zespołu uruchamia tę samą bazę danych, ten sam serwer aplikacyjny i te same zależności w kontenerach.
- Testy automatyczne – pipeline CI/CD (np. GitLab CI, GitHub Actions) odpala aplikację w kontenerze, uruchamia testy i po zakończeniu po prostu sprząta kontenery.
- Mikroserwisy – każda usługa ma swój obraz, własne zależności, można je niezależnie aktualizować i wdrażać.
- Nauka narzędzi – chcesz przetestować nową bazę danych albo serwer? Uruchamiasz gotowy obraz Dockera i nie brudzisz nic w systemie.
Dobry przykład z codziennej pracy: zespół ma komputery z Windowsem, macOS i różnymi dystrybucjami Linuksa. Zamiast pisać trzy różne instrukcje instalacji, przygotowuje jeden obraz Dockera. Każdy włącza kontener przez jedno polecenie i skupia się na kodzie, a nie na konfiguracji.
Kiedy Docker pomaga, a kiedy może przeszkadzać
Docker świetnie nadaje się do:
- aplikacji serwerowych (HTTP API, strony WWW, mikrousługi),
- bazy danych używanej lokalnie do dev/testów,
- projektów, które mają wiele elementów (backend, frontend, kolejka, cache),
- powtarzalnych zadań: np. okresowe generowanie raportów w tym samym środowisku.
Są jednak sytuacje, gdzie Docker tylko komplikuję sprawę:
- Mały jednorazowy skrypt, który ma działać tylko na twoim komputerze, bez zależności – szybciej uruchomić go bez kontenera.
- Aplikacje z ciężkim GUI (np. pełne programy desktopowe) – da się, ale jest to niewygodne dla początkującego.
- Gdy nie znasz jeszcze podstaw systemu (uprawnienia, porty, sieć) – Docker może zamaskować problemy, zamiast je wyjaśnić.
Jeśli zadanie da się łatwo rozwiązać bez Dockera, a nie planujesz wdrożenia na serwer czy pracy w zespole – użycie kontenerów może być przerostem formy nad treścią. Ale w momencie, gdy projekt ma trafić na produkcję lub korzysta z kilku usług naraz, Docker zaczyna realnie oszczędzać czas.
Podstawowe pojęcia po ludzku: obrazy, kontenery, rejestry
Obraz Dockera – „zamrożone” środowisko aplikacji
Obraz Dockera to szablon, z którego powstają kontenery. Można go porównać do pliku ISO systemu operacyjnego albo do zamrożonego stanu środowiska. Zawiera:
- bazowy system (np. minimalny Linux),
- zainstalowane pakiety i zależności,
- pliki aplikacji,
- instrukcję, jaką komendę uruchomić na starcie (np. serwer HTTP).
Obrazy są budowane warstwowo: jeśli zmienisz jeden plik w projekcie, Docker nie musi budować wszystkiego od zera, ale dołoży kolejną warstwę. To przyspiesza kolejne buildy i pozwala współdzielić część warstw między różnymi obrazami.
Kontener – działająca instancja obrazu
Kontener to uruchomiona instancja obrazu. Analogią może być:
- obraz (image) – plik ISO systemu,
- kontener – konkretny, działający system z tego pliku ISO w maszynie wirtualnej.
Ten sam obraz możesz uruchomić wiele razy, tworząc kilka kontenerów. Każdy ma:
- własny system plików (kopię warstw obrazu + swoje zmiany),
- własne procesy,
- własne interfejsy sieciowe i porty.
Gdy kasujesz kontener, zmiany wewnątrz zwykle znikają (chyba że używasz wolumenów). Obraz pozostaje w systemie i możesz tworzyć na jego podstawie kolejne kontenery.
Rejestry obrazów: Docker Hub i spółka
Obrazy muszą gdzieś „mieszkać”, żeby inni mogli je pobierać. Od tego są rejestry obrazów (registry). Najpopularniejszy to:
- Docker Hub – publiczne repozytorium setek tysięcy obrazów, dostępne dla każdego.
Poza tym istnieją m.in.:
- GitHub Container Registry,
- GitLab Container Registry,
- Amazon ECR, Google Artifact Registry, Azure Container Registry,
- prywatne rejestry wewnątrz firm.
Domyślnie, gdy wpisujesz docker pull nginx, Docker kontaktuje się z Docker Hubem, pobiera obraz library/nginx i zapisuje go lokalnie.
Jak czytać nazwy obrazów: repozytorium i tag
Pełna nazwa obrazu ma postać:
rejestr/organizacja/repozytorium:tag
Kilka przykładów:
nginx:latest– domyślne repozytorium (Docker Hub), oficjalny obraz Nginx, taglatest.python:3.11-slim– oficjalny Python, wersja 3.11, wariant „slim”.mycompany/app-backend:1.0.0– własny obraz z tagiem wersji.registry.example.com/team/service:dev– obraz w prywatnym rejestrze.
Tag to etykieta wskazująca wersję lub wariant obrazu. Jeśli tagu brak (np. nginx), Docker przyjmuje domyślnie :latest. To wygodne do nauki, ale na produkcji bywa groźne – latest może się zmienić, a wraz z nim zachowanie aplikacji.
Oficjalne obrazy vs. obrazy społeczności
Na Docker Hubie widać dwa główne typy obrazów:
- Oficjalne – utrzymywane we współpracy z twórcami danego projektu (np.
nginx,mysql,python). Mają przejrzoną dokumentację, często lepszą kontrolę jakości. - Obrazy społeczności – tworzone przez użytkowników, firmy, projekty open source. Mogą być bardzo dobre, ale też przypadkowe.
Przy wybieraniu obrazu warto zwrócić uwagę na:
- oznaczenie „Official Image” na Docker Hubie,
- liczbę pobrań i „gwiazdek”,
- aktualność (data ostatniego pushu),
- opis i link do dokumentacji źródłowej.

Szybki start: instalacja Dockera na Windows, macOS i Linux
Docker Desktop vs Docker Engine – co jest czym
Pod nazwą „Docker” kryją się dwa powiązane elementy:
- Docker Engine – silnik kontenerów działający w tle (daemon). To on faktycznie uruchamia kontenery.
- Docker CLI – narzędzie wiersza poleceń
docker, którym rozmawiasz z silnikiem.
Na Linuksie zwykle instaluje się sam Docker Engine (plus CLI). Na Windows i macOS używa się Docker Desktop, który:
- instaluje silnik kontenerów w lekkiej maszynie wirtualnej,
- zapewnia graficzny interfejs do zarządzania kontenerami,
- konfiguruje integrację z systemem (np. udostępnianie plików).
Wymagania wstępne i uwagi systemowe
Przed instalacją Docker Desktop na Windows/macOS przydaje się szybka kontrola:
- system w wersji wspieranej (np. Windows 10/11 Pro lub Home z WSL2, aktualny macOS),
- włączona w BIOS/UEFI sprzętowa wirtualizacja (Intel VT-x / AMD-V),
- uprawnienia administratora (instalator musi wprowadzić zmiany w systemie).
Na Linuksie kluczowe jest:
- konto z uprawnieniami sudo,
- kompatybilna dystrybucja (np. Ubuntu, Debian, Fedora, CentOS, Arch – każda ma swoją instrukcję),
- czasem dodanie użytkownika do grupy
docker, by nie używać stalesudo.
Instalacja Dockera na Windows i macOS – zarys kroków
Na Windows i macOS wystarczą ogólne kroki:
- Pobierz Docker Desktop ze strony docker.com dla swojego systemu.
- Uruchom instalator i przejdź przez kreatora (zwykle „Next, Next, Finish”).
- Na Windows w razie potrzeby włącz WSL2 i funkcję „Virtual Machine Platform” (instalator może to zasugerować).
- Po zakończeniu instalacji uruchom Docker Desktop i poczekaj, aż ikona „w tacce” przestanie się animować – oznacza to gotowość.
- Otwórz terminal (PowerShell, cmd, Terminal na macOS) i sprawdź
docker --version.
Jeśli wersja się wyświetli, Docker działa. Jeśli pojawia się błąd, zwykle Docker Desktop jeszcze nie wystartował albo polecenie nie jest w ścieżce systemowej (PATH).
Instalacja Dockera na Linuksie – przykład dla Ubuntu
Na Linuksie instalacja różni się w zależności od dystrybucji, ale na Ubuntu często wygląda to z grubsza tak (schematycznie):
- Odinstalowanie starej wersji, jeśli istnieje:
sudo apt-get remove docker docker-engine docker.io containerd runc - Dodanie oficjalnego repozytorium Dockera (skrótowo):
sudo apt-get update sudo apt-get install ca-certificates curl gnupg # dodanie klucza GPG i repozytorium zgodnie z dokumentacją Dockera - Instalacja Dockera:
sudo apt-get install docker-ce docker-ce-cli containerd.io - Dodanie użytkownika do grupy
docker(opcjonalnie):sudo usermod -aG docker $USER
Po ponownym zalogowaniu możesz używać docker bez sudo (jeżeli dodałeś się do grupy).
Test instalacji: docker –version i hello-world
Szybka checklista po instalacji:
- Sprawdzenie wersji:
docker --version - Uruchomienie testowego kontenera:
docker run hello-world
Po komendzie docker run hello-world Docker:
Co faktycznie robi docker run hello-world
Uruchomienie:
docker run hello-worldwykonuje kilka rzeczy krok po kroku:
- Docker sprawdza, czy lokalnie istnieje obraz
hello-world. - Jeśli go nie ma, łączy się z domyślnym rejestrem (Docker Hub) i pobiera obraz.
- Tworzy nowy kontener na podstawie tego obrazu.
- Uruchamia w nim komendę startową (w tym przypadku – prosty program wypisujący komunikaty).
- Po zakończeniu działania proces w kontenerze się kończy, a sam kontener przechodzi w stan „exited”.
To drobiazgowy test: sprawdza pobieranie obrazów, tworzenie kontenerów i komunikację z silnikiem Dockera. Jeśli widzisz przyjazny komunikat „Hello from Docker!”, można przejść dalej.
Pierwszy kontakt z kontenerami: docker run krok po kroku
Najprostszy możliwy kontener
Dobrym startem jest uruchomienie czegoś bardzo prostego, np. obrazu alpine – minimalnej dystrybucji Linuksa:
docker run alpine echo "Cześć z kontenera"Co tutaj się dzieje:
alpine– nazwa obrazu, który ma zostać użyty,echo "Cześć z kontenera"– komenda, którą Docker uruchomi wewnątrz kontenera.
Docker pobierze obraz (jeśli go nie ma), stworzy kontener, uruchomi komendę i wypisze jej wynik w Twoim terminalu. Kontener po zakończeniu działania komendy się wyłączy.
Tryb interaktywny: mała „maszyna” w terminalu
Kontener może działać jak osobna, maleńka maszyna, do której można się „wlogować”:
docker run -it alpine shUżyte opcje:
-i– tryb interaktywny (stdin pozostaje otwarty),-t– pseudoterminal, dzięki czemu powłoka zachowuje się normalnie,sh– powłoka, którą uruchamiamy w kontenerze.
Po tej komendzie trafisz do promptu wewnątrz kontenera, np.:
/ #Możesz uruchamiać komendy Linuksa:
uname -a
ls
cat /etc/os-release
Aby wyjść, wpisz exit lub użyj skrótu Ctrl+D. Kontener zakończy działanie.
Lista działających i zakończonych kontenerów
Żeby zobaczyć, co teraz żyje w Dockerze, przydają się dwie komendy:
- tylko działające kontenery:
docker ps - wszystkie, także zakończone:
docker ps -a
W tabeli zobaczysz m.in. identyfikator kontenera, użyty obraz, komendę startową, status oraz mapowanie portów.
Zatrzymywanie, uruchamianie i usuwanie kontenerów
Jeśli kontener nadal działa w tle (np. serwer), można nim sterować jak procesem:
- zatrzymanie działającego kontenera:
docker stop NAZWA_LUB_ID - ponowne uruchomienie zatrzymanego (status
Exited):docker start NAZWA_LUB_ID - usunięcie kontenera:
docker rm NAZWA_LUB_ID
Nazwę lub ID możesz wziąć z docker ps -a. Docker sam nadaje losowe nazwy, jeśli nie podasz swojej (czasem dość zabawne połączenia słów).
Daemon a jednorazowe procesy – różne style pracy kontenera
W praktyce spotykasz dwa typy kontenerów:
- krótkotrwałe – uruchamiają jedną komendę, po jej zakończeniu wychodzą (np. migrują bazę, generują raport),
- długotrwałe – uruchamiają usługę (serwer HTTP, bazę danych) i działają, dopóki ich nie zatrzymasz.
W kolejnych przykładach skupimy się głównie na kontenerach z usługami, bo to najczęstszy scenariusz przy aplikacjach webowych.

Praca z obrazami: docker pull, docker images, docker rmi
Pobieranie obrazów: docker pull
Obraz można pobrać „na zapas”, zanim zacznie się z niego korzystać:
docker pull nginx:latestTa komenda:
- łączy się z rejestrem (tu: Docker Hub),
- pobiera warstwy obrazu,
- zapisuje go lokalnie, tak by kolejne uruchomienia były szybkie.
Jeśli pominiesz tag, Docker przyjmie :latest:
docker pull nginxPrzegląd lokalnych obrazów
Po kilku testach w systemie pojawia się kolekcja obrazów. Można ją podejrzeć:
docker imagesWynik to tabela z m.in. nazwą repozytorium, tagiem, rozmiarem oraz ID obrazu. To Twój lokalny magazyn klocków, z których powstają kontenery.
Usuwanie niepotrzebnych obrazów: docker rmi
Z czasem obrazy zaczynają zajmować sporo miejsca. Jeżeli coś już nie jest potrzebne, można to usunąć:
docker rmi nginx:latestlub używając ID obrazu:
docker rmi ID_OBRAZUObrazów używanych aktualnie przez działające kontenery nie da się usunąć, dopóki nie zatrzymasz i nie usuniesz tych kontenerów. Czasami wygodny jest też automatyczny „odkurzacz”:
docker image prunektóry usuwa obrazy, do których nie ma już żadnych referencji (tzw. „dangling images”).
Sprawdzanie szczegółów obrazu
Jeśli chcesz podejrzeć, co dokładnie zawiera obraz (np. jakie ma zmienne środowiskowe, jaki jest domyślny port), możesz użyć:
docker inspect nginx:latestTo pokaże spory JSON z metadanymi. Przydaje się, gdy trzeba sprawdzić parametr bez zaglądania do dokumentacji na stronie rejestru.
Twoja pierwsza aplikacja w Dockerze w 15 minut
Plan: prosty serwer HTTP w kontenerze
Najprostszy, a jednocześnie bardzo praktyczny przykład to miniaturowa aplikacja webowa. Można ją napisać w Pythonie, Node.js, Go czy czymkolwiek innym. Dla czytelności użyjemy Pythona z wbudowanym modułem http.server, dzięki czemu nie trzeba instalować dodatkowych bibliotek.
Przygotowanie katalogu projektu
Najpierw utwórz katalog na dysku, w którym będą pliki aplikacji:
mkdir moja-pierwsza-aplikacja
cd moja-pierwsza-aplikacja
Dodaj prosty plik HTML, który będziemy serwować:
echo "<h1>Docker działa!</h1>" > index.htmlW katalogu powinien teraz leżeć plik index.html z prostą zawartością.
Serwer HTTP „na żywo” w kontenerze
Zamiast instalować serwer na swoim systemie, można skorzystać z gotowego obrazu Pythona i uruchomić serwer bezpośrednio w kontenerze:
docker run --rm -it
-p 8000:8000
-v "$PWD":/usr/src/app
-w /usr/src/app
python:3.11-slim
python -m http.server 8000Rozbijmy to na części:
--rm– po wyłączeniu kontenera usuń go automatycznie (nie będzie śmieci),-p 8000:8000– mapowanie portów: port 8000 kontenera jest widoczny jako port 8000 na Twoim komputerze,-v "$PWD":/usr/src/app– podmontowanie bieżącego katalogu jako/usr/src/appw kontenerze,-w /usr/src/app– ustawienie katalogu roboczego kontenera na/usr/src/app,python:3.11-slim– obraz bazowy, który ma zostać użyty,python -m http.server 8000– komenda startowa uruchamiająca serwer HTTP na porcie 8000.
Teraz w przeglądarce wejdź na adres:
http://localhost:8000Powinieneś zobaczyć napis „Docker działa!”. Serwer fizycznie działa w kontenerze, a Ty tylko przeglądasz jego wynik.
Porty: jak udostępnić kontener na zewnątrz
Bez opcji -p kontener działałby „w środku” i nie byłby dostępny z Twojej przeglądarki. Parametr:
-p HOST_PORT:CONTAINER_PORTspina port Twojego komputera (hosta) z portem wewnątrz kontenera. Praktyczne przykłady:
-p 8080:80– ruch nalocalhost:8080trafia na kontenerowy port 80 (typowe przy serwerach WWW),-p 5432:5432– wystawia bazę PostgreSQL na ten sam port, co zwykle.
W środowiskach produkcyjnych często to reverse proxy (np. Nginx) łączy się z kontenerami po wewnętrznej sieci Dockera, ale na start -p w zupełności wystarcza.
Udostępnianie plików: wolumeny i bind mounty
Opcja -v podmontowała Twój katalog do środka kontenera. Dzięki temu:
- pliki edytujesz lokalnie w ulubionym edytorze,
- serwer w kontenerze od razu widzi zmiany (bo to ten sam katalog).
Taki sposób montowania (mapowanie konkretnego katalogu z hosta) nazywa się bind mountem. Istnieją też wolumeny zarządzane przez Dockera, które nadają się do trwałych danych, np. baz. Do pierwszych eksperymentów w zupełności wystarczy bind mount.
Automatyczne restartowanie i tryb „daemonowy”
Serwer przed chwilą działał na pierwszym planie – dopóki nie przerwiesz go w terminalu (Ctrl+C). Można też uruchomić kontener w tle:
docker run -d --name moj-serwer
-p 8000:8000
-v "$PWD":/usr/src/app
-w /usr/src/app
python:3.11-slim
python -m http.server 8000Nowe elementy:
-d– tryb „detached”, czyli uruchom kontener w tle,--name moj-serwer– nadanie własnej nazwy kontenerowi (łatwiej się nim zarządza).
Teraz można kontenerem sterować:
docker ps # zobacz, czy działa
docker stop moj-serwer # zatrzymaj
docker start moj-serwer # uruchom ponownie
docker rm moj-serwer # usuń, gdy już niepotrzebny
Dockerfile od kuchni: jak opisać swoją aplikację
Dlaczego sam docker run to za mało
Przed chwilą korzystałeś z gotowego obrazu python:3.11-slim i ręcznie montowałeś kod z hosta. W projekcie, który ma wylądować na serwerze, taki układ się nie sprawdza. Potrzebny jest powtarzalny przepis na obraz aplikacji: krok po kroku, jak ją zbudować i uruchomić. Tym przepisem jest plik Dockerfile.
Najprostszy możliwy Dockerfile
Pozostańmy przy prostym serwerze Pythona, ale tym razem umieśćmy wszystko w obrazie. W katalogu projektu (moja-pierwsza-aplikacja) utwórz plik app.py:
cat << 'EOF' > app.py
from http.server import SimpleHTTPRequestHandler
from socketserver import TCPServer
PORT = 8000
handler = SimpleHTTPRequestHandler
with TCPServer(("", PORT), handler) as httpd:
print(f"Serwer działa na porcie {PORT}")
httpd.serve_forever()
EOFTeraz stwórz plik Dockerfile:
FROM python:3.11-slim
WORKDIR /app
COPY . /app
EXPOSE 8000
CMD ["python", "app.py"]Co oznaczają instrukcje w Dockerfile
Każda linijka pełni tu konkretną rolę:
FROM python:3.11-slim– wybór obrazu bazowego. To punkt startu: system + zainstalowany Python.WORKDIR /app– ustawienie katalogu roboczego wewnątrz obrazu. Wszystkie kolejne instrukcje będą wykonywane względem/app.COPY . /app– skopiowanie bieżącego katalogu (kodu aplikacji) do katalogu/appwewnątrz obrazu.
Budowanie własnego obrazu: docker build
Gdy plik Dockerfile jest gotowy, można z niego stworzyć własny obraz. Będąc w katalogu projektu, uruchom:
docker build -t moja-pierwsza-aplikacja:latest .Parametry układają się w prosty schemat:
-t moja-pierwsza-aplikacja:latest– nadanie nazwy (i tagu) budowanemu obrazowi,.– kontekst budowania, czyli katalog, z którego Docker ma wziąć pliki (tu: bieżący katalog).
Docker przeanalizuje Dockerfile, krok po kroku zbuduje obraz i zapisze go lokalnie. Po chwili nowa pozycja powinna pojawić się w:
docker imagesUruchamianie własnego obrazu
Gotowego obrazu używa się już tak samo jak oficjalnych z Docker Hub. Przykład:
docker run --rm -p 8000:8000 moja-pierwsza-aplikacja:latestPo kilku sekundach serwer powinien nasłuchiwać na porcie 8000. Wejdź w przeglądarce na:
http://localhost:8000Żadnego montowania katalogów, żadnego ręcznego instalowania zależności – wszystko siedzi w obrazie. W praktyce oznacza to, że ten sam obraz możesz zanieść na inny komputer (albo do chmury) i aplikacja zachowa się tak samo.
Warstwy obrazu i cache: dlaczego kolejny build jest szybszy
Obraz powstaje z warstw, a każda instrukcja w Dockerfile tworzy kolejną. Gdy budujesz obraz ponownie, Docker sprawdza, które warstwy się nie zmieniły, i używa ich z pamięci podręcznej.
Prosty schemat:
- zmienisz tylko jedną linijkę w
app.py– przebudowana będzie wyłącznie warstwa zCOPYi wszystko po niej, - podmienisz wersję Pythona w
FROM– invalidate’ujesz (unieważniasz) cały cache, build potrwa dłużej.
Dzięki temu w codziennej pracy kolejne przebudowy są szybkie, o ile Dockerfile jest ułożony sensownie.
Jak układać Dockerfile, żeby budował się szybko
Kolejność instrukcji ma znaczenie. Warto zacząć od rzeczy, które rzadko się zmieniają, a dopiero na końcu kopiować kod aplikacji. Typowy wzorzec przy większych projektach Pythonowych wygląda na przykład tak:
FROM python:3.11-slim
WORKDIR /app
# Najpierw zależności (rzadziej się zmieniają)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Dopiero potem reszta kodu (częściej się zmienia)
COPY . .
EXPOSE 8000
CMD ["python", "app.py"]Dzięki temu drobna poprawka w kodzie nie wymusza ponownego instalowania paczek Pythona przy każdym buildzie.
Ignorowanie zbędnych plików: .dockerignore
Podczas budowania Docker wysyła do demona Dockera cały kontekst – czyli pliki z katalogu, który wskazałeś (zwykle .). Jeśli w projekcie są logi, katalog node_modules czy inne śmieci, build staje się wolniejszy, a obraz rośnie bez potrzeby.
Rozwiązaniem jest plik .dockerignore, podobny do .gitignore. Przykład:
.git
.gitignore
__pycache__/
*.pyc
.env
*.log
node_modules/
dist/
build/Te wpisy mówią Dockerowi, że plików i katalogów z listy nie trzeba brać pod uwagę przy budowaniu obrazu. Zyskujesz mniejszy kontekst, szybszy upload do demona i czystszy obraz.
Zmienne środowiskowe i konfiguracja: ENV i ARG
W obrazie wygodniej jest trzymać mechanizm aplikacji niż jej konfigurację. Ustawienia (hasła, adresy baz, tryb debug) dobrze jest wstrzykiwać przez zmienne środowiskowe.
W Dockerfile można ustawić domyślne wartości:
ENV APP_ENV=production
ENV APP_PORT=8000W Pythonie masz do nich dostęp przez os.environ. Z kolei podczas uruchamiania kontenera wartości można nadpisać:
docker run -e APP_ENV=dev -e APP_PORT=5000
-p 5000:5000 moja-pierwsza-aplikacja:latestIstnieją też zmienne ARG, używane tylko podczas budowania obrazu. Przykład:
ARG PYTHON_VERSION=3.11-slim
FROM python:${PYTHON_VERSION}W czasie builda możesz podać inną wersję:
docker build --build-arg PYTHON_VERSION=3.10-slim
-t moja-apka:py310 .Instrukcje CMD i ENTRYPOINT: co naprawdę startuje kontener
Na końcu Dockerfile zwykle znajdują się dwie instrukcje odpowiedzialne za komendę startową:
CMD– domyślna komenda, którą można łatwo nadpisać przydocker run,ENTRYPOINT– „główne wejście”, trudniej nadpisywalne, przydaje się w narzędziach CLI.
Przykładowy zestaw:
ENTRYPOINT ["python", "app.py"]
CMD ["--port", "8000"]Po uruchomieniu kontenera Docker złoży z tego jedną komendę:
python app.py --port 8000Jeśli wywołasz:
docker run moja-pierwsza-aplikacja:latest --port 9000to część z CMD zostanie zastąpiona przez Twoje argumenty, ale ENTRYPOINT pozostanie bez zmian. W zwykłych aplikacjach webowych często wystarcza samo CMD. ENTRYPOINT świeci pełnym blaskiem przy narzędziach typu linia poleceń (np. własny wrapper do migracji bazy).
Obrazy „ciężkie” i „lekkie”: dobór bazowego FROM
Za kulisami obrazy to po prostu systemy plików z niezbędnymi komponentami. Można użyć pełnego systemu (np. ubuntu), można też postawić na wersje „odchudzone” (slim, alpine).
Przykładowe warianty:
python:3.11– pełny obraz Debiana z Pythonem, wygodny do debugowania, ale większy,python:3.11-slim– przycięty Debian, dobry kompromis między wagą a wygodą,python:3.11-alpine– bardzo mały, oparty na Alpine Linux, czasem wymaga dopisywania dodatkowych pakietów systemowych.
W praktycznych projektach często startuje się na slim, a po opanowaniu zależności schodzi niżej do jeszcze lżejszych baz, jeśli jest taka potrzeba.
Minimalne obrazy produkcyjne: multi-stage build
Podczas budowania aplikacji potrzebne są narzędzia, których w środowisku produkcyjnym w ogóle nie trzeba. Po co wozić z aplikacją kompilatory i build toolsy, jeśli kontener ma tylko serwować HTTP?
Rozwiązaniem jest multi-stage build, czyli budowanie w kilku etapach. Prosty przykład (Node.js, ale idea jest uniwersalna):
# Etap 1: build
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Etap 2: obraz runtime
FROM node:20-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
RUN npm install --production
EXPOSE 3000
CMD ["node", "dist/server.js"]W pierwszym etapie powstaje skompilowana aplikacja. W drugim – budowany jest finalny, dużo mniejszy obraz, który zawiera tylko to, co potrzebne do uruchomienia. Ten wzorzec świetnie sprawdza się przy frontendach, aplikacjach Javy, Go, a także przy Pythonie (np. gdy trzeba najpierw skompilować rozszerzenia w C).
Debugowanie obrazu: interaktywna powłoka w kontenerze
Czasem coś nie działa i trzeba zerknąć do środka obrazu: sprawdzić ścieżki, prawa do plików, zmienne środowiskowe. Najprostszy sposób to uruchomienie powłoki w kontenerze:
docker run --rm -it moja-pierwsza-aplikacja:latest /bin/shalbo, jeśli jest dostępny Bash:
docker run --rm -it moja-pierwsza-aplikacja:latest /bin/bashPo wejściu do takiego środka można używać klasycznych komend Linuksa: ls, cat, env, sprawdzić, czy plik naprawdę istnieje tam, gdzie zakładałeś, czy port jest odsłuchiwany, czy logi pojawiają się w odpowiednim miejscu.
Logi z kontenera: gdzie trafia output aplikacji
Standardowy output (to, co printujesz albo wypisujesz na stdout) nie znika w próżni. Docker przechwytuje te dane i traktuje jako logi kontenera. Z zewnątrz widać je tak:
docker logs moj-serwerMożna też śledzić je na żywo:
docker logs -f moj-serwerWarto więc, by Twoja aplikacja logowała na standardowe wyjście, a nie do plików w środku kontenera. Operatory logów (czyli systemy, które je zbierają) później i tak przyczepią się do tego strumienia.
Tagowanie obrazów: wersje, środowiska, architektury
Nazwa obrazu z początku może wyglądać niewinnie (moja-pierwsza-aplikacja:latest), ale z czasem dobrze jest wprowadzić porządek w tagach. Kilka praktycznych konwencji:
moja-apka:1.0.0– powiązanie z wersją aplikacji,moja-apka:1.0.0-dev,moja-apka:1.0.0-prod– warianty na różne środowiska,moja-apka:1.0.0-amd64,moja-apka:1.0.0-arm64– różne architektury procesorów (przydatne przy Raspberry Pi czy M1/M2).
Podczas builda tag można nadać lub zmienić:
docker build -t moja-apka:1.0.0 .A istniejący obraz otagować ponownie:
docker tag moja-apka:1.0.0 moja-apka:latestWysyłanie obrazów do rejestru: docker push
Lokalny obraz jest użyteczny tylko na Twoim komputerze. Aby inne maszyny (lub serwery CI/CD) mogły go pobrać, trzeba go umieścić w rejestrze – np. Docker Hub czy GitHub Container Registry.
Ogólny schemat wygląda następująco:
- Tworzysz konto w wybranym rejestrze (np. Docker Hub).
- Logujesz się z linii komend:
docker login - Tagujesz obraz tak, aby zawierał nazwę rejestru i użytkownika:
docker tag moja-pierwsza-aplikacja:latest nazwa_uzytkownika/moja-pierwsza-aplikacja:latest - Wysyłasz obraz:
docker push nazwa_uzytkownika/moja-pierwsza-aplikacja:latest
Od tej chwili z innej maszyny wystarczy:
docker pull nazwa_uzytkownika/moja-pierwsza-aplikacja:latest
docker run -p 8000:8000 nazwa_uzytkownika/moja-pierwsza-aplikacja:latestI ten sam serwer HTTP uruchomi się w kilka sekund, bez kopiowania kodu przez pendrive’a czy SSH.
Bezpieczeństwo obrazów: zależności i użytkownik w kontenerze
Kontener nie jest magiczną tarczą ochronną. Jeśli aplikacja ma luki, kontener je po prostu niesie ze sobą. Warto zwracać uwagę na kilka prostych aspektów:
- regularnie odświeżać obrazy bazowe (nowszy
python:3.11-slimto zwykle załatane biblioteki systemowe), - instalować wyłącznie potrzebne pakiety (mniej pakietów = mniejsza powierzchnia ataku),
- nie uruchamiać aplikacji jako
rootw kontenerze, jeśli nie jest to konieczne.
Przykład uruchomienia aplikacji jako nieuprzywilejowany użytkownik:
FROM python:3.11-slim
WORKDIR /app
RUN useradd -m appuser
COPY . /app
RUN chown -R appuser:appuser /app
USER appuser
EXPOSE 8000
CMD ["python", "app.py"]Dzięki temu ewentualne przejęcie kontroli nad procesem daje atakującemu znacznie mniejsze możliwości niż w przypadku kontenera działającego jako root.
Kluczowe Wnioski
- Docker rozwiązuje problem „u mnie działa” przez spakowanie aplikacji razem z całym środowiskiem (system bazowy, biblioteki, konfiguracja), dzięki czemu ten sam obraz działa tak samo na Windowsie, macOS i Linuksie.
- Kontener jest lekką alternatywą dla wirtualnej maszyny: współdzieli jądro systemu z gospodarzem, startuje w ułamku sekundy i zużywa mniej zasobów, a jednocześnie zapewnia izolację procesów, sieci i systemu plików.
- Docker szczególnie pomaga w złożonych projektach: środowiskach deweloperskich, testach automatycznych, mikroserwisach i szybkiej nauce nowych narzędzi, gdzie kluczowa jest powtarzalność i łatwość sprzątania po eksperymentach.
- Nie każde zadanie wymaga kontenera – małe, jednorazowe skrypty czy aplikacje z ciężkim interfejsem graficznym często szybciej i prościej uruchomić bez Dockera, zwłaszcza gdy projekt nie trafi na serwer ani do zespołu.
- Obraz Dockera to „zamrożone” środowisko aplikacji budowane warstwowo; zmiana jednego pliku nie wymaga przebudowy wszystkiego, co przyspiesza kolejne buildy i pozwala współdzielić wspólne warstwy między różnymi obrazami.
- Kontener jest działającą instancją obrazu: możesz uruchomić wiele kontenerów z jednego obrazu, każdy z własnym systemem plików i procesami, a po ich usunięciu środowisko znika bez śladu w systemie gospodarza.
Źródła
- Docker Overview. Docker Inc. – Podstawowe pojęcia: obrazy, kontenery, rejestry, zastosowania Dockera
- Docker Engine Overview. Docker Inc. – Architektura Dockera, różnice kontenery vs tradycyjne uruchamianie aplikacji
- Kubernetes Documentation – Containers. Cloud Native Computing Foundation – Definicje kontenerów, izolacja procesów, porównanie z VM






