Docker healthcheck: настройка
Docker показывает статус контейнера как running, даже если приложение внутри уже зависло, упало в бесконечный цикл или перестало отвечать на запросы, — сам по себе контейнер этого не замечает и не сообщит о проблеме. docker ps продолжит рапортовать «всё хорошо», пока пользователи получают таймауты. HEALTHCHECK решает именно эту проблему: он запускает внутри контейнера проверку по расписанию и честно показывает, живо ли приложение, а не только жив ли процесс. Разберём, как настроить HEALTHCHECK в Dockerfile и docker-compose и связать его с автоматическим перезапуском.
Содержание
- Почему статус running не гарантирует работоспособность
- Синтаксис директивы HEALTHCHECK в Dockerfile
- Параметры interval, timeout, retries, start-period
- Практический пример: HTTP-эндпоинт и проверка процесса
- HEALTHCHECK в docker-compose
- Как посмотреть статус healthcheck
- Связка с restart policy для автоперезапуска
Почему статус running не гарантирует работоспособность
Docker считает контейнер живым, пока жив его главный процесс (PID 1). Это узкое определение: процесс может быть запущен, но зависнуть в дедлоке, исчерпать пул соединений к базе, застрять в бесконечном ожидании ответа от внешнего API — и при этом ничего не писать в логи и не завершаться. Для Docker это по-прежнему running, потому что процесс формально существует.
Типичный пример — Node.js или Java-приложение, у которого event loop заблокирован синхронной операцией, или веб-сервер, у которого исчерпаны воркеры и новые запросы просто копятся в очереди без ответа. Балансировщик или пользователь видит зависший сервис, а инфраструктура — «здоровый» контейнер. HEALTHCHECK закрывает этот разрыв: вместо вопроса «жив ли процесс» он задаёт вопрос «отвечает ли приложение корректно», выполняя реальную проверку — HTTP-запрос, TCP-соединение или скрипт — с заданной периодичностью.
Синтаксис директивы HEALTHCHECK в Dockerfile
Директива добавляется в Dockerfile и описывает, как и с какой частотой проверять контейнер:
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci --production
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
CMD ["node", "server.js"]
Ключевые моменты синтаксиса:
HEALTHCHECK [опции] CMD команда— сама проверка, её код возврата определяет статус.- Команда должна завершаться кодом
0(healthy),1(unhealthy) или ничем не выполняться дольшеtimeout(тогда попытка тоже считается неудачной). HEALTHCHECK NONEотключает healthcheck, унаследованный от базового образа — полезно, если базовый образ уже содержит свою проверку, которая вам не подходит.- В образе может быть только одна директива
HEALTHCHECK— если их несколько, действует последняя.
Проверка выполняется процессом внутри самого контейнера, поэтому в команде используется localhost, а не имя сервиса или внешний IP — с точки зрения healthcheck-команды вы обращаетесь к собственному процессу.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать VPSПараметры interval, timeout, retries, start-period
Четыре параметра управляют тем, как часто и как строго Docker проверяет контейнер:
| Параметр | По умолчанию | Что делает |
|---|---|---|
--interval | 30s | Пауза между проверками |
--timeout | 30s | Сколько ждать ответа от команды проверки, прежде чем засчитать неудачу |
--retries | 3 | Сколько подряд неудачных проверок нужно, чтобы статус стал unhealthy |
--start-period | 0s | Время после старта контейнера, в течение которого неудачные проверки не считаются в счётчик retries |
start-period — параметр, который чаще всего забывают, а зря. Приложению нужно время на инициализацию: прогрев соединения к базе, чтение конфигов, JIT-компиляцию. Без start-period первые несколько неудачных проверок сразу начнут копиться в счётчик retries, и контейнер может улететь в unhealthy ещё до того, как реально стартовал. Ставьте start-period чуть больше, чем реальное время холодного старта приложения — для тяжёлых Java-сервисов это может быть 30-60 секунд, для лёгкого HTTP-сервиса на Go — 5-10 секунд. Ориентируйтесь на своё приложение, эти цифры сильно зависят от стека и ресурсов сервера.
Логика проста: Docker ждёт interval секунд, выполняет проверку, ждёт от неё ответа не дольше timeout. Если проверка провалилась (ненулевой код или не уложилась в timeout) retries раз подряд, контейнер помечается unhealthy. Одна успешная проверка сбрасывает счётчик обратно в ноль.
Практический пример: HTTP-эндпоинт и проверка процесса
Самый надёжный вариант — свой health-эндпоинт, который реально проверяет работоспособность приложения, а не просто отвечает 200 OK на любой запрос:
HEALTHCHECK --interval=15s --timeout=3s --retries=3 \
CMD curl -sf http://localhost:8080/health || exit 1
Флаг -f у curl важен: без него curl вернёт код 0 даже на ответ 500, потому что сам HTTP-запрос прошёл успешно — а -f заставляет curl завершиться с ошибкой при статусах 4xx/5xx. Хороший /health-эндпоинт стоит делать «умным»: проверять не только то, что веб-сервер отвечает, но и доступность зависимостей — соединение с базой, доступность кэша. Но не переусердствуйте: если /health дергает пять внешних сервисов, любой сбой одного из них будет валить весь healthcheck без необходимости.
Если в образе нет curl (например, минималистичный Alpine или distroless), варианты:
# через wget, часто уже есть в alpine
HEALTHCHECK CMD wget -q --spider http://localhost:8080/health || exit 1
# без HTTP вообще — проверка, что процесс слушает порт
HEALTHCHECK CMD nc -z localhost 8080 || exit 1
# проверка процесса по имени, если HTTP-эндпоинта нет
HEALTHCHECK CMD pgrep -f "node server.js" || exit 1
Проверка через pgrep слабее HTTP-проверки — она подтверждает, что процесс запущен, но не что он отвечает на запросы. Используйте её только когда у приложения в принципе нет сетевого интерфейса для проверки (воркеры очередей, cron-подобные процессы).
HEALTHCHECK в docker-compose
В docker-compose.yml та же логика описывается ключом healthcheck на уровне сервиса — это удобно, когда вы не хотите пересобирать образ ради изменения параметров проверки:
services:
web:
image: myapp:latest
ports:
- "8080:8080"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 15s
timeout: 3s
retries: 3
start_period: 10s
restart: unless-stopped
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
Формат test бывает двух видов: ["CMD", "команда", "аргумент1", ...] выполняет команду напрямую без обращения к shell, а ["CMD-SHELL", "команда"] запускает её через /bin/sh -c, что нужно, если используете пайпы, переменные окружения или составные условия. Значения в compose переопределяют то, что задано в Dockerfile образа, — это удобно для тестового окружения, где вы хотите более частые проверки, чем в проде.
Отдельный практический плюс healthcheck в compose — директива depends_on с условием:
services:
web:
depends_on:
db:
condition: service_healthy
Так web стартует не просто после запуска контейнера db, а только после того, как db реально пройдёт healthcheck и станет готов принимать соединения. Это надёжнее, чем depends_on без условий, который просто дожидается, пока контейнер запустится — база может быть ещё не готова принимать подключения к моменту старта зависимого сервиса.
Как посмотреть статус healthcheck
Статус здоровья виден прямо в выводе docker ps — в колонке STATUS появляется пометка (healthy), (unhealthy) или (health: starting) в период start-period:
docker ps
CONTAINER ID IMAGE STATUS PORTS
a1b2c3d4e5f6 myapp Up 2 minutes (healthy) 0.0.0.0:8080->8080/tcp
Для детальной картины — история последних проверок, их вывод и коды возврата — используйте docker inspect:
docker inspect --format='{{json .State.Health}}' имя_контейнера | jq
Вывод покажет текущий статус (Status), число неудач подряд (FailingStreak) и массив Log с последними попытками — временем запуска, кодом выхода и выводом команды проверки. Это первое место, куда стоит смотреть, если контейнер помечен unhealthy, а логи приложения ничего подозрительного не показывают: часто причина как раз в самой команде проверки — неверный порт, эндпоинт ещё не готов, curl не установлен в образе.
Если нужен только статус без лишнего:
docker inspect --format='{{.State.Health.Status}}' имя_контейнера
Связка с restart policy для автоперезапуска
Сам по себе HEALTHCHECK ничего не перезапускает — статус unhealthy показывается в docker ps, но Docker не убивает и не перезапускает контейнер только на основании него. Автоматический перезапуск зависшего контейнера строится через связку с restart policy, но не напрямую, а через прокладку: контейнеры с политикой restart: always или unless-stopped перезапускаются, только когда сам процесс завершается — то есть healthcheck должен либо привести к падению процесса, либо вы добавляете внешний наблюдатель.
Практический вариант — контейнер сам завершает свой главный процесс, если понимает, что завис (watchdog-логика внутри приложения), и restart policy его поднимает:
services:
web:
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 15s
timeout: 3s
retries: 3
Второй вариант — внешний наблюдатель, который следит за статусом unhealthy и перезапускает контейнер сам. Простейший вариант — легковесный проект docker-autoheal: он слушает события Docker, и при переходе контейнера в unhealthy выполняет docker restart. Поднимается как ещё один контейнер с доступом к /var/run/docker.sock:
services:
autoheal:
image: willfarrell/autoheal
restart: always
environment:
- AUTOHEAL_CONTAINER_LABEL=all
volumes:
- /var/run/docker.sock:/var/run/docker.sock
Третий вариант, если вы уже используете оркестрацию поверх обычного Docker — например, Docker Swarm, где healthcheck встроен глубже: Swarm сам подменяет unhealthy-задачи новыми репликами без сторонних инструментов. Для одиночного docker-compose на одном сервере связка healthcheck + docker-autoheal — самый простой рабочий вариант без лишней инфраструктуры.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать VPSНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Healthcheck замедляет работу контейнера?
Нет, если проверка лёгкая (простой HTTP-запрос к своему же процессу занимает миллисекунды). Не делайте проверку тяжёлой — не гоняйте в ней полные интеграционные тесты, это лишняя нагрузка на каждый интервал.
Можно ли использовать healthcheck без HTTP-эндпоинта?
Да, подойдёт проверка через nc -z на нужный порт или pgrep по имени процесса — это грубее, но лучше, чем ничего, для сервисов без веб-интерфейса.
Почему контейнер сразу помечается unhealthy после запуска?
Скорее всего не задан или занижен start-period — приложению не хватает времени на инициализацию, и первые проверки проваливаются ещё до полного старта.
Чем HEALTHCHECK отличается от простого restart: always?
restart: always перезапускает контейнер, только когда падает его главный процесс. HEALTHCHECK ловит случай, когда процесс жив, но не отвечает, — это разные сценарии сбоя, и для полного покрытия нужны оба механизма вместе.
Нужен ли healthcheck для баз данных вроде Postgres или Redis?
Да, особенно если от их готовности зависят другие сервисы через depends_on: condition: service_healthy — это защищает от гонки при старте, когда приложение подключается раньше, чем база готова принимать соединения.