Healthcheck в docker compose для PostgreSQL, MySQL, Redis, RabbitMQ и других сервисов — PROG-TIME

Healthcheck в docker compose для PostgreSQL, MySQL, Redis, RabbitMQ и других сервисов

27.05.2026

Если контейнер «поднялся», это ещё не значит, что сервис внутри него готов принимать запросы. Postgres может стартовать 5–10 секунд, Kafka — все 30, Elasticsearch — больше минуты. Без правильных healthcheck‑ов depends_on в docker compose превращается в иллюзию: зависимый контейнер запустится раньше, чем база примет первое соединение, и приложение упадёт с connection refused на первом же запросе.

В этой статье собраны рабочие healthcheck‑конфигурации для самых популярных сервисов, которые встречаются в docker compose — баз данных, брокеров, поисковых движков, веб‑серверов, очередей и не только. Все примеры проверены на актуальных версиях образов и используют утилиты, которые уже есть внутри контейнера, без лишних зависимостей.

Синтаксис healthcheck в docker compose

Базовый блок выглядит так:

services:
  app:
    image: myapp:latest
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
  • test — команда, которая должна вернуть код 0 (healthy) или ненулевой (unhealthy). Форма ["CMD", ...] выполняется напрямую, ["CMD-SHELL", "..."] — через /bin/sh -c и понимает пайпы, переменные и кавычки.
  • interval — как часто запускать проверку (по умолчанию 30s).
  • timeout — сколько ждать ответа, прежде чем считать попытку неудачной.
  • retries — сколько подряд провалов нужно, чтобы пометить контейнер как unhealthy.
  • start_period — «грейс‑период» после старта: провалы внутри него не считаются и не увеличивают счётчик retries. Критично для медленно стартующих сервисов вроде Kafka и Elasticsearch.

А чтобы зависимые контейнеры действительно ждали готовности, в depends_on нужно использовать расширенную форму:

  app:
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

Реляционные базы данных

PostgreSQL

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: app
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 5s
      timeout: 3s
      retries: 10
      start_period: 10s

Утилита pg_isready поставляется вместе с официальным образом и проверяет, что сервер принимает соединения. Двойной $$ экранирует переменную, чтобы compose не пытался её подставить сам.

MySQL и MariaDB

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: app
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-proot"]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 30s

Для MariaDB замените mysqladmin на healthcheck.sh — официальный образ его уже содержит:

    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 10

ClickHouse

  clickhouse:
    image: clickhouse/clickhouse-server:latest
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8123/ping"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 20s

Ключ‑значение и кэши

Redis

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

Если Redis запущен с паролем, добавьте флаг -a $$REDIS_PASSWORD. Команда ping возвращает PONG, и redis‑cli отдаёт код 0 — этого достаточно.

Memcached

  memcached:
    image: memcached:1.6-alpine
    healthcheck:
      test: ["CMD-SHELL", "echo stats | nc -w 1 localhost 11211 | grep -q uptime"]
      interval: 10s
      timeout: 5s
      retries: 5

В alpine‑образе Memcached nc отсутствует — используйте memcached:1.6 на Debian или замените на проверку через /dev/tcp у busybox.

Документоориентированные и NoSQL

