DevOps
Docker Compose и multi-container
ML-стек: FastAPI + Redis + PostgreSQL + Celery + Flower. Вручную - 5 терминалов, 5 команд, строгий порядок запуска, половина команды не помнит нужные флаги. Compose: один файл, `docker compose up`. 30 секунд - весь стек работает. `docker compose down` - весь стек остановлен. Это не деплой инструмент - это инструмент для того чтобы окружение разработки было воспроизводимым.
- **ML-стек**: FastAPI + Celery + Redis + PostgreSQL поднять за 30 секунд для любого члена команды без 'как это установить'
- **CI/CD**: GitHub Actions запускает тесты против настоящей PostgreSQL через Compose - не mock, не SQLite
- **Команда из 5 человек**: одинаковое окружение без 'у меня работает'. README содержит одну команду: `docker compose up`
- **LLM inference**: vLLM + Redis (rate limiting) + Nginx одной командой - воспроизводимо на любом сервере с Docker
Compose-файл
**2014 год.** Компания Orchard выпустила Fig - инструмент для запуска multi-container приложений через один YAML-файл. Docker купил Fig и переименовал в Compose. Один файл заменил десятки страниц документации по запуску стека. `docker-compose.yml` описывает сервисы, сети и volumes декларативно: не «как запускать», а «что должно быть запущено». Compose читает файл и сам разбирается с порядком, сетями и монтированием.
**Секреты в .env**: файл `.env` рядом с `docker-compose.yml` подхватывается автоматически. Переменные внутри файла подставляются через `${VAR_NAME}` в compose-файле. `.env` добавляется в `.gitignore` - ключи и пароли не попадают в репозиторий.
**Bind mount vs Named volume**: `./models:/models` - bind mount, папка хоста проброшена внутрь (для разработки, hot reload). `redis_data:/data` - named volume, Docker управляет им сам (для production данных). Смешивать можно: development окружение часто использует оба типа одновременно.
В docker-compose.yml описан сервис `api` с `ports: ["8000:8000"]` и сервис `redis` без `ports`. Что это означает?
Сети в Compose
Compose автоматически создаёт bridge-сеть для проекта и подключает все сервисы. Внутри этой сети работает **встроенный DNS**: контейнер `backend` обращается к базе данных просто как `postgres`, к кэшу - просто как `redis`. Имя сервиса в compose-файле и есть hostname. Нет IP, нет `--link`, нет ручного `/etc/hosts`. Это то, почему `localhost` внутри контейнера - неправильный адрес для другого сервиса.
**localhost - ловушка**: внутри контейнера `localhost` или `127.0.0.1` указывает на loopback самого контейнера, не на хост-машину и не на другой сервис. Типичная ошибка при переносе `.env` с локальной разработки в Docker - `DATABASE_URL=localhost:5432` перестаёт работать. Правило: в Docker - имя сервиса, вне Docker - localhost.
Сервис `api` в Compose пытается подключиться к PostgreSQL через `localhost:5432`. PostgreSQL запущен как сервис `db` в том же Compose-файле. Что произойдёт?
depends_on и порядок
**depends_on не ждёт готовности сервиса.** Он ждёт когда контейнер запустился - это принципиально разные события. PostgreSQL процесс стартует за 0.1 секунды, но принимает соединения через 2-3 секунды после инициализации WAL и recovery. Приложение с `depends_on: [db]` запустится когда контейнер db перешёл в Running, но подключиться к базе ещё не сможет. Без healthcheck нужен retry в самом приложении.
**condition: service_completed_successfully** - специальный вариант для одноразовых сервисов (миграции, seed данных). Ждёт exit code 0. Если миграция завершилась с ошибкой - зависящие сервисы не запустятся.
Сервис `api` имеет `depends_on: [db]` без condition. PostgreSQL занимает 3 секунды на полный старт. Что произойдёт?
Health checks
**HEALTHCHECK** - директива которая периодически проверяет: сервис работает или завис. Docker выполняет test-команду внутри контейнера и отслеживает exit code: 0 - healthy, 1 - unhealthy, 2 - reserved. Статус влияет на depends_on с `condition: service_healthy`, на restart policies и на оркестраторы (Swarm, Kubernetes). Проверки пишутся под конкретный сервис: PostgreSQL - `pg_isready`, Redis - `redis-cli ping`, HTTP API - `curl`.
**start_period**: время после старта контейнера в которое неудачные проверки не считаются - контейнер остаётся в starting state, не переходит в unhealthy. Критично для сервисов с долгой инициализацией: загрузка ML модели, прогрев JVM, первый migration run.
**Compose vs Dockerfile HEALTHCHECK**: healthcheck можно задать как в Dockerfile (`HEALTHCHECK CMD ...`), так и в compose-файле. Compose-версия перекрывает Dockerfile-версию. Для разработки удобно переопределить в compose менее строгие параметры (interval: 2s вместо 30s).
depends_on + healthcheck гарантирует что сервисы запустятся строго по порядку: db, потом migrator, потом api
depends_on + healthcheck гарантирует только что зависимый сервис не стартует до healthy-статуса зависимости. Независимые сервисы запускаются параллельно - порядок между ними не определён
Compose строит граф зависимостей и запускает независимые ветки параллельно для скорости. Если worker-a и worker-b оба зависят от db, они оба дождутся db healthy, затем запустятся одновременно. Для строгого последовательного порядка нужна явная цепочка: a depends_on b depends_on c.
Два независимых сервиса - `worker-a` и `worker-b` - оба имеют `depends_on: db: condition: service_healthy`. Что гарантирует эта конфигурация?
Ключевые идеи
- **Compose-файл** - декларативное описание стека: services/networks/volumes в одном YAML. `.env` для секретов, bind mounts для dev, named volumes для production данных
- **Docker DNS**: сервисы обращаются друг к другу по имени сервиса (`redis:6379`, не `localhost:6379`). localhost внутри контейнера = loopback самого контейнера
- **depends_on** без condition - только порядок старта контейнеров, не readiness. Для ожидания готовности - healthcheck + `condition: service_healthy`
- **HEALTHCHECK** - периодическая проверка через exit code: pg_isready, redis-cli ping, curl. start_period защищает от false unhealthy при медленной инициализации
Связанные темы
Docker Compose - мост между локальной разработкой и production инфраструктурой:
- Docker: основы — Compose оперирует Docker-примитивами: images, volumes, networks
- Kubernetes — Production-grade оркестрация поверх тех же концепций что и Compose
- Serverless / Lambda — Альтернативная модель упаковки без постоянно работающих контейнеров
- Репликация баз данных — Классический multi-container сценарий: primary + replica в одном Compose
- LLM API integration — vLLM + Redis + backend - типовой ML Compose-стек с rate limiting
Вопросы для размышления
- В стартапе все разработчики используют docker compose up для локальной разработки. Тимлид предлагает использовать тот же Compose-файл для деплоя на продакшн сервер. Какие конкретные проблемы создаёт такой подход?
- Сервис `trainer` стартует только после того как db и migrator завершились успешно. Как описать эту зависимость в Compose-файле и почему простой depends_on: [db, migrator] не достаточен?
- Команда жалуется что `docker compose up` у разных разработчиков даёт разное поведение - у одних база инициализируется, у других нет. Как воспроизводимо решить проблему порядка инициализации?