Если контейнер «поднялся», это ещё не значит, что сервис внутри него готов принимать запросы. 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 уже будет под рукой, и вам не придётся методом тыка подбирать команду между «процесс жив» и «сервис реально готов».