MongoDB

  mongo:
    image: mongo:7
    healthcheck:
      test: ["CMD", "mongosh", "--quiet", "--eval", "db.runCommand({ ping: 1 }).ok"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 20s

В версиях до 6.0 вместо mongosh используется устаревший mongo. Если включена авторизация — добавьте --username, --password и --authenticationDatabase admin.

Cassandra

  cassandra:
    image: cassandra:4.1
    healthcheck:
      test: ["CMD-SHELL", "nodetool status | grep -E '^UN'"]
      interval: 30s
      timeout: 10s
      retries: 10
      start_period: 90s

Cassandra стартует ощутимо дольше остальных — закладывайте start_period минимум полторы минуты. Шаблон UN означает «Up, Normal» — нода в кластере поднята и работает штатно.

Очереди и брокеры сообщений

RabbitMQ

  rabbitmq:
    image: rabbitmq:3.13-management-alpine
    healthcheck:
      test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
      interval: 15s
      timeout: 10s
      retries: 5
      start_period: 30s

Команда rabbitmq-diagnostics ping работает быстрее и легче, чем status, и при этом гарантирует, что брокер полностью прогрелся.

Apache Kafka

  kafka:
    image: bitnami/kafka:3.7
    healthcheck:
      test: ["CMD-SHELL", "kafka-topics.sh --bootstrap-server localhost:9092 --list || exit 1"]
      interval: 15s
      timeout: 10s
      retries: 10
      start_period: 60s

В образах от Confluent скрипт называется без расширения — kafka-topics. Проверка через --list заодно убеждается, что брокер доступен по нужному адресу и слушает порт.

NATS

  nats:
    image: nats:2.10
    command: ["-m", "8222"]
    healthcheck:
      test: ["CMD", "wget", "-q", "-O-", "http://localhost:8222/healthz"]
      interval: 10s
      timeout: 5s
      retries: 5

NATS отдаёт встроенный HTTP‑эндпоинт /healthz, если запущен с флагом -m (порт мониторинга).

Поисковые движки

Elasticsearch и OpenSearch

  elasticsearch:
    image: elasticsearch:8.13.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
    healthcheck:
      test: ["CMD-SHELL", "curl -sf http://localhost:9200/_cluster/health | grep -vq '\"status\":\"red\"'"]
      interval: 15s
      timeout: 10s
      retries: 10
      start_period: 60s

Для single‑node кластера статус yellow — это норма (нет реплик), red — поломка. Поэтому проверяем именно отсутствие красного. Для OpenSearch конфиг практически идентичен — меняется только образ.

Meilisearch

  meilisearch:
    image: getmeili/meilisearch:v1.8
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:7700/health"]
      interval: 10s
      timeout: 3s
      retries: 5

Веб‑серверы и прокси

Nginx

  nginx:
    image: nginx:1.27-alpine
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--spider", "http://localhost/"]
      interval: 10s
      timeout: 3s
      retries: 3

В alpine‑образе по умолчанию нет curl, зато есть wget из busybox. Флаг --spider не качает тело ответа — только проверяет код.

Caddy

  caddy:
    image: caddy:2-alpine
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:2019/config/"]
      interval: 10s
      timeout: 5s
      retries: 3

Caddy слушает админ‑API на :2019. Если не хочется его открывать, проверяйте обычный 80/443 порт через тот же wget --spider.

Traefik

  traefik:
    image: traefik:v3.0
    command:
      - "--ping=true"
    healthcheck:
      test: ["CMD", "traefik", "healthcheck", "--ping"]
      interval: 10s
      timeout: 5s
      retries: 3

У Traefik есть встроенный подкоманда healthcheck — самый чистый и надёжный вариант.

Объектное хранилище и файлы

MinIO

  minio:
    image: minio/minio:latest
    command: server /data --console-address ":9001"
    healthcheck:
      test: ["CMD", "mc", "ready", "local"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 15s

Команда mc ready появилась в свежих сборках и заменила старый curl /minio/health/live. Если используете старую версию — оставайтесь на curl‑проверке HTTP‑эндпоинта.

Приложения и рантаймы

Node.js

  node-app:
    image: node:20-alpine
    healthcheck:
      test: ["CMD", "node", "-e", "fetch('http://localhost:3000/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"]
      interval: 10s
      timeout: 5s
      retries: 5

В Node 18+ глобально доступен fetch — отдельный curl в образе не нужен.

Python (Django, Flask, FastAPI)

  python-app:
    image: python:3.12-slim
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request, sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8000/health').status == 200 else 1)"]
      interval: 10s
      timeout: 5s
      retries: 5

Используем стандартную библиотеку — никаких лишних зависимостей. У вашего приложения должен быть эндпоинт /health, который отвечает 200 только когда подключение к БД, кэшу и внешним сервисам в порядке.

PHP‑FPM (Laravel, Symfony)

  php-fpm:
    image: php:8.3-fpm-alpine
    healthcheck:
      test: ["CMD-SHELL", "SCRIPT_NAME=/ping SCRIPT_FILENAME=/ping REQUEST_METHOD=GET cgi-fcgi -bind -connect 127.0.0.1:9000 || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 3

В php-fpm включите pm.status_path = /status и ping.path = /ping в пуле, поставьте пакет fcgi (Alpine: apk add fcgi) — и FPM начнёт отвечать на ping напрямую, без необходимости поднимать рядом nginx.

Мониторинг и инфраструктура

Prometheus

  prometheus:
    image: prom/prometheus:latest
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:9090/-/healthy"]
      interval: 15s
      timeout: 5s
      retries: 3

Grafana

  grafana:
    image: grafana/grafana:latest
    healthcheck:
      test: ["CMD-SHELL", "wget --quiet --tries=1 --spider http://localhost:3000/api/health || exit 1"]
      interval: 15s
      timeout: 5s
      retries: 5

HashiCorp Vault

  vault:
    image: hashicorp/vault:latest
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:8200/v1/sys/health"]
      interval: 10s
      timeout: 5s
      retries: 5

Vault считается «healthy» только в активном и распечатанном (unsealed) состоянии. Если используете шифрование на старте, увеличьте start_period и подключите автоматическое распечатывание.

Consul

  consul:
    image: hashicorp/consul:latest
    healthcheck:
      test: ["CMD", "consul", "members"]
      interval: 10s
      timeout: 5s
      retries: 5

Универсальный приём: HTTP‑эндпоинт

Если в образе нет ни curl, ни wget, а команды‑специфика отсутствует — используйте встроенный в bash механизм /dev/tcp:

    healthcheck:
      test: ["CMD-SHELL", "bash -c 'cat < /dev/tcp/localhost/8080' || exit 1"]
      interval: 10s
      timeout: 3s
      retries: 5

Способ работает только при наличии bash (в alpine его нет — придётся поставить или использовать nc), но не требует никаких внешних утилит.

Чек‑лист и типичные ошибки

  • Не забывайте про start_period. Без него медленный сервис будет помечен как unhealthy и зависимые контейнеры так и не дождутся его готовности.
  • Проверяйте именно готовность, а не «процесс запущен». pgrep postgres вернёт 0 ещё до того, как Postgres начнёт принимать клиентов — это плохой healthcheck.
  • Используйте утилиты внутри контейнера, а не на хосте. Не нужно добавлять отдельный sidecar только ради проверки — почти у каждого образа есть свой CLI‑клиент.
  • Не зовите curl в alpine. Его там нет по умолчанию. wget есть и его достаточно для большинства проверок.
  • Экранируйте переменные двойным $$. Иначе compose попытается подставить значение из окружения хоста на этапе парсинга.
  • Не делайте healthcheck слишком тяжёлым. Запрос к БД с агрегацией внутри проверки нагрузит сервис ради формальности — простого ping почти всегда хватает.

Сохраните эту страницу в закладках: когда в очередной раз понадобится поднять незнакомый сервис в docker compose — рабочий healthcheck уже будет под рукой, и вам не придётся методом тыка подбирать команду между «процесс жив» и «сервис реально готов».