# Compose: вся система одной командой

> compose.yml, сети и volumes, .env, healthcheck и depends_on, dev/staging, когда нужен Kubernetes

_Источник: https://sii.sergeivolchkov.ru/materials/docker/compose_





Критерий приёмки звучит буквально так: **`docker compose up`
поднимает всю систему одной командой**. Не «бэкенд, а базу поставьте
локально», не «сначала запустите три терминала» — одна команда и рабочий
контур.

<img alt="Топология compose" src="__img0" />

## Скелет compose.yml [#скелет-composeyml]

```yaml title="compose.yml"
services:
  api:
    build: ./backend
    environment:
      DATABASE_URL: postgresql://app:${DB_PASSWORD}@db:5432/app
      LLM_API_KEY: ${LLM_API_KEY}
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 10s
      timeout: 3s
      retries: 5
      start_period: 20s

  web:
    build: ./frontend
    ports:
      - "3000:3000"        # единственный порт наружу
    depends_on:
      api:
        condition: service_healthy

  db:
    image: postgres:17-alpine
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app"]
      interval: 5s
      retries: 10

volumes:
  pgdata:
```

Ключ `version:` больше не пишут — он устарел вместе со схемой Compose v1.
Файл называется `compose.yml` — так его и называем в курсе (Compose
понимает и `compose.yaml`, и старое `docker-compose.yml`).

## Сеть, порты, тома [#сеть-порты-тома]

**Сеть.** Compose создаёт общую сеть, и сервисы видят друг друга по имени:
`db:5432`, `api:8000`. Никаких `localhost` между контейнерами — внутри
контейнера `localhost` это он сам.

**Порты — только там, где реально нужен вход снаружи.** Опубликованный
порт базы «чтобы посмотреть через DBeaver» на проде превращается в
открытый доступ к данным. Нужен доступ к внутреннему сервису — открой его
временно или сходи через `docker compose exec`.

**Volumes — граница между «данные» и «мусор».** Именованный том переживает
`docker compose down` и пересоздание контейнера; данные, лежащие в слое
контейнера, — нет. Всё, что нельзя потерять (БД, загруженные файлы), живёт
в томе. `down -v` удаляет тома — это единственная команда здесь, которая
стирает данные.

**Переменные окружения.** `.env` подхватывается автоматически и &#x2A;*лежит в
`.gitignore`**; в репозитории — `.env.example` со всеми ключами и
безопасными заглушками. Правило простое: переменная появилась в
`compose.yml` — в ту же минуту появилась в `.env.example`.

## Healthcheck: «запущен» ≠ «готов» [#healthcheck-запущен--готов]

Без healthcheck `depends_on` ждёт только запуска процесса. Постгрес при
этом ещё инициализирует кластер, и API падает на первой же миграции —
классическое «у меня после `up` всё сломалось, а после второго `up`
заработало».

Связка из двух частей: у зависимости описан `healthcheck`, у зависящего —
`condition: service_healthy`. Тогда Compose держит старт, пока проверка не
пройдёт. Полезная деталь — `start_period`: время на прогрев, в течение
которого провалы не считаются падениями.

Тот же healthcheck потом читает прод: перезапуск нездорового контейнера,
проверка после деплоя, метрика доступности.

## Dev и staging из одних файлов [#dev-и-staging-из-одних-файлов]

Базовый `compose.yml` описывает систему, а различия окружений живут в
overrides — так staging собирается из тех же файлов, что и dev:

```bash
docker compose up                                    # dev (compose.override.yml подхватится сам)
docker compose -f compose.yml -f compose.staging.yml up -d   # staging
```

Для разработки удобен `docker compose watch`: он пересобирает или
синхронизирует сервис при изменении файлов — правишь код, контейнер
подхватывает, без ручного `up --build` по кругу.

Рабочие команды на каждый день:

```bash
docker compose up -d --build     # поднять, пересобрав изменившееся
docker compose logs -f api       # смотреть логи одного сервиса
docker compose exec api bash     # зайти внутрь
docker compose ps                # статус и healthy/unhealthy
docker compose down              # остановить (данные в томах целы)
```

## Грабли compose [#грабли-compose]

* **Правка `.env` без пересоздания.** `restart` не перечитает окружение —
  нужен `up -d` (Compose пересоздаст контейнер).
* **`ports` вместо `expose`.** Между сервисами порт публиковать не нужно:
  внутри сети доступны все порты.
* **Bind-mount поверх зависимостей.** Монтируешь `.:/app` — и затираешь
  установленные в образе `node_modules`/`.venv`. Либо ставь их вне
  рабочей папки, либо исключай монтированием пустого тома.
* **Разные версии образов у команды.** Пин конкретных тегов
  (`postgres:17-alpine`), а не `postgres:latest`.

## А Kubernetes? [#а-kubernetes]

**Не нужен.** Проект курса — это один сервер и несколько контейнеров:
Compose здесь не «упрощённый вариант», а адекватный инструмент. Kubernetes
решает задачи, которых у вас нет: десятки сервисов, автоскейлинг под
переменной нагрузкой, катящиеся обновления без даунтайма, несколько нод.
Его цена — оператор, который это обслуживает.

Переезжать имеет смысл, когда одновременно совпало: нагрузка не помещается
в один сервер, downtime при выкате стал стоить денег, и есть кому держать
кластер. До этого момента Kubernetes в студенческом проекте — это
[«цена решения»](/materials/architecture/c4-levels) без стороны выгоды.
Хорошая новость: правильно описанный Compose переносится в манифесты почти
механически — в том числе [генерацией из архитектурной
модели](/materials/architecture/aact).
