Problem, który rozwiązuje Compose
Prawdziwa aplikacja rzadko jest jednym kontenerem. Web API potrzebuje bazy danych, baza potrzebuje wolumenu, żeby jej dane przetrwały restart, a potem dochodzi jeszcze cache Redis i worker w tle. To cztery polecenia docker run, każde z własnymi flagami dla portów, wolumenów, zmiennych środowiskowych i wspólnej sieci. W odpowiedniej kolejności. Za każdym razem, gdy siadasz do pracy.
Docker Compose zastępuje te polecenia jednym plikiem i jednym poleceniem. Opisujesz kontenery i to, jak się łączą, w pliku o nazwie compose.yaml, a docker compose up uruchamia je wszystkie. Jeśli obrazów i docker run jeszcze nie znasz, przeczytaj najpierw czym jest Docker i jak działają kontenery.
Co opisuje plik compose
Plik compose ma garść kluczy najwyższego poziomu. Ten, którego używasz zawsze, to services — każda usługa to kontener, który Compose uruchomi.
| |
Dwa kontenery. api jest budowany z pliku Dockerfile w bieżącym katalogu i publikuje port 8000. db uruchamia oficjalny obraz postgres:18 i trzyma swoje dane w nazwanym wolumenie, więc dane zostają, kiedy odtwarzasz kontener.
Dwa szczegóły wykonują tu prawdziwą robotę:
apidociera dodbpo nazwie hostadb. Compose umieszcza każdą usługę we wspólnej sieci i rejestruje nazwę każdej usługi jako nazwę DNS. Żadnych adresów IP, żadnego starego--link.DATABASE_URLwskazuje nadb:5432i nazwa się rozwiązuje.depends_onzcondition: service_healthywstrzymujeapi, aż Postgres naprawdę odpowie. Zwykłedepends_onczeka tylko na start kontenera, a nie na to, aż proces bazy w środku zacznie przyjmować połączenia — ta różnica to najczęstsza przyczyna „connection refused” przy pierwszym uruchomieniu.
Trzy główne klucze: services, volumes, networks
Trzy klucze najwyższego poziomu odpowiadają pojęciom Dockera, które już znasz:
| Klucz | Co definiuje | Ręczny odpowiednik |
|---|---|---|
services | kontenery do uruchomienia | docker run |
volumes | nazwane wolumeny dla danych, które muszą przetrwać | docker volume create |
networks | sieci między usługami | docker network create |
networks deklarujesz rzadko. Compose tworzy jedną sieć na projekt i podłącza do niej każdą usługę. Dlatego przykład z Postgresem nie wymagał żadnej konfiguracji sieci. Deklaruj sieci jawnie tylko wtedy, gdy chcesz odciąć jedną grupę usług od drugiej:
| |
Polecenia, których naprawdę będziesz używać
Uruchamiaj je z katalogu z plikiem compose.yaml.
| |
up i down to para, którą wpisujesz najczęściej. up można bez ryzyka uruchamiać wielokrotnie: po edycji pliku odtwarza tylko te usługi, których konfiguracja się zmieniła, a resztę zostawia w spokoju.
Co docker compose up wypisuje przy pierwszym uruchomieniu
Uruchom powyższy przykład z Postgresem, a dostaniesz mniej więcej to:
| |
Do nazw Compose dokłada z przodu nazwę projektu (domyślnie nazwę katalogu), a na końcu numer, bo potrafi uruchomić więcej niż jedną replikę tej samej usługi. app-db-1 jest Healthy, zanim wystartuje app-api-1 — to condition: service_healthy wykonuje swoją pracę.
Jak trzymać hasła poza plikiem compose
Compose czyta plik o nazwie .env w katalogu projektu i podstawia odwołania ${VAR} w pliku compose:
| |
| |
Trzymaj .env poza kontrolą wersji, a zamiast niego zacommituj .env.example z pustymi lub przykładowymi wartościami. Do czegoś naprawdę wrażliwego w środowisku produkcyjnym użyj Docker secrets zamiast zmiennych środowiskowych.
Opcjonalne usługi z profiles
Nie każda usługa musi startować za każdym razem. Wpis profiles trzyma usługę wyłączoną, dopóki o nią nie poprosisz:
| |
docker compose up uruchamia api i db, a seed pomija zupełnie. docker compose --profile tools run --rm seed uruchamia seeder, gdy o niego poprosisz. Używaj profiles do wszystkiego jednorazowego: seedery, migracje, powłoka do debugowania, generator obciążenia, którego chcesz tylko podczas testu.
Compose v2 kontra stary docker-compose
Jeśli poradnik każe ci uruchomić docker-compose z myślnikiem, pochodzi sprzed 2023 roku. To była wersja v1, napisana w Pythonie, dziś już bez wsparcia. Obecne narzędzie to docker compose jako podpolecenie CLI Dockera; jest częścią Docker Desktop oraz pakietu wtyczki Compose dla Docker Engine.
Starsze pliki często mają na początku taką linię:
| |
W Compose v2 klucz version jest ignorowany. Compose sprawdza plik według aktualnej Compose Specification i ostrzega, kiedy ten klucz jest obecny. Usuń go, a ostrzeżenie zniknie.
Domyślna nazwa pliku też się zmieniła: compose.yaml jest aktualna, docker-compose.yml nadal działa, a Compose szuka obu.
Przeładowanie na żywo podczas developmentu
docker compose watch (Compose 2.22 i nowsze) aktualizuje kontenery, gdy edytujesz kod. Dodaj do usługi blok develop:
| |
sync kopiuje zmienione pliki wprost do działającego kontenera, więc zmiana w kodzie jest widoczna bez przebudowy. rebuild wyzwala pełną przebudowę obrazu, gdy zmienia się plik wpływający na build, na przykład lockfile zależności. Uruchom to przez docker compose watch albo dodaj --watch do up.
Kiedy Compose to złe narzędzie
Compose uruchamia kontenery na jednej maszynie. To jest granica, a większość tego, czego ludziom brakuje w Compose, leży po jej drugiej stronie.
Nadaje się do lokalnego developmentu, automatycznych testów w CI i małych wdrożeń na jednym hoście. Nie potrafi rozkładać kontenerów na wiele serwerów, zastąpić jednego, gdy węzeł padnie, robić stopniowych wdrożeń sterowanych przez healthcheck ani autoskalować.
To zadania orkiestratora: Kubernetes albo Docker Swarm, jeśli chcesz czegoś lżejszego. Te dwa nie konkurują ze sobą. Plik compose to często wstępny szkic, który później staje się zestawem manifestów Kubernetes, a wiele zespołów dalej używa Compose lokalnie długo po tym, jak produkcja przeniosła się na klaster.
Z Podmanem jest podobnie: podman compose i podman-compose czytają ten sam format pliku, z zastrzeżeniami opisanymi w Docker vs Podman.
Częste błędy
- Sekrety zacommitowane w
compose.yaml. Plik trafia do Gita. Użyj.env(w gitignore) albo Docker secrets. - Liczenie na to, że
depends_onpoczeka na gotowość usługi. Samo w sobie czeka na start kontenera, nie na to, aż usługa zacznie przyjmować połączenia. Połącz go zhealthcheckicondition: service_healthy. - Ustawianie
container_name. Uniemożliwia to uruchomienie więcej niż jednej kopii projektu i psujedocker compose up --scale. Pozwól Compose nazwać kontenery. - Bind mount na katalogu z zainstalowanymi zależnościami. Zamontowanie folderu projektu w kontenerze Node lub Pythona może zasłonić
node_modulesalbo virtualenv utworzony podczas builda. Montuj podkatalogi kodu źródłowego albo podłącz anonimowy wolumen do ścieżki zależności.
Jak przenieść istniejącą aplikację na Compose
Weź aplikację, którą dziś uruchamiasz skryptem pełnym linii docker run, i przenieś ją do compose.yaml usługa po usłudze. Uruchom, przeczytaj logi, napraw to, co się psuje, dodaj kolejną usługę. Daj healthcheck wszystkiemu, od czego zależą inne usługi — to krok, który wszyscy pomijają, a który zatrzymuje „connection refused” przy zimnym starcie.
Poznasz, że się udało, gdy docker compose down -v && docker compose up odbuduje całe środowisko od zera, a aplikacja za każdym razem wróci tak samo.