HostiServer
2026-09-21 11:18
Docker Compose: více služeb dohromady
📚 Série „Docker na VPS", část 2 z 5:
- Docker na VPS: instalace a první kontejner
- Docker Compose: více služeb dohromady ← jste zde
- Docker v produkci
- Docker + CI/CD: automatický deploy s GitHub Actions / GitLab CI
- Gitea / Forgejo v Dockeru: kompletní self-hosted stack
Docker Compose: více služeb dohromady
V první části série jsme se naučili spustit jeden kontejner příkazem docker run. Reálná aplikace zřídka žije sama: přidává se k ní databáze, cache, a často i fronta zpráv. Tři služby jsou už tři samostatné příkazy docker run, v každém až desítka přepínačů, plus síť, kterou je třeba vytvořit ručně předem: docker network create app-net. Na pořadí spuštění přitom záleží, ale nic ho nezaručuje.
Problém je zvlášť patrný po restartu serveru. Kontejnery s restart: unless-stopped naběhnou samy, ale Docker neví, která služba má startovat první. Pokud aplikace naběhne dřív než databáze, první požadavek spadne s ECONNREFUSED — a je třeba aplikaci ručně restartovat, až je databáze připravená.
Docker Compose to řeší změnou samotného způsobu uvažování. Místo sekvence příkazů popisujete požadovaný stav v souboru docker-compose.yml: jaké služby mají existovat, jak jsou propojené, co na čem závisí. Pořadí spuštění, sítě a volumes bere na sebe Compose.
Jednu technickou věc je dobré vyřešit hned na začátku. Moderní příkaz je docker compose (dvě slova, plugin v Go, vestavěný do Docker Engine od verze 20.10+). Starý docker-compose (s pomlčkou, samostatný nástroj v Pythonu, větev v1) byl oficiálně vyřazen z podpory v květnu 2023. Ověřit, jaká verze je nainstalovaná:
docker compose version
Očekávaný výstup je něco jako Docker Compose version v2.x.x. Pokud příkaz nenajde, plugin se instaluje samostatně: apt install docker-compose-plugin.
2. Struktura docker-compose.yml
2.1 K řádku version:
Většina starších tutoriálů začíná soubor řádkem version: "3.8". V Compose v2 je tento řádek ignorován s varováním o zastaralosti — lze ho klidně odstranit, soubor bude kratší a bez zbytečného šumu v logách.
2.2 services: — klíč, jméno a DNS zároveň
Název každé služby pod klíčem services: plní dvojí roli: ve výchozím stavu z něj vzniká jméno kontejneru (<projekt>-<služba>-1) a zároveň je to DNS jméno, pod kterým je služba vidět ostatním kontejnerům ve stejné síti. Pokud nastavíte container_name: my-app explicitně, jméno bude předvídatelné, ale pak službu nelze škálovat příkazem docker compose up --scale app=3 — Compose nemůže vytvořit víc kontejnerů se stejným pevným jménem.
2.3 image: nebo build:
image: postgres:16-alpine stáhne hotový image z registru. build: . sestaví image z Dockerfile v aktuálním adresáři. Pokud Dockerfile neleží v kořeni projektu, uvede se explicitně:
build:
context: ./app
dockerfile: Dockerfile.prod
2.4 environment: — dva formáty zápisu
Proměnné prostředí lze psát jako mapu (DATABASE_URL: postgres://...) nebo jako seznam (- DATABASE_URL=postgres://...). Formát mapy se čte lépe při velkém počtu proměnných. Výraz ${VAR} se v obou formátech dosazuje z hodnoty v souboru .env nebo ze shell prostředí v okamžiku spuštění Compose.
2.5 ports: — jen to, co skutečně potřebuje být venku
Formát "3000:3000" znamená port hostu : port kontejneru. Bez sekce ports: zůstává služba nedostupná zvenčí, ale dostupná ostatním službám ve stejné síti Compose. Pro databázi v produkci ports: zpravidla není potřeba a je nežádoucí — otevírat port SŘBD do internetu bez naprosté nutnosti se nevyplatí.
2.6 volumes: u služby
Krátká forma je pgdata:/var/lib/postgresql/data. Dlouhá forma umožňuje explicitně uvést typ — type: volume pro named volume nebo type: bind pro bind mount. Named volume použitý ve službě musí mít odpovídající blok na horní úrovni souboru: volumes: pgdata: — právě tady Compose přebírá řízení jeho životního cyklu.
2.7 networks: — vlastní výchozí síť
Bez explicitní deklarace Compose vytvoří síť s názvem <projekt>_default a připojí do ní všechny služby. Vlastní síť se nastaví takto: u služby networks: [app-net], a dole v souboru networks: app-net: (prázdný blok znamená bridge síť s výchozím nastavením).
2.8 restart: — čtyři varianty
| Hodnota | Chování |
|---|---|
no |
Výchozí — vůbec se nerestartuje |
always |
Restartuje se vždy, i po docker compose stop |
unless-stopped |
Restartuje se, kromě případu, kdy byl zastaven ručně |
on-failure |
Jen pokud proces skončil s nenulovým kódem |
Pro produkci se jako výchozí vyplatí zvolit unless-stopped: služba přežije restart hostu, ale nebude se snažit naběhnout znovu, pokud jste ji zastavili vědomě.
2.9 command: a entrypoint:
Obě direktivy přepisují to, co je nastaveno v Dockerfile přes CMD nebo ENTRYPOINT. Typické použití je samostatný dev režim: command: npm run dev místo produkčního spouštěcího příkazu, bez nutnosti udržovat dva různé Dockerfile.
3. Reálný stack: Node.js + PostgreSQL + Redis
Dáme to všechno dohromady do jednoho funkčního příkladu — aplikace na Node.js, databáze PostgreSQL a cache Redis:
services:
app:
build: .
ports: ["3000:3000"]
env_file: .env
environment:
DATABASE_URL: postgres://user:${DB_PASSWORD}@db:5432/mydb
REDIS_URL: redis://cache:6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
networks: [app-net]
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: mydb
volumes: [pgdata:/var/lib/postgresql/data]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
interval: 5s
timeout: 5s
retries: 10
networks: [app-net]
restart: unless-stopped
cache:
image: redis:7-alpine
command: redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru
networks: [app-net]
restart: unless-stopped
volumes:
pgdata:
networks:
app-net:
3.1 Proměnné POSTGRES_*
Oficiální image postgres čte POSTGRES_USER, POSTGRES_PASSWORD a POSTGRES_DB jen jednou — při první inicializaci clusteru na prázdném volume. Při dalších spuštěních, kdy volume už existuje, se tyto proměnné ignorují. Heslo pozdější změnou proměnné nezměníte — bude třeba provést ALTER USER uvnitř už běžící databáze.
3.2 depends_on s podmínkou service_healthy
Sekce depends_on: db: condition: service_healthy znamená, že app nezačne startovat, dokud healthcheck databáze nevrátí úspěch. Prostý zápis depends_on: [db], bez podmínky, čeká jen na start samotného procesu kontejneru — a PostgreSQL v tu chvíli ještě může inicializovat cluster a nepřijímat spojení. Přesně to způsobuje ECONNREFUSED při prvním požadavku po startu stacku.
3.3 Proč retries: 10, ne 5
Při interval: 5s a retries: 10 je Compose ochoten čekat na healthy stav databáze až 50 sekund celkem. To není s rezervou: první inicializace clusteru PostgreSQL sama o sobě může trvat 10-20 sekund, a při retries: 5 (25 sekund) může Compose označit databázi jako unhealthy ještě předtím, než skutečně naběhla.
3.4 redis:7-alpine a limit paměti
Tag redis:latest znamená, že obsah image se může kdykoli změnit; redis:7-alpine fixuje hlavní verzi a váží zhruba 40 MB. Přepínač --maxmemory 128mb v command: je v produkci kriticky důležitý: bez explicitního limitu se Redis ve výchozím stavu snaží využít veškerou dostupnou paměť hostu, a při aktivním cachování to může vést k OOM zabití sousedních kontejnerů.
3.5 Jak služby najdou jedna druhou podle jména
Řádek @db:5432 v DATABASE_URL funguje díky vestavěnému DNS Compose: jméno služby db se přeloží na interní IP adresu odpovídajícího kontejneru. To funguje jen pro služby ve stejné sekci networks: a nefunguje z hostu — z terminálu serveru jméno db nic neznamená.
4. .env — proměnné a secrets
4.1 Automatické načtení
Soubor .env ve stejném adresáři jako docker-compose.yml Compose načte automaticky, bez dalšího nastavení:
DB_PASSWORD=supersecret123
NODE_ENV=production
4.2 Dva různé mechanismy — nezaměňovat
Tady se snadno spletou dva podobné, ale odlišné způsoby, jak se hodnoty dostanou do kontejneru:
${DB_PASSWORD}přímo v compose souboru — dosazení proběhne během parsování YAML samotným Compose; hodnota se dostane do konfigurace, ale ne automaticky do proměnných prostředí kontejneru.env_file: .envv sekci služby — soubor se celý předá dovnitř kontejneru jako proměnné prostředí, dostupné aplikaci přes, například,process.env.DB_PASSWORD.
4.3 Ověření dosazení
docker compose config
Tento příkaz vypíše finální compose soubor už po dosazení všech ${VAR} — pohodlný způsob, jak se ujistit, že hodnoty z .env se skutečně načetly, ještě před spuštěním stacku.
4.4 Priorita zdrojů hodnot
Od nejvyšší priority po nejnižší: proměnná shell prostředí → hodnota ze souboru .env → výchozí hodnota přímo ve výrazu, pokud je zadaná jako ${VAR:-default}.
4.5 .gitignore
Soubor .env se musí přidat do .gitignore. Rozumný vzor je *.env a .env*, což zároveň vyloučí .env.local a .env.production, které se často přidávají později.
4.6 .dockerignore taky
.env musí být i v .dockerignore — jinak COPY . . v Dockerfile zkopíruje secrets přímo dovnitř image. Snadno se to ověří:
docker run --rm my-app cat /app/.env
Pokud příkaz vypíše obsah souboru, secret je už „zapečený" v image, a problém nevyřeší smazání souboru v dalším commitu: předchozí vrstva image ho stále obsahuje.
4.7 Přístupová práva k souboru
chmod 600 .env omezí čtení souboru jen na vlastníka — pro jeden VPS to stačí. Pro scénáře náročnější než jeden server (Swarm, víc týmů s různou úrovní důvěry) se vyplatí podívat se po Docker Secrets nebo HashiCorp Vault.
5. Sítě: jak služby najdou jedna druhou
5.1 Izolace podle projektů
Každý Compose projekt — obvykle samostatný adresář — dostane vlastní izolovanou síť. Služba app z jednoho projektu nevidí db jiného projektu, i když oba běží na stejném hostu.
5.2 Jméno projektu
Ve výchozím stavu se jméno projektu bere z názvu adresáře. Lze ho nastavit explicitně přepínačem docker compose -p myproject up nebo řádkem name: myproject na horní úrovni compose souboru — to ovlivňuje jména kontejnerů a sítí, a tedy i to, zda se dva stacky navzájem „uvidí".
5.3 Minimum otevřených portů
Pravidlo je jednoduché: ports: se píše jen u služeb, které skutečně potřebují přístup zvenčí. V příkladu výše je to app (3000:3000); db a cache takový blok nemají. Zbytečný řádek "5432:5432" v sekci databáze otevře PostgreSQL celému internetu — to je jedna z nejčastějších příčin kompromitace databáze na VPS.
5.4 Komunikace mezi různými stacky
Pokud spolu mají komunikovat dva nezávislé Compose projekty — například samostatný monitorovací stack, který potřebuje vidět metriky aplikace — použije se připojení k už existující síti:
networks:
monitoring-net:
external: true
6. Klíčové příkazy
| Příkaz | Co dělá |
|---|---|
docker compose up -d |
Spustí všechny služby na pozadí; při prvním spuštění vytvoří sítě, volumes, stáhne image a sestaví build:; při opakovaném restartuje jen změněné služby |
docker compose up -d --build |
Vynutí přesestavení image před spuštěním — potřeba po změnách v Dockerfile nebo kódu, jinak Compose vezme cachovaný image |
docker compose up -d --no-deps app |
Restartuje jen app, aniž by se dotkl db a cache — pohodlné při nasazení nové verze aplikace |
docker compose down |
Zastaví a smaže kontejnery a sítě; named volumes zůstávají — data v pgdata jsou zachována |
docker compose down -v |
Totéž, plus smazání named volumes — v produkci nevratné: pgdata zmizí spolu se všemi daty databáze |
docker compose stop / start |
Zastavit nebo spustit bez mazání kontejnerů — rychlejší než down+up, volumes a sítě zůstávají |
docker compose restart app |
Restartovat konkrétní službu; compose soubor se přitom znovu nenačítá — pro použití změn konfigurace je potřeba up -d |
docker compose ps |
Stav všech služeb: jméno, image, stav, porty, health |
docker compose logs -f app |
Follow logy konkrétní služby; bez jména služby logy všech najednou s barevnými prefixy; --tail=100 omezí výstup na poslední řádky |
docker compose exec db psql -U user -d mydb |
Otevřít psql uvnitř kontejneru databáze pro debug |
docker compose run --rm app npm run migrate |
Spustit jednorázový příkaz v novém kontejneru služby app a hned po dokončení ho smazat — typické pro migrace DB |
docker compose pull && docker compose up -d |
Aktualizovat image z registru a restartovat — základní vzor deploye v CI/CD (článek 4 série) |
7. Závěr
Teď je celý stack — aplikace, databáze, cache — popsaný v jednom souboru. git clone plus docker compose up -d vytvoří identické prostředí na jakémkoli serveru s Dockerem za minutu až dvě, bez jediného ručního kroku a bez otázky „v jakém pořadí to spustit".
Zůstává pár věcí, které docker-compose.yml sám o sobě neřeší. HTTPS a SSL certifikáty — stack potřebuje před sebou reverse proxy jako Nginx nebo Traefik. Víc domén na jedné IP adrese se také řeší na úrovni proxy, ne Compose. Limity zdrojů (mem_limit: 512m, cpus: "0.5") zatím chybí — a bez nich může jedna služba, která se vymkne kontrole, vzít veškerou paměť serveru ostatním. A logy jsou zatím dostupné jen přes docker compose logs, bez rotace a bez uchování po smazání kontejneru.
Přesně tyto mezery jsou tématem dalšího článku: před stack se postaví Nginx nebo Traefik, objeví se certifikát Let's Encrypt a automatické spuštění se přesune z ručního docker compose up -d na systemd jednotku, která stack po restartu serveru naběhne sama, bez účasti člověka.
📚 Navigace v sérii:
Čtete část 2 z 5 „Docker Compose: více služeb dohromady".
Předchozí: Část 1. Docker na VPS: instalace a první kontejner ←
Dál: Část 3. Docker v produkci →
🚀 VPS pro váš Compose stack
Víc kontejnerů najednou už není „jedno dvě jádra pro jistotu": aplikace, databáze a cache běží paralelně a každá drží vlastní paměť. Hostiserver dává zdroje počítané přesně pro takový stack.
🖥️ Dedikované servery
- Od $90/měsíc, plná kontrola nad hardwarem pro náročnější produkční stacky
- Bez sousedů o zdroje: healthcheck databáze nezávisí na cizí zátěži
- Podpora 24/7: pomůžeme se sítěmi, volumes a systémovým automatickým spuštěním stacku
💻 Cloud (VPS) hosting
- Od $19.95/měsíc, KVM izolace, vyhrazené vCPU a RAM
- NVMe disky: PostgreSQL a Redis s diskem, který se nestane úzkým hrdlem
- Dost RAM už od prvního tarifu: app + db + cache najednou bez swapování
- Snadné škálování: víc paměti pro novou službu ve stejném compose souboru
💬 Nejste si jistí, kterou variantu potřebujete?
💬 Napište nám — pomůžeme s výběrem!
Časté dotazy
- Čím se docker compose (v2) liší od docker-compose (v1)?
docker-compose (s pomlčkou) je samostatný nástroj v Pythonu, větev v1, oficiálně vyřazená z podpory v květnu 2023. docker compose (dvě slova) je plugin v Go, vestavěný do Docker Engine od verze 20.10+, a právě ten se teď vyvíjí. Syntaxe compose souboru se prakticky nezměnila, rozdíl je hlavně v samotném příkazu spuštění a rychlosti.
- Aplikace hned po startu spadne s ECONNREFUSED — co je špatně?
Nejčastější příčina je prostý
depends_on: [db]bez podmínky: Compose čeká jen na start procesu kontejneru databáze, ne na její připravenost přijímat spojení. Řešení jedepends_on: db: condition: service_healthyspolu s nastavenýmhealthcheck:přímo u služby databáze.
- Potřebuje moderní compose soubor řádek version:?
Ne. V Compose v2 je ignorován s varováním o zastaralosti a nemá vliv na fungování souboru. Lze ho odstranit z jakéhokoli projektu bez následků — soubor bude jen kratší.
- docker compose down vs down -v — jaký je rozdíl?
downsmaže kontejnery a sítě, ale nechá named volumes na místě — data databáze jsou zachována.down -vnavíc smaže i volumes a s nimi všechna data uvnitř. Na produkčním serveru se-vvyplatí použít jen vědomě a po ověření, že záloha skutečně existuje.
- Jak předat heslo k databázi, aniž bych ho ukládal do gitu?
Heslo se vyčlení do souboru
.envvedledocker-compose.yml, a samotný soubor se přidá do.gitignore(a do.dockerignore, aby se nedostal do image). V compose souboru se hodnota dosazuje přes${DB_PASSWORD}. Pro scénáře s víc servery nebo týmy se vyplatí zvážit Docker Secrets nebo externí úložiště jako HashiCorp Vault.
- Lze otevřít port databáze ven kvůli debugu?
Technicky ano, přidáním
ports: ["5432:5432"], ale v produkci to není žádoucí — port se stane dostupným celému internetu. Bezpečnější cesta pro debug jedocker compose exec db psql -U user -d mydb, který otevře session psql přímo uvnitř kontejneru bez otevření portu ven, nebo dočasný SSH tunel na dobu práce.
- Kolik paměti přidělit každé službě?
V základním
docker-compose.ymlbez explicitních limitů může každá služba využít veškerou dostupnou paměť hostu — přesně proto Redis v příkladu výše běží s--maxmemory 128mb. Pro systémové limity na úrovni Compose se používajímem_limitacpusu každé služby; víc o tom v dalším článku série, věnovaném produkčnímu nastavení.