Стек Compose
Пять сервисов, две сети, порядок старта. Схема соответствует проекту 3.
Состав
хост
│ 127.0.0.1:8000
▼
╔═════════════════════╗
║ сеть frontend ║
║ ║
║ ┌───────┐ ║
║ │ api │ ║
║ └───┬───┘ ║
╚══════════┼══════════╝
│
╔══════════┼════════════════════════════╗
║ сеть backend (internal: true) ║
║ │ ║
║ ┌─────┴─────┬──────────┐ ║
║ ▼ ▼ ▼ ║
║ ┌─────┐ ┌───────┐ ┌────────┐ ║
║ │ db │ │ cache │ │ worker │ ║
║ └──┬──┘ └───┬───┘ └────────┘ ║
║ │ │ ║
║ ┌──┴───┐ ┌───┴─────┐ ║
║ │pgdata│ │cachedata│ тома ║
║ └──────┘ └─────────┘ ║
║ ║
║ ┌─────────┐ ║
║ │ migrate │ разовая задача ║
║ └─────────┘ ║
╚═══════════════════════════════════════╝
Как читать
Двойная рамка — сеть, не машина: всё это работает на одном хосте. api состоит в двух сетях, потому что он единственный принимает запросы снаружи.
migrate нарисован отдельно: это не сервис, а задача, которая завершается.
Порядок старта
время ──────────────────────────────────────────────►
db [запуск][инициализация][healthy ✓]──────────────
│
cache [запуск][healthy ✓]────────┤
│
migrate └►[применяет][exit 0]
│
api └►[запуск][healthy ✓]
worker └►[запуск]
Три разных условия, и каждое нужно своё:
| Сервис | Ждёт | Условие |
|---|---|---|
migrate | Готовности базы | service_healthy |
api | Схемы и зависимостей | service_completed_successfully + service_healthy |
worker | Схемы | service_completed_successfully |
Что здесь неочевидно
depends_on в форме списка ждёт запуска, а не готовности.
depends_on: [db] depends_on:
db:
condition: service_healthy
──────────────────── ────────────────────────────
db: container запущен db: pg_isready отвечает
api: стартует api: стартует
api: Connection refused api: подключается
PostgreSQL после старта container'а несколько секунд инициализирует кластер. Симптом плавающий: на прогретой машине успевает, на холодной нет.
migrate должен завершиться, а не работать.
docker compose ps --format 'table {{.Service}}\t{{.Status}}'
api Up 30 seconds (healthy)
cache Up 45 seconds (healthy)
db Up 45 seconds (healthy)
migrate Exited (0) 35 seconds ago ← правильно
worker Up 30 seconds (healthy)
migrate в состоянии Up означает, что в конце команды стоит что-то вроде sleep infinity — container живёт, работа не сделана, никто не узнает.
internal: true у backend запрещает выход наружу изнутри. Это не то же, что «не публиковать порты»: то управляет входом, это — выходом.
Три конфигурации из одного файла
compose.yaml базовая
│
├── + compose.dev.yaml разработка:
│ монтирование кода,
│ порты базы на 127.0.0.1,
│ read_only: !override false
│
└── + compose.test.yaml тесты:
tmpfs вместо тома,
fsync=off,
сервис tests
docker compose up -d # базовая
docker compose -f compose.yaml -f compose.dev.yaml up -d # разработка
docker compose -f compose.yaml -f compose.test.yaml run --rm tests
Отдельные файлы, а не профили: когда отличий много, в одном файле отладочные настройки рано или поздно уезжают в эксплуатацию.
Проверка воспроизводимости
Один успешный запуск ничего не доказывает — гонка проявляется не всегда:
for i in $(seq 10); do
docker compose down -v >/dev/null 2>&1
docker compose up -d >/dev/null 2>&1
sleep 15
printf '%2d: %s\n' "$i" "$(docker compose ps api --format '{{.Status}}')"
done
Десять запусков из чистого состояния — минимальная проверка.
Подробнее
Урок 9.4. Healthcheck и зависимости
Урок 9.7. Практический стек
Проект 3. Multi-service stack