Po co Docker Compose, skoro jest sam Docker?
Ręczne docker run kontra deklaratywny docker-compose.yml
Uruchomienie pojedynczego kontenera za pomocą docker run jest proste i szybkie. Problem zaczyna się, gdy aplikacja składa się z kilku elementów: backend, baza danych, cache, worker, narzędzia pomocnicze. Wtedy linie poleceń robią się długie, trudne do odtworzenia i podatne na błędy.
Docker Compose wprowadza podejście deklaratywne: konfiguracja całego stosu trafia do jednego pliku docker-compose.yml. Zamiast zapamiętywać dziesięć komend docker run, wystarczy jeden plik i jedno polecenie:
docker compose up
Plik jest wersjonowany razem z kodem, można go przeglądać, recenzować w code review, modyfikować i porównywać między gałęziami. To różnica podobna do ręcznego wywoływania komend SQL w konsoli vs posiadanie migracji w plikach – oba podejścia działają, lecz jedno dużo lepiej skaluje się w czasie i w zespole.
Kiedy wystarczy pojedynczy kontener, a kiedy potrzebny jest stos
Sam Docker i docker run sprawdzają się, gdy:
- testujesz pojedynczy obraz (np. gotowy serwer Redis lub Nginx),
- uruchamiasz tymczasowe narzędzia typu
alpinelubbusyboxdo jednorazowych zadań, - masz prostą aplikację monolityczną bez zależności (np. same statyczne pliki).
Docker Compose jest nieporównanie wygodniejszy, gdy dochodzi choćby jedna zależność: baza danych, broker wiadomości, osobny frontend, osobny worker do zadań w tle. Nawet app + db to już sensowny powód, aby przejść na Compose, bo pojawiają się kwestie:
- ustawienia sieci (hosty, porty, izolacja),
- trwałość danych (wolumeny),
- konfiguracja zmiennych środowiskowych dla kilku usług naraz,
- kolejność startu i powiązania
depends_on.
Im więcej komponentów, tym większa różnica w wygodzie i powtarzalności między zestawem ręcznych komend a jednym opisanym stosie w Compose.
Typowe zastosowania Docker Compose w praktyce
Compose najczęściej pojawia się w kilku scenariuszach:
- Lokalne środowisko developerskie – każdy developer ma identyczny stos uruchamiany jednym poleceniem; nie trzeba ręcznie instalować baz, MQ, a nawet JDK czy Node na hoście.
- Testy integracyjne i e2e – pipeline CI może przed testami podnieść cały stos w Compose, odpalić testy i potem go zwinąć.
- Demo dla klienta – łatwo uruchomić „mini-produkcję” na jednym serwerze czy laptopie, bez budowania pełnej infrastruktury.
- Środowisko QA / UAT – zespół testerski działa na spójnej konfiguracji, która jest bliska lokalnemu devowi.
W tych scenariuszach Compose jest szybszy do wdrożenia niż pełna orkiestracja, a jednocześnie daje znacznie więcej porządku niż luźne kontenery uruchamiane ręcznie.
Gdzie kończy się Compose, a zaczyna Kubernetes lub Swarm
Docker Compose nie zastępuje pełnoprawnej platformy orkiestracyjnej. Nie zajmuje się automatycznym skalowaniem, samonaprawianiem klastrów, rolloutami, rolling update’ami na dziesiątkach węzłów. Jest zaprojektowany przede wszystkim do:
- pracy na jednym hoście (lokalnie lub serwer testowy),
- prostego skalowania w granicach jednego serwera (
replicasprzezdocker compose up --scale), - scenariuszy dev / test / demo / mała produkcja.
Gdy pojawia się potrzeba:
- automatycznego restartu usług na innych serwerach po awarii hosta,
- zaawansowanych polityk autoskalowania,
- dynamicznego routingowania ruchu i service discovery w skali wielu maszyn,
na scenę wchodzi Kubernetes (lub w mniejszej skali Swarm). Compose i K8s często współistnieją: Compose na lokalnych laptopach, Kubernetes na produkcji, a konfiguracje są do siebie w pewnym stopniu mapowane.
Krótkie przypomnienie podstaw Dockera – tylko to, co potrzebne
Obraz, kontener, wolumen – układanka, którą wykorzystuje Compose
Zrozumienie trzech podstawowych pojęć bardzo ułatwia korzystanie z Docker Compose:
- Obraz (image) – szablon systemu plików i metadanych. Tworzy się go zwykle przez
docker buildna podstawieDockerfile. Jest niezmienny – jak snapshot. - Kontener – działająca instancja obrazu. Ma własny system plików (warstwowy), swoje procesy, konfigurację sieci.
- Wolumen (volume) – miejsce na dane trwalsze niż życie kontenera. Używany dla baz danych, logów, uploadów.
Docker Compose nie zastępuje tych elementów, tylko je opisuje i łączy: określa, z jakich obrazów mają powstać kontenery, jakie wolumeny mają być podpięte i w jakiej sieci mają się komunikować.
Minimum komend Dockera, które porządkuje Compose
Przy pracy bez Compose często używa się kilku podstawowych komend:
docker build -t myapp .– budowa obrazu z bieżącego katalogu,docker run -d -p 8080:80 --name myapp myapp– uruchomienie kontenera w tle,docker ps– lista działających kontenerów,docker logs myapp– podgląd logów kontenera,docker stop myapp/docker rm myapp– zatrzymanie i usunięcie kontenera.
Compose agreguje wiele takich operacji. Na przykład docker compose up --build potrafi jednocześnie zbudować obrazy, stworzyć sieci, uruchomić kilka kontenerów i podpiąć wolumeny. Znajomość „gołych” komend pomaga zrozumieć, co Compose robi pod spodem i ułatwia diagnozowanie problemów.
Sieci i porty w Dockerze bez Compose
Domyślnie Docker tworzy sieć typu bridge. Kontenery uruchomione w tej sieci widzą się po adresach IP, ale nie mają dostępu z zewnątrz, dopóki nie opublikuje się portów.
Standardowe mapowanie portu bez Compose wygląda tak:
docker run -d -p 8080:80 --name web nginx
Port 80 wewnątrz kontenera jest wtedy dostępny na porcie 8080 hosta. Przy kilku usługach i różnych portach robi się to mało czytelne. Compose opisuje te mapowania w zorganizowany sposób i dodatkowo nadaje usługom nazwy hostów w obrębie sieci, dzięki czemu backend może odwoływać się do db:5432, a nie do zmiennego adresu IP.
Dlaczego te podstawy są kluczowe dla diagnozy problemów w Compose
Wiele błędów przy korzystaniu z Compose sprowadza się do klasycznych problemów Dockera: brak wolumenu, źle zmapowany port, konflikt nazw kontenerów, kontener wychodzący z błędem jeszcze przed przyłączeniem się do sieci. Gdy wiadomo, jak ręcznie sprawdzić:
docker ps– czy kontener faktycznie działa,docker logs nazwa_uslugi-1– jakie są logi przy starcie,docker inspect– jak wygląda konfiguracja sieci i wolumenów,
diagnostyka z Compose staje się przewidywalna. W praktyce wielu problemów da się uniknąć, myśląc o usługach Compose jak o normalnych kontenerach Dockera zebranych w grupę.

