Learning Platform
Глоссарий Troubleshooting
Урок 12.01 · 20 мин
Начальный
dockercomposeyaml

Зачем нужен 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 сам:

  1. Создал сеть <projectname>_default (имя — по папке).
  2. Создал volume <projectname>_pgdata.
  3. Запустил postgres и redis.
  4. Подождал, пока postgres healthy.
  5. Запустил app.

Cleanup:

docker compose down -v

Всё — сеть, контейнеры, volume — удалены одной командой.

docker run x N vs compose: что меняется
docker run x 4 + network create + volume createИмперативный подход: ты сам отвечаешь за порядок, флаги, имена, очистку. Каждая команда длинная и легко забываешь
vs
compose up - один YAMLДекларативный подход: описал желаемое состояние, compose его обеспечил. Сам создаст сеть, дождётся healthcheck, проставит env vars
bash-скрипт = воспроизводимостьБывает: команды в README, README устаревает, на новой машине что-то ломается. Скрипт быстро превращается в legacy
compose.yml = state-as-codeYAML в репозитории, под версией Git. Любой клон и docker compose up — работает на любой машине. Это и есть infrastructure-as-code в маленьком масштабе

Compose v1 vs v2

Compose существует с 2014. Есть две версии реализации, не путать с версиями файла:

ЧтоCompose v1Compose v2
РеализацияPython (отдельный binary docker-compose)Go plugin для Docker CLI (docker compose)
Status в 2026EOL с июля 2023 (deprecated)Активная разработка, default
Командаdocker-compose updocker 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.

WARNING

Не пиши 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). Сервисы стартуют в правильном порядке. Параллельно стартуют те, что не зависят друг от друга.

Compose DAG для стенда Airflow
postgresБД для метаданных Airflow. Стартует первой, healthcheck pg_isready
redisБрокер для Celery executor. Параллельно с postgres, никаких зависимостей
airflow-initОдин раз бежит миграции метаданных. Зависит от postgres healthcheck
webserverUI Airflow на 8080. Стартует после init
schedulerРасписание DAG. Параллельно webserver
workerCelery worker, тянет таски из redis. После init

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 ищет:

  1. compose.yaml
  2. compose.yml
  3. docker-compose.yaml
  4. docker-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
TIP

В команде 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.


Проверка знанийKnowledge check
Что произойдёт, если в compose.yml убрать depends_on целиком и сделать app , который коннектится к Postgres? Опиши race condition и почему depends_on: condition: service_healthy фундаментально отличается от depends_on без condition.
ОтветAnswer
Без depends_on compose стартует все сервисы параллельно. App стартует одновременно с postgres, открывает TCP-соединение на postgres:5432, попадает в ConnectionRefused или ConnectionReset, потому что postgres-процесс ещё не успел подняться (initdb на пустом volume может занять 2-5 секунд). Это classic race condition. С depends_on без condition: compose ждёт, пока контейнер postgres перейдёт в state "running" — это значит, главный процесс запустился, но Postgres-демон внутри может ещё инициализироваться. App снова попадает в ConnectionRefused. С depends_on: condition: service_healthy compose проверяет healthcheck сервиса postgres (например, pg_isready) и стартует app ТОЛЬКО когда postgres стал healthy. Это значит: TCP-listener поднят, БД готова к запросам, миграции applied. Это единственный надёжный способ.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. В чём фундаментальная разница compose v1 и v2?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5