Приложение падает с ошибкой подключения к базе данных в первые секунды после docker compose up, хотя в файле честно прописан depends_on с именем сервиса базы. Через пару секунд, если перезапустить вручную, все работает. Проблема не в depends_on как таковом, а в том, что он по умолчанию означает совсем не то, что кажется на первый взгляд.
Что depends_on проверяет на самом деле по умолчанию
Базовый depends_on без дополнительных условий гарантирует только порядок запуска контейнера, а не готовность сервиса внутри него принимать соединения.
services:
web:
image: myapp:latest
depends_on:
- db
db:
image: postgres:16
Здесь Docker Compose запустит контейнер db раньше, чем web, это правда. Но само понятие “запущен” для Docker означает лишь то, что процесс postgres внутри контейнера начал стартовать, а не то, что PostgreSQL уже прошел инициализацию и готов принимать TCP соединения на своем порту. Между этими двумя моментами вполне может быть разрыв в несколько секунд, особенно при первом запуске с инициализацией пустой базы данных.
Явное условие через healthcheck
Решение — добавить в сервис базы данных healthcheck, реальную проверку готовности, а в зависимом сервисе указать явное условие ожидания именно здоровья, а не просто факта запуска контейнера.
services:
web:
image: myapp:latest
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
Теперь Docker Compose не просто запустит db раньше web, а будет периодически выполнять команду pg_isready внутри контейнера db, и только когда эта проверка несколько раз подряд успешно отработает, контейнер получит статус healthy, и лишь после этого начнется запуск web.
Три доступных условия depends_on
| Условие | Что означает | Когда уместно |
|---|---|---|
| service_started | Контейнер запущен, процесс стартовал | Сервисы без состояния готовности, простые скрипты |
| service_healthy | Пройден healthcheck сервиса | База данных, кеш, очередь сообщений |
| service_completed_successfully | Контейнер завершился с кодом 0 | Одноразовые задачи вроде миграций базы данных |
Третье условие особенно удобно для сценария с миграциями: отдельный сервис-контейнер выполняет миграцию схемы базы и завершается, а основное приложение стартует только после его успешного завершения.
services:
migrate:
image: myapp:latest
command: npm run migrate
depends_on:
db:
condition: service_healthy
web:
image: myapp:latest
depends_on:
migrate:
condition: service_completed_successfully
Частая ошибка: healthcheck без реальной проверки готовности
healthcheck:
test: ["CMD", "true"]
Такой healthcheck технически существует и формально проходит мгновенно, но не проверяет вообще ничего осмысленного про готовность сервиса, это простая имитация проверки, которая ничем не отличается от полного отсутствия healthcheck по сути решаемой проблемы. Проверка должна реально стучаться в порт или выполнять реальную команду вроде pg_isready, redis-cli ping или простого curl на endpoint здоровья приложения.
Как быстро увидеть свою реальную проблему
Если сомневаетесь, есть ли в вашем стеке эта проблема, Docker Compose Service Graph покажет все связи depends_on в вашем файле, и по этому списку легко заметить сервисы, у которых нет условия healthy, хотя логически они точно должны быть готовы принимать соединения, прежде чем зависимый сервис начнет их использовать.
Итоговый чеклист
Базовый depends_on без условия гарантирует только порядок запуска контейнеров, а не готовность сервиса внутри принимать реальные соединения.
Для баз данных, кешей и очередей сообщений используйте condition service_healthy вместе с реальным healthcheck, который действительно проверяет готовность, а не имитирует проверку заглушкой.
Для одноразовых задач вроде миграций используйте condition service_completed_successfully вместо healthcheck, потому что такой контейнер не должен продолжать работать бесконечно.
Настраивайте interval, timeout и retries healthcheck разумно: слишком редкая проверка увеличит время ожидания старта, слишком частая создаст лишнюю нагрузку на сервис в момент его собственной инициализации.