docker compose — više kontejnera odjednom
Aplikacija retko radi sama. Uz nju ide baza, možda Redis, možda reverse proxy. Sa docker run to znači tri duge komande, ručno pravljenje mreže i pamćenje redosleda pokretanja.
Compose sve to opisuje u jednom fajlu. Jedna komanda diže ceo sklop, jedna ga ruši.
docker-compose ili docker compose
Stariji alat je bio zaseban program pisan u Pythonu i zvao se
docker-compose, sa crticom. Danas je Compose ugrađen u Docker
kao poddomanda — docker compose, sa razmakom.
$ docker compose version Docker Compose version v2.29.1
Ako ti to ne radi, instaliraj dodatak:
# dnf install -y docker-compose-plugin
Fajl se danas zove compose.yaml. Staro ime
docker-compose.yml i dalje radi i videćeš ga svuda.
Prvi compose fajl
$ mkdir moj-sajt && cd moj-sajt $ vi compose.yaml
services:
web:
image: nginx:1.25-alpine
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html:ro
$ mkdir html $ echo "<h1>Radi!</h1>" > html/index.html $ docker compose up -d $ curl localhost:8080
Rušenje:
$ docker compose down
Nekoliko reči o YAML-u
Compose fajl je YAML, a YAML je osetljiv na razmake. Dva pravila koja rešavaju devedeset posto problema:
- Tabulatori nisu dozvoljeni. Samo razmaci.
- Uvlačenje mora biti dosledno — po dva razmaka po nivou.
Ako ti compose kaže „yaml: line 12: did not find expected key", skoro sigurno je uvlačenje. Proveri fajl bez pokretanja:
$ docker compose config
Ispisuje kompletnu, razrešenu konfiguraciju — ili grešku sa brojem reda.
Aplikacija i baza
Ovo je razlog zbog kojeg Compose postoji:
services:
web:
build: .
ports:
- "8000:8000"
environment:
DATABASE_URL: postgresql://app:tajna@baza:5432/appdb
depends_on:
- baza
restart: unless-stopped
baza:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: tajna
POSTGRES_DB: appdb
volumes:
- podaci:/var/lib/postgresql/data
restart: unless-stopped
volumes:
podaci:
Obrati pažnju na DATABASE_URL — adresa baze je
baza, ime servisa. Compose pravi zajedničku mrežu u kojoj se
servisi vide po imenu, pa ti ne treba ni IP adresa ni ručno povezivanje.
Servis web nema image nego build: .
— gradi se iz
Dockerfile-a u
tekućem direktorijumu.
Ključne stavke
image i build
image: postgres:16-alpine
build: .
build:
context: .
dockerfile: Dockerfile.prod
args:
VERZIJA: "2.0"
ports
ports: - "8080:80" # port hosta : port kontejnera - "127.0.0.1:5432:5432"
Drugi oblik vezuje port samo za loopback, pa baza nije dostupna spolja. Za sve što ne mora biti javno, ovo je pravilo a ne preporuka.
Ako servisu port ne treba spolja — a bazi obično ne treba — jednostavno ga ne objavljuj. Ostali servisi mu i dalje pristupaju kroz zajedničku mrežu.
volumes
volumes: - podaci:/var/lib/postgresql/data # imenovani volumen - ./html:/usr/share/nginx/html:ro # bind mount, samo za čitanje - ./config/nginx.conf:/etc/nginx/nginx.conf:ro
Imenovani volumen je za podatke kojima upravlja Docker — baze, otpremljeni
fajlovi. Mora se prijaviti u odeljku volumes: na dnu fajla.
Bind mount povezuje direktorijum sa hosta. Odličan je u razvoju, jer
menjaš fajl i odmah vidiš efekat. Sufiks :ro znači da kontejner
ne može da piše — koristi ga za konfiguracije uvek.
Detaljnije o samim volumenima u lekciji docker volume.
environment i env_file
environment: APP_MODE: production DATABASE_URL: postgresql://app:tajna@baza:5432/appdb
Lozinke ipak ne drži u compose fajlu — on završi u Git-u. Izdvoji ih:
env_file: - .env
.env:
POSTGRES_PASSWORD=stvarna-lozinka DATABASE_URL=postgresql://app:stvarna-lozinka@baza:5432/appdb
Zatim .env obavezno u .gitignore i
.dockerignore.
Vrednosti iz .env mogu se koristiti i u samom compose
fajlu:
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?mora biti postavljena}
restart
restart: unless-stopped
| Vrednost | Ponašanje |
|---|---|
no | podrazumevano, ne restartuje |
on-failure | samo ako je izašao sa greškom |
always | uvek, uključujući posle ručnog zaustavljanja |
unless-stopped | uvek, osim ako si ga ti zaustavio |
Za servise na serveru koristi unless-stopped.
depends_on i zašto nije dovoljan
depends_on: - baza
Ovo određuje samo redosled pokretanja. Kontejner baze je pokrenut — ali PostgreSQL unutra možda još učitava. Aplikacija se javi, dobije odbijenu vezu i padne.
Rešenje je provera zdravlja:
services:
web:
build: .
depends_on:
baza:
condition: service_healthy
baza:
image: postgres:16-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
Sad web čeka da baza zaista odgovara. Ovo je razlika između
sklopa koji se digne iz prve i onog koji zahteva da ga „pokreneš još
jednom".
Komande
$ docker compose up -d # pokreni u pozadini $ docker compose up # pokreni i prati logove $ docker compose down # zaustavi i ukloni $ docker compose down -v # i obriši volumene (podaci nestaju!) $ docker compose ps # šta radi $ docker compose logs -f # logovi svih servisa $ docker compose logs -f web # logovi jednog servisa $ docker compose restart web $ docker compose stop $ docker compose start $ docker compose build # sagradi image-e $ docker compose up -d --build # sagradi pa pokreni $ docker compose pull # povuci novije verzije
Ulazak u kontejner:
$ docker compose exec web /bin/bash $ docker compose exec baza psql -U app appdb
Jednokratna komanda u novom kontejneru:
$ docker compose run --rm web python manage.py migrate
Razlika je bitna: exec ulazi u kontejner koji već radi,
run pravi novi. Za migracije baze hoćeš run --rm.
Pazi na down -v — briše imenovane volumene, dakle i
sadržaj baze. Bez -v podaci preživljavaju.
Skaliranje
$ docker compose up -d --scale web=3
Radi samo ako servis nema fiksno objavljen port — tri kontejnera ne mogu da dele port 8080 na hostu. Otuda i potreba za nečim ispred, poput HAProxy-ja ili nginx-a kao reverse proxy-ja.
Mreže
Compose sam pravi jednu mrežu i sve servise stavlja u nju. Ako hoćeš da odvojiš bazu od spoljnog sloja:
services:
proxy:
image: nginx:1.25-alpine
networks:
- spolja
web:
build: .
networks:
- spolja
- unutra
baza:
image: postgres:16-alpine
networks:
- unutra
networks:
spolja:
unutra:
internal: true
Oznaka internal: true znači da ta mreža nema izlaz na
internet. Baza je dostupna aplikaciji, ali ne i spolja — niti sama može
napolje.
Razvoj i produkcija u istom projektu
Compose spaja više fajlova, pri čemu kasniji dopunjuje raniji:
compose.yaml:
services:
web:
build: .
environment:
APP_MODE: production
compose.override.yaml:
services:
web:
volumes:
- .:/app
environment:
APP_MODE: development
ports:
- "8000:8000"
Fajl sa override u imenu Compose učitava automatski, pa
lokalno dobijaš razvojne postavke. Na serveru ga jednostavno nema, ili
navedeš fajlove izričito:
$ docker compose -f compose.yaml -f compose.prod.yaml up -d
Kompletan primer
Aplikacija, baza, keš i reverse proxy:
services:
proxy:
image: nginx:1.25-alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- web
restart: unless-stopped
web:
build: .
env_file:
- .env
depends_on:
baza:
condition: service_healthy
redis:
condition: service_started
restart: unless-stopped
baza:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?nedostaje lozinka}
POSTGRES_DB: appdb
volumes:
- podaci:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 5s
timeout: 3s
retries: 5
restart: unless-stopped
redis:
image: redis:7-alpine
command: redis-server --save 60 1
volumes:
- keš:/data
restart: unless-stopped
volumes:
podaci:
keš:
Nijedan servis osim proxy ne objavljuje port. Aplikacija,
baza i Redis dostupni su samo unutar mreže — spolja se ulazi isključivo
kroz nginx.
Backup baze iz Compose sklopa
Spoj sa onim iz bash serije:
#!/usr/bin/env bash
set -euo pipefail
cd /opt/moja-app
datum=$(date +%Y%m%d_%H%M)
arhiva="/backup/appdb_${datum}.sql.gz"
docker compose exec -T baza \
pg_dump -U app appdb | gzip > "$arhiva"
echo "Napravljeno: $arhiva"
Zastavica -T isključuje dodelu terminala — bez nje komanda
pokrenuta iz
cron-a ne radi.
Compose ili Kubernetes
Compose je za jedan server. Sve dok ti aplikacija staje na jednu mašinu, on je jednostavniji, brži i sasvim dovoljan — i mnogo manjih produkcija godinama radi upravo tako.
Kubernetes rešava ono što Compose ne pokušava: raspoređivanje po više čvorova, automatsko skaliranje, oporavak od pada mašine. Cena je znatno veća složenost. O tome u Kubernetes lekcijama.
Vežba
Sklopi Compose fajl sa:
- WordPress-om na portu 8080
- MySQL bazom koja ne objavljuje port
- imenovanim volumenima za bazu i za
wp-content - lozinkama u
.env, ne u compose fajlu healthcheck-om na bazi i uslovnimdepends_on-om
Zatim ga sruši sa docker compose down (bez
-v), pa ponovo digni — sadržaj mora ostati.
Povezano: Dockerfile i docker build · docker volume · docker network · DevOps
Comments
Post a Comment