Anatomia pliku docker-compose.yml – składniki i różnice wersji
Podstawowa struktura: services, volumes, networks
Współczesny Compose w wersji pluginu CLI (Docker Compose V2) nie wymaga już sekcji version w pliku. Klasyczny szkielet wygląda tak:
services:
app:
image: myapp:latest
db:
image: postgres:15
volumes:
db-data:
networks:
default:
driver: bridge
Sekcja services definiuje kontenery, które mają zostać uruchomione, z ich konfiguracją. Sekcja volumes rezerwuje nazwy i parametry wolumenów, a networks – sieci. Jeżeli sieci nie zdefiniujesz, Compose i tak utworzy domyślną sieć o nazwie opartej na katalogu projektu, więc można zacząć minimalistycznie.
Kluczowe pola usług: image, build, ports, volumes, environment
Definicja jednej usługi składa się z kilku często używanych pól. Przykład:
services:
app:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/app
depends_on:
- db
db:
image: postgres:15
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=app
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
Najważniejsze elementy:
image– nazwa obrazu z rejestru (np. Docker Hub) lub lokalnego. Używane, gdy nie budujesz obrazu samodzielnie.build– katalog lub parametry budowania obrazu na podstawie Dockerfile. Często ustawiane na., gdy Dockerfile leży w katalogu projektu.ports– mapowanie portów w formaciehost:kontener. Można podawać jako string lub osobne pola.volumes– podpinanie wolumenów i katalogów:nazwa_wolumenu:ścieżka_w_kontenerzelub./kod:/appdla bind mount.environment– zmienne środowiskowe dla kontenera, przydatne do przekazywania konfiguracji (hasła, URL-e, feature flagi).depends_on– zależności pomiędzy usługami. Informuje Compose o kolejności startu, ale nie gwarantuje dostępności aplikacyjnej (do tego służą zdrowotne checki lub retry w kodzie).command– nadpisanie domyślnej komendy startowej z obrazu.restart– polityka restartu jak w czystym Dockerze, np.always,on-failure.
Minimalny a rozbudowany przykład – jak rośnie złożoność
Minimalny plik pozwalający uruchomić prostą aplikację webową:
services:
web:
image: nginx:alpine
ports:
- "8080:80"
Ten plik zawiera jednoznaczną definicję usługi, ale nic ponad absolutne minimum. Dla porównania, bardziej realny stos ma już konfigurację sieci, wolumenów i środowisk:
services:
frontend:
build: ./frontend
ports:
- "3000:3000"
environment:
- API_URL=http://backend:8000
depends_on:
- backend
backend:
build: ./backend
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/app
volumes:
- ./backend:/app
depends_on:
- db
db:
image: postgres:15
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=app
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
Struktura jest taka sama, tylko ilość konfiguracji rośnie. Dzięki temu można precyzyjnie kontrolować sposób działania całego stosu, od kodu po dane i sieć.
docker-compose vs docker compose – narzędzie a plugin
Przez lata istniały dwa narzędzia: osobny binarny docker-compose (V1) oraz nowszy plugin docker compose (V2), integrujący Compose z głównym CLI Dockera. Obecnie rekomendowany jest właśnie plugin:
| Cecha | docker-compose (V1) | docker compose (V2) |
|---|---|---|
| Instalacja | Osobny plik binarny | Plugin wbudowany w Docker Desktop / pakiet Dockera |
| Składnia wywołania | docker-compose up | docker compose up |
| Wsparcie | Tryb utrzymania, bez rozwoju | Aktywnie rozwijany, nowe funkcje |
| Plik YAML | Wymagał sekcji version | Może działać bez version |
W nowych projektach lepiej używać docker compose. Składnia pliku jest w praktyce taka sama, różni się tylko sposób instalacji i wywołania narzędzia.
Pierwszy działający stos w 15 minut: backend + baza danych
Założenia przykładowego stosu
Dla konkretu przyjmijmy prosty scenariusz:
- backend HTTP (np. Python / Node / Go) nasłuchujący na porcie
8000wewnątrz kontenera, - baza danych Postgres 15,
- lokalny development – kod ma być montowany z hosta, żeby nie przebudowywać obrazu przy każdej zmianie.
Takie połączenie jest dość uniwersalne: da się podmienić język backendu bez zmiany ogólnej struktury docker-compose.yml, a baza Postgresa zostaje ta sama. Różnica między „gołym” Dockerem a Compose zaczyna być widoczna przy zależnościach i konfiguracji.
Struktura katalogów dla prostego backendu
Dla przejrzystości przyjmijmy układ:
projekt-compose/
backend/
Dockerfile
app.py # lub index.js, main.go itd.
requirements.txt
docker-compose.yml
Backend może być dowolny. Najważniejsze, żeby:
- miał Dockerfile,
- pobierał konfigurację bazy z
DATABASE_URLlub osobnych zmiennych (DB_HOST,DB_USERitd.), - potrafił uruchomić prosty endpoint testowy (np.
/health).
Przykładowy Dockerfile backendu
Dla konkretu – prosty backend w Pythonie z FastAPI/Flaskiem, ale analogicznie można wstawić Node/Express lub coś innego:
# backend/Dockerfile
FROM python:3.11-slim
WORKDIR /app
# Instalacja zależności
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Kopia kodu
COPY . .
# Zmienna na port (opcjonalne)
ENV PORT=8000
# Domyślna komenda (np. Uvicorn / Flask)
CMD ["python", "app.py"]
Dockerfile nie musi znać nic o Compose. Buduje po prostu obraz, który uruchamia backend, a całym „orchestration” zajmie się docker compose.
docker-compose.yml: backend + Postgres
Teraz plik Compose w katalogu głównym:
services:
backend:
build: ./backend
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgres://app_user:app_pass@db:5432/app_db
depends_on:
- db
volumes:
- ./backend:/app
db:
image: postgres:15
environment:
- POSTGRES_USER=app_user
- POSTGRES_PASSWORD=app_pass
- POSTGRES_DB=app_db
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
Backend backend widzi bazę pod nazwą hosta db, ponieważ Compose tworzy wspólną sieć. Z punktu widzenia kodu nie ma potrzeby znać adresu IP – nazwa usługi w Compose staje się DNS-em.
Start stosu w praktyce: od zera do działającego endpointu
W katalogu projektu wystarczy:
docker compose up --build
Różnice względem pojedynczych komend Dockera:
- Obrazy dla wszystkich usług, które mają
build, zostaną zbudowane w odpowiedniej kolejności. - Postgres dostanie własny wolumen
db-data, więc dane nie znikną po restarcie kontenerów. - Tworzona jest sieć, w której backend i baza komunikują się po nazwach usług.
Po starcie aplikacja backendowa powinna być dostępna pod http://localhost:8000. Gdy któryś kontener się wywali, logi można podejrzeć na dwa sposoby:
docker compose logs backend
docker compose logs db
albo w trybie ciągłym:
docker compose logs -f backend
Zmiany w kodzie bez przebudowy obrazu
Dzięki:
volumes:
- ./backend:/app
kod hosta jest automatycznie widoczny w kontenerze. Różnica w workflow w porównaniu z klasycznym Dockerem:
- bez bind mounta – każda zmiana kodu =
docker build+ restart kontenera, - z bind mountem – edycja pliku na hostcie od razu widoczna w kontenerze; wystarczy, że aplikacja ma hot-reload (np.
uvicorn --reload,nodemon).
Dla developera przypomina to lokalny run bez kontenerów, ale środowisko (baza, zależności systemowe, wersje bibliotek) jest zamknięte w obrazie Dockera.
Sieci, wolumeny i konfiguracja – przegląd trzech filarów
Przy pojedynczym kontenerze te trzy elementy bywają rozproszone po wielu flagach w docker run. Compose grupuje je w jednym pliku w przewidywalnych sekcjach: services, volumes i networks, a konfigurację aplikacji przekazuje głównie przez environment i env_file.
Sieci: domyślna vs sieci nazwane
Domyślnie:
- Compose tworzy jedną sieć bridgę per projekt,
- wszystkie usługi widzą się przez DNS po nazwie usługi,
- porty wystawia się tylko na hosta, gdy są w polu
ports.
Dla prostego stosu backend + db wystarczy domyślna sieć. Różnica zaczyna się, gdy dochodzi więcej komponentów (np. Redis, frontend, worker). Wtedy opłaca się podział na sieci, np.:
services:
backend:
build: ./backend
ports:
- "8000:8000"
networks:
- backend-net
- db-net
depends_on:
- db
db:
image: postgres:15
networks:
- db-net
redis:
image: redis:7
networks:
- backend-net
networks:
backend-net:
db-net:
Backend jest na dwóch sieciach: rozmawia jednocześnie z db i redis, ale Postgres nie ma powodu widzieć Redisa (i odwrotnie). W porównaniu z jednym wielkim mostem, taki podział lepiej odzwierciedla faktyczne zależności i utrudnia przypadkowe „przestrzelenia się” komponentów.
Wolumeny: dane trwałe vs kod aplikacji
Compose wyróżnia trzy praktyczne typy podpięć:
- nazwane wolumeny Dockera (np.
db-data) – używane głównie na dane baz i cache, - bind mounty katalogów z hosta (np.
./backend:/app) – wygodne w developmencie, - anonimowe wolumeny (bezpośrednio w
volumesusługi, bez zdefiniowania w sekcji głównej) – przydatne, gdy trwałość nie jest priorytetem.
Porównanie na bazie Postgresa:
- bez wolumenu – każdy restart z
docker compose downusuwa dane bazy, - z wolumenem nazwanym – dane przetrwają restart kontenera i nawet usunięcie
docker compose down, dopóki osobno nie usunie się wolumenu, - z bind mountem (np.
./db-data:/var/lib/postgresql/data) – dane leżą w czytelnym katalogu projektu, łatwo je zbackupować, ale trzeba pilnować uprawnień.
Dla środowiska deweloperskiego bazę najczęściej trzyma się w nazwanym wolumenie. Kod aplikacji natomiast wygodniej podpiąć jako bind mount, żeby nie przebudowywać obrazu przy każdej zmianie.
Konfiguracja: environment, env_file i zmienne hosta
Compose wspiera kilka wzorców przekazywania ustawień:
services:
backend:
build: ./backend
environment:
- LOG_LEVEL=debug
- DATABASE_URL=${DATABASE_URL}
env_file:
- .env.backend
db:
image: postgres:15
env_file:
- .env.db
Różnice między podejściami:
environmentz literalami – najprostsze, ale wszystko ląduje w Git, łącznie z hasłami,env_file– konfiguracja wyciągnięta do pliku.env, który można wyłączyć z repo,- interpolacja
${VAR}– wartości pobierane z otoczenia (shella / systemu CI).
W praktyce wygodne jest połączenie: konfiguracja sensowna domyślnie w .env.example (commitowana), lokalne sekrety w .env (zignorowane przez Git), a Compose wczytuje je przez env_file. Różnica względem twardo zaszytych haseł w YAML-u jest oczywista – łatwiej zmienić hasło lokalnie bez ingerencji w repozytorium.
Rozszerzenie prostego stosu: migracje bazy i narzędzia pomocnicze
Prosty backend + Postgres szybko rośnie o dodatkowe elementy: narzędzie do migracji (np. Alembic, Prisma, Flyway), panel administracyjny czy worker zadań asynchronicznych. W Compose każde z nich może być osobną usługą, ale korzystać z tego samego obrazu i konfiguracji sieciowej.
services:
backend:
build: ./backend
ports:
- "8000:8000"
environment:
- DATABASE_URL=${DATABASE_URL}
depends_on:
- db
migrate:
build: ./backend
command: ["alembic", "upgrade", "head"]
environment:
- DATABASE_URL=${DATABASE_URL}
depends_on:
- db
profiles:
- tools
db:
image: postgres:15
env_file:
- .env.db
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
Usługa migrate nie musi działać cały czas. Można ją odpalić tylko wtedy, gdy potrzeba migracji schematu:
docker compose run --rm migrate
lub przy użyciu profili:
docker compose --profile tools up migrate
Różnica względem ręcznego wchodzenia do kontenera i uruchamiania komendy jest taka, że Compose utrzymuje ten „jednorazowy” kontener w tym samym świecie sieci i konfiguracji co reszta usług. Nie trzeba więc pamiętać o portach, sieciach ani zmiennych środowiskowych.
Profile i warianty stosu: development vs test vs demo
Jednym z częstszych pytań przy Compose jest podział na „warianty” środowiska. Najmniejszy opór stawia osobny plik (np. docker-compose.dev.yml, docker-compose.test.yml), ale nowsza funkcja profili bywa wygodniejsza, gdy różnice są niewielkie.
Porównanie podejść:
- osobne pliki – pełna niezależność konfiguracji, ale powielanie definicji i większe ryzyko rozjazdów,
- profile Compose – jedna baza YAML + elementy oznaczone profilem, które włączasz opcjonalnie (
--profile), - zmienne środowiskowe – jeden plik Compose, ale różne wartości
.envzależnie od środowiska.
Przykład użycia profili dla dodatkowego narzędzia developerskiego, np. adminer / pgAdmin:
services:
backend:
build: ./backend
ports:
- "8000:8000"
environment:
- DATABASE_URL=${DATABASE_URL}
depends_on:
- db
db:
image: postgres:15
env_file:
- .env.db
volumes:
- db-data:/var/lib/postgresql/data
adminer:
image: adminer
ports:
- "8080:8080"
depends_on:
- db
profiles:
- dev
volumes:
db-data:
Na codzień możesz odpalać minimalny zestaw:
docker compose up backend db
a gdy chcesz podejrzeć bazę w przeglądarce:
docker compose --profile dev up adminer
Różnica w porównaniu z osobnym plikiem dla Adminera jest taka, że konfiguracja bazy zostaje w jednym miejscu – nie trzeba duplikować definicji Postgresa.
Compose w codziennym developmencie: podstawowy workflow
W praktyce praca z Compose sprowadza się do kilku komend, które zastępują znacznie dłuższe sekwencje przy gołym Dockerze.
docker compose up– standardowy start w trybie „zawieszonym” w terminalu z logami na żywo,docker compose up -d– start w tle; terminal pozostaje wolny, logi podglądasz osobno,docker compose down– stop usług i usunięcie kontenerów oraz sieci (wolumeny zostają),docker compose stop– tylko zatrzymanie kontenerów, pozostają w systemie,docker compose restart backend– szybki restart konkretnej usługi po zmianie konfiguracji.
Zmiana kodu z bind mountem nie wymaga up za każdym razem, o ile proces wewnątrz kontenera sam w sobie obsługuje reload (np. npm run dev, uvicorn --reload). Różnica względem „gołych” kontenerów jest głównie w tym, że Compose traktuje zestaw usług jako projekt i zarządza nim jednym poleceniem, a nie pojedynczymi kontenerami.
Debugowanie: porównanie typowych ścieżek diagnozy
Najczęstsze problemy przy użyciu Compose to:
- backend nie widzi bazy,
- port nie jest dostępny na hoście,
- dane bazy znikają po restarcie stosu.
Typowe problemy i krótkie ścieżki naprawy
Każdy z trzech kłopotów z poprzedniej listy da się rozłożyć na kilka prostych kroków diagnostycznych. Różnica względem „gołego” Dockera polega głównie na tym, że dochodzi kontekst projektu (sieci Compose, profile, nazwy usług).
Backend nie widzi bazy
Pierwsze rozróżnienie: problem z nazwą hosta, z samym serwerem bazy czy z siecią.
-
Sprawdzenie nazwy hosta i portu
W Compose adres bazy todb:5432, gdziedbtoservice name, a nielocalhost. Typowy błąd:DATABASE_URL=postgres://user:pass@localhost:5432/mydbzamiast:
DATABASE_URL=postgres://user:pass@db:5432/mydbJeśli backend jest pod
backend, a baza poddb, nazwy te są równocześnie DNS-ami w sieci Compose. -
Ping z wnętrza kontenera
Zamiast zgadywać, można sprawdzić połączenie bezpośrednio:docker compose exec backend sh -c "apt-get update && apt-get install -y netcat && nc -vz db 5432"Jeśli
ncmeldujeConnection refusedalbo timeout, problem leży w bazie lub sieci, a nie w kodzie backendu. -
Stan usługi bazy i kolejność startu
docker compose pspokaże, czy
dbw ogóle żyje. Jeśli kontener wychodzi od razu po starcie, logi zwykle wszystko wyjaśniają:docker compose logs dbdepends_onw Compose pilnuje kolejności startu, ale nie gwarantuje, że baza jest już gotowa do przyjęcia połączeń. Przy bardziej kapryśnych bazach przydaje się prosty „wait-for-db.sh” lub narzędzie typuwait-for-itw komendzie backendu.
Port nie jest dostępny na hoście
Tutaj zwykle przeplatają się dwie przyczyny: brak mapowania portu w Compose lub konflikt z innym procesem na hoście.
-
Weryfikacja mapowania w YAML-u
Usługa musi mieć sekcjęports, np.:services: backend: ports: - "8000:8000"Pierwsza liczba to port hosta, druga – port wewnątrz kontenera. Jeśli backend nasłuchuje na
5000, a w YAML-u widnieje"8000:8000", nic nie zadziała. -
Konflikt z inną aplikacją na hoście
Zdarza się klasyczne „Already in use”. W takiej sytuacji:
lsof -i :8000 # Linux/macOS netstat -ano | findstr :8000 # WindowsJeśli port jest zajęty, można albo zabić proces, albo zmienić pierwszą część mapowania, np. na
"8001:8000". -
Różnica: port vs sieć Compose
Debugując, warto rozróżnić dwie płaszczyzny:- z hosta – używasz portu opublikowanego, np.
localhost:8000, - z innej usługi – używasz nazwy usługi i portu wewnętrznego, np.
http://backend:8000.
Jeśli frontend (w innym kontenerze) próbuje łączyć się z
http://localhost:8000, a siedzi w tej samej sieci Compose, to strzela do „swojego” localhosta, a nie do hosta. Różnica bywa subtelna, ale efekt jest jeden:ECONNREFUSED. - z hosta – używasz portu opublikowanego, np.
Dane bazy znikają po restarcie
Najczęściej przyczyną jest brak nazwanych wolumenów albo nieużywany katalog hosta.
-
Sprawdzenie definicji wolumenu
W bazowym przykładzie Postgresa:services: db: volumes: - db-data:/var/lib/postgresql/data volumes: db-data:Jeśli dolna sekcja
volumes:jest pominięta, Docker utworzy anonimowy wolumen, o którym łatwo zapomnieć. -
Różnica między
downadown -vdocker compose downusuwa kontenery i sieci, ale zostawia wolumeny. Z kolei:docker compose down -vkasuje również wolumeny nazwane. Po takim poleceniu baza wróci do „fabrycznych” ustawień.
-
Bind mount vs wolumen nazwany
Porównując dwa podejścia:- wolumen nazwany – dane są w zarządzanej przez Dockera lokalizacji, mniej ingerencji, ale trudniej je odnaleźć ręcznie,
- bind mount – katalog w projekcie, łatwy podgląd, ale ryzyko przypadkowego skasowania lub nadpisania z hosta.
W projektach „do zabawy” wolumen nazwany zwykle wystarcza. W długotrwałym devie lub gdy trzeba przerzucać dane między maszynami często wygodniejszy jest bind mount.
Logi, interaktywne shell’e i szybki podgląd stanu
Compose ma kilka komend, które wyraźnie przyspieszają codzienną diagnozę w porównaniu z ręcznym szukaniem ID kontenerów.
-
Podgląd logów wybranej usługi
docker compose logs backend docker compose logs -f backendPierwsza komenda wypisze historię, druga będzie ją śledzić na żywo. Można też użyć wielu nazw usług naraz.
-
Wejście do kontenera „jak na serwer”
docker compose exec backend sh # lub, jeśli w obrazie jest bash docker compose exec backend bashRóżnica w stosunku do
docker run -it ...jest taka, że trafiasz do już uruchomionej instancji w aktualnym stanie stosu, z tymi samymi wolumenami i sieciami. -
Lista usług i ich status
docker compose pspokazuje coś w rodzaju
docker ps, ale ograniczone do konkretnego projektu. Jeśli używasz wielu stosów naraz (np. kilku mikroserwisów), to wygodniejszy widok niż globalna lista wszystkich kontenerów.

