Зачем нужен Docker Compose
После предыдущих модулей у тебя в голове есть docker run, -v, -p, --network. Этого достаточно, чтобы поднять один контейнер. Но как только нужно поднять три-четыре сервиса вместе — Postgres, Redis, приложение, плюс какой-нибудь init-job — docker run превращается в проблему. Docker Compose решает её декларативно.
jq и yq: JSON и YAML в shell
Боль без compose
Допустим, ты хочешь локально поднять стенд: Postgres + Redis + Python ETL-app. Без compose:
# 1. Сеть, чтобы сервисы видели друг друга по имени.
docker network create etl-net
# 2. Volume для Postgres data.
docker volume create pgdata
# 3. Postgres.
docker run -d \
--name postgres \
--network etl-net \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_DB=etl \
-v pgdata:/var/lib/postgresql/data \
-p 127.0.0.1:5432:5432 \
--healthcheck-cmd 'pg_isready -U postgres -d etl' \
--healthcheck-interval 5s \
postgres:17
# 4. Redis.
docker run -d \
--name redis \
--network etl-net \
-p 127.0.0.1:6379:6379 \
redis:7-alpine
# 5. Подождать, пока Postgres healthy. Вручную, потому что у docker run нет depends_on.
until docker exec postgres pg_isready -U postgres -d etl; do sleep 1; done
# 6. Приложение.
docker run -d \
--name app \
--network etl-net \
-e DATABASE_URL=postgresql://postgres:secret@postgres:5432/etl \
-e REDIS_URL=redis://redis:6379 \
-v $(pwd)/src:/app/src:ro \
myorg/etl-app:dev
Это уже 30 строк. Через неделю забудешь, какие env vars передавал. Коллега не сможет повторить — нужно описать команды в README, где они быстро устареют.
Cleanup тоже ручной:
docker rm -f app redis postgres
docker volume rm pgdata
docker network rm etl-net
Caмая частая ошибка — забыть прописать --network хоть в одном из контейнеров, и потом полчаса дебажить «почему app не видит redis».
Compose: тот же стенд в YAML
services:
postgres:
image: postgres:17
environment:
POSTGRES_PASSWORD: secret
POSTGRES_DB: etl
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "127.0.0.1:5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d etl"]
interval: 5s
redis:
image: redis:7-alpine
ports:
- "127.0.0.1:6379:6379"
app:
image: myorg/etl-app:dev
depends_on:
postgres:
condition: service_healthy
environment:
DATABASE_URL: postgresql://postgres:secret@postgres:5432/etl
REDIS_URL: redis://redis:6379
volumes:
- ./src:/app/src:ro
volumes:
pgdata:
Запуск:
docker compose up -d
Compose сам:
- Создал сеть
<projectname>_default(имя — по папке). - Создал volume
<projectname>_pgdata. - Запустил
postgresиredis. - Подождал, пока postgres healthy.
- Запустил
app.
Cleanup:
docker compose down -v
Всё — сеть, контейнеры, volume — удалены одной командой.
Compose v1 vs v2
Compose существует с 2014. Есть две версии реализации, не путать с версиями файла:
| Что | Compose v1 | Compose v2 |
|---|---|---|
| Реализация | Python (отдельный binary docker-compose) | Go plugin для Docker CLI (docker compose) |
| Status в 2026 | EOL с июля 2023 (deprecated) | Активная разработка, default |
| Команда | docker-compose up | docker compose up |
| File format | Поддерживал version: '3.x' сверху | version: устарел, игнорируется |
Когда в туториалах видишь docker-compose up через дефис — это v1, устаревшая. Используй docker compose up (через пробел) — это v2, плагин к docker CLI.
docker compose version
# Docker Compose version v2.39.0
Compose v2 — это plugin: ~/.docker/cli-plugins/docker-compose. Устанавливается вместе с Docker Engine 28, OrbStack, Docker Desktop.
Не пиши version: '3.8' в новых compose-файлах. С 2023 года ключ version: игнорируется compose v2 и выдаёт warning. Compose v2 определяет версию синтаксиса автоматически.
Compose как DAG зависимостей
В compose сервисы можно связывать через depends_on и condition:
services:
app:
depends_on:
postgres:
condition: service_healthy
kafka:
condition: service_started
compose up смотрит на эти связи и строит граф (DAG — directed acyclic graph). Сервисы стартуют в правильном порядке. Параллельно стартуют те, что не зависят друг от друга.
depends_on без condition — старая семантика, проверяет только «контейнер запущен», но не «приложение готово». Это разница между «postgres запустился» и «postgres готов принимать SQL». Поэтому condition: service_healthy критичен.
Имя проекта
docker compose -p etl up -d
# Создаёт сеть etl_default, volume etl_pgdata.
Без явного -p имя проекта — это имя папки, в которой лежит compose.yml. Папка etl/ -> проект etl. Папка My Project (2)/ -> проект my_project_2 (нормализованный).
Это влияет на:
- Имена volumes и networks (
<project>_<name>). - Изоляцию между одновременно запущенными стендами на одной машине.
Можешь запустить два одинаковых compose-стенда из разных папок: они не пересекутся, потому что project name разный.
Файлы compose ищет автоматически
cd ./my-project
docker compose up
Compose ищет:
compose.yamlcompose.ymldocker-compose.yamldocker-compose.yml
В порядке приоритета. Современный канонический — compose.yaml, но docker-compose.yml встречается чаще в существующих проектах. Можно явно указать файл:
docker compose -f my-stack.yml up
Попробуй сам
# 1. Минимальный compose.
mkdir -p ./compose-demo && cd ./compose-demo
cat > compose.yml <<'YAML'
services:
hello:
image: alpine:3.20
command: sh -c "echo Hello from compose; sleep 5"
YAML
docker compose up
# Запускается, печатает, выходит через 5 секунд.
# 2. Сравни с -p.
docker compose -p myproj up
# Контейнер называется myproj-hello-1, а не compose-demo-hello-1.
# 3. Список процессов.
docker compose ps -a
# Список контейнеров текущего compose-проекта.
# 4. Cleanup.
docker compose down
cd ..
rm -rf compose-demo
В команде compose много под-команд: up, down, ps, logs, exec, build, pull, restart, pause, unpause, stop, start, kill. Они работают почти как docker <cmd>, но в рамках текущего compose-проекта. Не нужно помнить имена контейнеров — compose сам знает, что относится к этому стенду.
В следующем уроке — структура compose.yml: services, image, build, ports, environment, volumes, networks.