МАТЕРИАЛЫ КУРСА
СИИ • НАВИГАЦИЯ

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

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

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

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

Топология compose

Скелет 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» — привычка, которая однажды окажется на публичном IP. Нужен доступ к внутреннему сервису — открой его временно или сходи через docker compose exec.

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

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

Healthcheck: «запущен» ≠ «готов»

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

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

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

Dev и staging из одних файлов

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

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

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

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

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

Грабли, на которые наступают все

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

А Kubernetes?

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

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