Compose w codziennym developmencie – praktyczne wzorce pracy
Pod względem workflow Compose zwykle wypiera kombinację: kilka skryptów bash + notatki z parametrami docker run. Poniżej kilka typowych scenariuszy i różnice między podejściami.
Tryb „cały stos działa w tle”
Gdy backend, baza, może jeszcze cache i worker mają działać bez przerwy przez dzień lub dwa:
docker compose up -dW porównaniu z trybem „na pierwszym planie”:
- terminal jest wolny,
- logi podglądasz tylko gdy są potrzebne (
docker compose logs -f), - restart hosta zwykle nie psuje środowiska: po podniesieniu systemu wywołujesz
docker compose up -di wracasz do pracy.
Alternatywą bywa docker compose start, jeśli kontenery już istnieją. Różnica:
up– buduje obrazy (jeśli trzeba), tworzy sieci, wolumeny i kontenery,start– tylko uruchamia wcześniej utworzone kontenery, nie tyka ich konfiguracji.
W praktyce do codziennego użycia wystarcza zwykle up -d + down. start/stop przydają się, gdy masz kilka wariantów tego samego projektu i ręcznie przełączasz się między nimi.
Tryb „jednorazowego” kontenera narzędziowego
Dla narzędzi typu migracje, seed bazy, jednorazowe skrypty administracyjne wygodniejsze bywa docker compose run niż up:
docker compose run --rm migrate
docker compose run --rm backend python manage.py shellRóżnica pomiędzy:
docker compose up migrate– usługa staje się elementem stosu, jest zarządzana jak pozostałe, jej kontener trwa, dopóki nie wywołaszdown,docker compose run --rm migrate– tworzony jest jednorazowy kontener, który po zakończeniu pracy jest usuwany.
W przypadku zadań okresowych lub manualnie odpalanych (migracje przy deployu, generowanie raportów) run --rm jest zwykle czytelniejsze i nie zaśmieca listy kontenerów.
Praca z kodem: przebudowywanie obrazu vs hot-reload
Dwa dominujące style pracy z Compose to:
- „produkcyjny” – kod jest w obrazie, każda zmiana wymaga przebudowy,
- „deweloperski” – kod jest montowany z hosta jako bind mount, backend sam przeładowuje się przy zmianach plików.
Porównanie na przykładzie Node.js:
services:
backend:
build: ./backend
volumes:
- ./backend:/app
command: ["npm", "run", "dev"]
Frontend w trybie dev czy backend z nodemon reagują na zmiany w katalogu ./backend. Jeśli ten sam projekt budujesz do produkcji, zwykle używasz osobnego pliku Compose lub profilu:
services:
backend:
build: ./backend
command: ["npm", "start"]
# brak volumes, kod jest w obrazie
profiles:
- prodPodejście „dwa profile, jedna definicja usługi” często upraszcza życie w porównaniu z utrzymywaniem dwóch zupełnie niezależnych plików Compose. Różnica jest głównie taka, że trudniej o rozjazd wersji obrazu czy konfiguracji sieci.
Compose a testy automatyczne
Dla testów integracyjnych Compose bywa wygodniejszy niż ręcznie odpalana baza lub osobny skrypt. Można potraktować go jak jednorazowe środowisko testowe, które powstaje na początku pipeline’u i znika na końcu.
Dwie typowe strategie:
-
Start pełnego stosu, testy z zewnątrz
docker compose -f docker-compose.test.yml up -d pytest tests/integration docker compose -f docker-compose.test.yml down -vTesty odpalane są na hoście (lub w runnerze CI), a Compose tylko zapewnia infrastrukturę (bazy, kolejki, API).
-
Osobny kontener testowy w tym samym projekcie
services: tests: build: ./backend command: ["pytest", "tests/integration"] depends_on: - db - backend profiles: - testW pipeline:
docker compose --profile test up --exit-code-from tests --abort-on-container-exitPo zakończeniu testów cały stos można skasować jedną komendą
down -v. W porównaniu z ręcznym pilnowaniem kolejności odpalania usług i czyszczenia baz to dość znaczące uproszczenie.
Docker Compose a inne opcje orkiestracji i automatyzacji
Compose rozwiązuje problem wielokontenorowego środowiska lokalnie i częściowo w prostych deployach. Gdy projekt rośnie, pojawia się wybór: zostać przy Compose, dopisać własne skrypty, czy przeskoczyć na coś w rodzaju Kubernetesa. Różnice nie są tylko technologiczne – dotyczą też kosztu utrzymania i bariery wejścia.
Compose vs pojedyncze kontenery Docker
Przy jednym kontenerze (np. tylko Postgres) Compose nie wnosi wiele – poza zapamiętaniem konfiguracji w YAML-u. Porównanie dwóch podejść dla prostej bazy:
# goły docker
docker run --name my-postgres -e POSTGRES_PASSWORD=secret -p 5432:5432 -d postgres:15
# docker compose
services:
db:
image: postgres:15
environment:
- POSTGRES_PASSWORD=secret
ports:
- "5432:5432"Dla pojedynczej usługi:
- Docker – szybciej, mniej plików, konfiguracja w historii powłoki,
- Compose – powtarzalność, łatwa wersjonowalność, jedno polecenie do startu/stopu.
Granica opłacalności często przebiega przy drugim/trzecim kontenerze: gdy dochodzi baza i cache, Compose zaczyna wygrywać klarownością. Zamiast trzech długich komend docker run i notatek z portami powstaje jeden YAML, który można uruchomić z nowym członkiem zespołu w kilkanaście sekund.
Compose vs skrypty bash / Makefile
Inne popularne podejście to zestaw skryptów (np. scripts/start_dev.sh) lub Makefile, który woła Dockera bezpośrednio. Oba podejścia można też łączyć z Compose.
Porównanie funkcji:
Najczęściej zadawane pytania (FAQ)
Czym Docker Compose różni się od zwykłego Dockera (docker run)?
Docker to „silnik” kontenerów, a docker run to pojedyncze polecenie uruchomienia jednego kontenera. Docker Compose dodaje warstwę deklaratywną: cały stos aplikacji (kilka kontenerów, sieci, wolumeny) opisujesz w jednym pliku docker-compose.yml i uruchamiasz jednym poleceniem docker compose up.
Przy jednym kontenerze docker run bywa szybszy, natomiast przy kilku usługach (app, baza, cache, worker) ręczne komendy robią się długie, trudne do odtworzenia i podatne na literówki. Compose porządkuje to w jednym, wersjonowanym pliku, który można przeglądać w code review i łatwo odtworzyć na innym komputerze.
Kiedy używać Docker Compose, a kiedy wystarczy pojedynczy kontener?
Pojedynczy kontener z docker run wystarczy, gdy uruchamiasz prosty, samodzielny serwis: np. testujesz Redis, Nginx, jednorazowy kontener alpine do narzędzi albo monolityczną aplikację serwującą statyczne pliki bez bazy danych.
Compose ma przewagę, gdy tylko pojawia się zależność: baza danych, broker wiadomości (RabbitMQ, Kafka), osobny frontend czy worker do zadań w tle. Nawet układ app + db uzasadnia przejście na Compose, bo wtedy dochodzi konfiguracja sieci, wolumenów, zmiennych środowiskowych i powiązań depends_on, które w jednym pliku są dużo czytelniejsze niż seria poleceń docker run.
Czy Docker Compose nadaje się na produkcję, czy potrzebny jest Kubernetes?
Docker Compose dobrze sprawdza się na jednym hoście: lokalnie u developera, na serwerze testowym, w środowiskach QA/UAT albo w małej produkcji typu demo dla klienta. Umożliwia proste skalowanie usług na jednym serwerze (np. docker compose up --scale app=3), ale nie zarządza klastrem wielu maszyn.
Jeżeli potrzebujesz automatycznego przenoszenia usług między serwerami po awarii, zaawansowanego autoskalowania, service discovery i routingowania ruchu w skali kilku–kilkunastu węzłów, wchodzą w grę platformy orkiestracyjne jak Kubernetes (lub w prostszych przypadkach Docker Swarm). Typowy układ to Compose na lokalnym devie, a Kubernetes na produkcji.
Do czego Docker Compose jest najczęściej używany w praktyce?
Najpopularniejsze scenariusze to:
- lokalne środowisko developerskie – każdy developer odpala identyczny stos komendą
docker compose up, bez instalowania baz czy MQ na swoim systemie, - testy integracyjne i e2e w CI – pipeline przed testami podnosi cały stos z Compose, uruchamia testy, a po wszystkim go wyłącza,
- demo lub „mini-produkcja” na jednym serwerze – szybkie postawienie kompletnego środowiska dla klienta czy zespołu QA.
W tych zastosowaniach Compose jest znacznie prostszy i szybszy w konfiguracji niż pełny Kubernetes, a jednocześnie dużo bardziej uporządkowany niż ręcznie uruchamiane pojedyncze kontenery.
Jak wygląda podstawowy plik docker-compose.yml i co musi zawierać?
W aktualnym Docker Compose (V2 jako plugin CLI) nie trzeba podawać pola version. Minimalny szkielet to sekcja services z definicjami usług oraz opcjonalne sekcje volumes i networks:
services:
app:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/app
depends_on:
- db
db:
image: postgres:15
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=app
Sekcja services określa, z jakich obrazów lub buildów mają powstać kontenery, jakie porty i wolumeny podpiąć oraz jakie zmienne środowiskowe ustawić. volumes i networks pozwalają nazwać i parametryzować dane oraz sieci, ale jeśli ich nie zdefiniujesz, Compose i tak tworzy domyślną sieć projektu.
Jak w Docker Compose działają sieci i komunikacja między usługami?
Docker domyślnie tworzy sieć typu bridge. Przy ręcznym docker run kontenery komunikują się po adresach IP, a porty trzeba mapować ręcznie (-p 8080:80). Przy kilku usługach szybko robi się z tego mało czytelny zestaw mapowań.
Compose grupuje usługi w jednej sieci i nadaje im nazwy hostów odpowiadające nazwom usług. Backend może połączyć się z bazą po adresie db:5432, a nie po zmieniającym się IP. Mapowanie portów na hosta zapisujesz w jednym miejscu w polu ports, więc łatwo zobaczyć, co jest wystawione na zewnątrz, a co działa wyłącznie wewnątrz sieci Compose.
Jak diagnozować błędy w Docker Compose – od czego zacząć?
Większość problemów w Compose sprowadza się do klasycznych kłopotów z Dockerem: kontener szybko się wyłącza, port jest źle zmapowany, brakuje wolumenu lub jest konflikt nazw. Najszybciej diagnozuje się to, traktując usługi z Compose jak zwykłe kontenery:
docker ps– sprawdź, czy kontener danej usługi faktycznie działa,docker logs NAZWA_USLUGI-1– przejrzyj logi startu i ewentualne stack trace,docker inspect– zobacz szczegóły konfiguracji sieci, portów i wolumenów.
Różnica jest taka, że Compose opakowuje wiele operacji w jedno polecenie docker compose up, ale pod spodem to nadal typowe kontenery Dockera. Znajomość tych kilku „gołych” komend bardzo ułatwia znalezienie źródła błędu.
Najważniejsze punkty
- Docker Compose rozwiązuje problem rosnącej złożoności przy wielu usługach – zamiast serii długich, podatnych na pomyłki komend
docker runmasz jeden deklaratywny plikdocker-compose.ymli jedno poleceniedocker compose up. - Plik
docker-compose.ymljest wersjonowany razem z kodem, dzięki czemu konfiguracja środowiska staje się powtarzalna, recenzowalna w code review i łatwa do porównania między gałęziami – podobnie jak migracje SQL zastępują ręczne klepanie zapytań. - Pojedynczy kontener i goły
docker runwystarczą przy prostych scenariuszach (np. sam Redis, Nginx, monolit bez zależności), natomiast już układ app + baza danych uzasadnia użycie Compose, bo dochodzą kwestie sieci, wolumenów, zmiennych środowiskowych i zależności startu usług. - Największy zysk z Compose widać w środowiskach dev, test, demo i małej produkcji: jednym poleceniem stawiasz kompletny stos dla dewelopera, pipeline CI czy środowiska QA, bez ręcznej instalacji baz, brokerów wiadomości czy runtime’ów na hoście.
- Compose uzupełnia, a nie zastępuje Dockera: opisuje, jak z obrazów tworzyć kontenery, jak podłączać wolumeny i sieci, oraz agreguje typowe operacje (
build,run, mapowanie portów, tworzenie sieci) w jedno spójne polecenie, ułatwiając zrozumienie i diagnozowanie całego stosu.






