Healthchecks в Docker Compose для Laravel: как сделать так, чтобы сервисы запускались в правильном порядке

от автора

Если вы хоть раз поднимали Laravel-проект в Docker Compose, наверняка сталкивались с ситуацией: контейнер с приложением стартует раньше, чем база данных успевает принять соединения, и миграции падают с ошибкой SQLSTATE[08006] или Connection refused. Перезапустишь — всё работает. На локалке терпимо, но в продакшене — это в падающие деплои.

По умолчанию Docker считает контейнер «живым», если его процесс запущен. Но это не всегда означает, что сервис внутри готов к работе.

Решение — правильно настроенные healthcheck’и и условие depends_on с параметром condition: service_healthy. В этой статье разберём, как это сделать для типичного стека Laravel: PHP-FPM, PostgreSQL, Redis и Nginx.

Почему depends_on без healthcheck не работает

Многие думают, что depends_on: [db] заставит контейнер с приложением ждать, пока база данных будет готова. На самом деле Docker Compose ждёт только запуска контейнера — то есть момента, когда процесс внутри стартовал. Между «процесс запустился» и «база готова принимать запросы» может пройти 5–15 секунд, особенно при первой инициализации.

Чтобы Compose действительно ждал готовности сервиса, нужно:

  1. Определить healthcheck у зависимого сервиса (БД, Redis и т. д.).

  2. В сервисе-потребителе указать depends_on в расширенной форме с condition: service_healthy.

Healthcheck для PostgreSQL

В официальный образ Postgres встроена утилита pg_isready — она и есть самая надёжная проверка готовности:

services:  db:    image: postgres:16-alpine    environment:      POSTGRES_DB: app      POSTGRES_USER: app      POSTGRES_PASSWORD: secret    volumes:      - pgdata:/var/lib/postgresql/data    healthcheck:      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]      interval: 5s      timeout: 3s      retries: 10      start_period: 10s

Важный момент — параметр start_period. Он задаёт «грейс-период»: в течение этого времени неуспешные проверки не считаются провалами. Для Postgres это критично, потому что при первом запуске инициализируется PGDATA, и pg_isready временно отвечает «not accepting connections».

Healthcheck для Redis

redis:    image: redis:7-alpine    healthcheck:      test: ["CMD", "redis-cli", "ping"]      interval: 5s      timeout: 3s      retries: 5      start_period: 5s

Команда redis-cli ping возвращает PONG, когда сервер готов. Если у вас включена авторизация, добавьте -a $REDIS_PASSWORD или используйте переменную окружения REDISCLI_AUTH, чтобы пароль не светился в docker ps.

Healthcheck для PHP-FPM

С PHP-FPM сложнее: в стандартный образ php:8.3-fpm-alpine не входит ни curl, ни wget. Самый универсальный способ — использовать встроенный в PHP-FPM статус-пинг. Включаем его в конфиге пула:

  app:    build:      context: .      dockerfile: docker/php/Dockerfile    depends_on:      db:        condition: service_healthy      redis:        condition: service_healthy    healthcheck:      test: ["CMD-SHELL", "SCRIPT_NAME=/ping SCRIPT_FILENAME=/ping REQUEST_METHOD=GET cgi-fcgi -bind -connect 127.0.0.1:9000 | grep -q pong"]      interval: 10s      timeout: 3s      retries: 5      start_period: 15s

Не забудьте установить fcgi в Dockerfile:

RUN apk add --no-cache fcgi

Healthcheck для Nginx

Для Nginx достаточно простой проверки через wget (он есть в alpine-образе):

  nginx:    image: nginx:alpine    depends_on:      app:        condition: service_healthy    healthcheck:      test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"]      interval: 10s      timeout: 3s      retries: 3      start_period: 5s    ports:      - "8080:80"

В конфиге Nginx добавьте отдельный location, который не идёт в PHP:

location = /health {    access_log off;    add_header Content-Type text/plain;    return 200 "ok";}

Полезные флаги

docker compose up --wait — поднимает все сервисы и блокирует консоль до тех пор, пока они не станут healthy (или не упадут). Идеально подходит для CI.

docker compose ps покажет столбец STATUS с пометкой (healthy) или (unhealthy) — удобно для быстрой диагностики на сервере.

Подводные камни

  • Слишком короткий interval создаёт лишнюю нагрузку. 5–10 секунд — обычно достаточно.

  • retries × interval определяет максимальное время ожидания. Если БД восстанавливается из бэкапа 2 минуты, а вы поставили retries: 3 и interval: 5s — контейнер пометится как unhealthy раньше, чем база реально упадёт.

  • Healthcheck должен проверять реальную готовность, а не просто наличие процесса. Например, pgrep postgres вернёт успех ещё до того, как Postgres примет первое соединение.

  • Не злоупотребляйте condition: service_healthy в проде на одном узле: если зависимый сервис упадёт и перезапустится, потребитель не «переподпишется» автоматически. Эта механика работает только при старте.

Итог

Несколько строчек YAML экономят часы отладки и убирают целый класс «магических» проблем: миграции, которые иногда падают; тесты, которые иногда красные; деплои, которые иногда обрываются. Если у вас в проекте до сих пор стоит голый depends_on без условий — самое время это починить.

Больше Healthcheck конструкций для docker compose вы можете найти здесь.

ссылка на оригинал статьи https://habr.com/ru/articles/1040132/