Начиная работать с Podman, я задал себе вопрос — функционирует ли healthcheck в Podman так же, как в Docker? Да и нет. Давай разбираться.
Статью написал Роман Шубин, CTO и автор Telegram-канала Bash Days.
Различия между Podman и Docker
Ключевая разница между Podman и Docker кроется не в самих «хелфчеках», а в архитектуре. Docker использует централизированный демон, который управляет контейнерами и следит за их состоянием.
Работает это так. В файле Dockerfile/Compose задается проверка:
HEALTHCHECK CMD curl --fail http://localhost:8080 || exit 1
Демон Docker сам запускает проверки, хранит статус healthy/unhealthy, может перезапускать контейнер в связке с restart policy. А в Podman все происходит чуть менее магически. Да, в нем есть такие же healthcheck, как и в Docker, но нет демона, который будет все это крутить. Все проверки происходят на уровне systemd или podman healthcheck run.
Podman не будет автоматически проверять контейнер каждые N секунд, если вы это явно не настроете. Это выглядит уже более гибко и по-современному.
И получаем в итоге такую картину:
- Docker → «убери руки, я сам все проверю и прослежу».
- Podman → «я дам тебе инструменты, а ты решай, как их использовать».
Хорошо, с этим разобрались, стало намного яснее. Едем дальше и переходим к практическим действиям.
Создаем и запускаем контейнер с healthcheck и разбором ошибок
Расчехляем лабораторию с развернутым Podman. Создаем файл app.py:
from flask import Flask, jsonify
import os
app = Flask(__name__)
@app.route("/")
def index():
return "Hello from Podman!"
@app.route("/health")
def health():
if os.getenv("FAIL", "false") == "true":
return jsonify({"status": "unhealthy"}), 500
return jsonify({"status": "ok"}), 200
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
Готовим Dockerfile:
FROM python:3.11-slim
WORKDIR /app
COPY app.py .
RUN pip install flask
EXPOSE 8080
HEALTHCHECK --interval=10s --timeout=3s --retries=3 CMD curl --fail http://localhost:8080/health || exit 1
CMD ["python", "app.py"]
Собираем и запускаем:
podman build -t healthcheck-demo
Сборка образа прошла успешно, но мы получаем сообщение:
HEALTHCHECK is not supported for OCI image format and will be ignored Must use docker format
Всплыло очень важное отличие Podman от Docker. По умолчанию Podman собирает образы в формате OCI, а в нем healthcheck не поддерживается.
Да, тут важно знать, что есть два формата образов:
- OCI (Open Container Initiative) — стандарт, который использует Podman.
- Docker format — старый формат Docker.
Поэтому указываем дополнительный ключ при сборке:
podman build --format docker -t healthcheck-demo

Все супер: собралось, ошибок и предупреждений не вылезло. Но это старый формат образа. И нам такое не совсем подходит, у нас OCI и Podman. Давайте ковырять.
Удаляем строчку с HEALTHCHECK из Dockerfile и пересобираем образ без ключа --format. Запускаем контейнер:
podman run -d -p 8080:8080 --name health-demo healthcheck-demo
Смотрим:

Хм, видим, колонка STATUS есть, но healthcheck не видно. Останавливаем контейнер:
podman stop d384c0c67528
И запускаем в таком формате:
podman run -d \
--health-cmd="curl --fail http://localhost:8080/health || exit 1" \
--health-interval=10s \
--health-timeout=3s \
--health-retries=3 \
--name demo \
healthcheck-demo

Отлично, в STATUS появился unhealthy. Это уже что-то! Давайте отдебажим, почему контейнер не поднял. Запускаем:
podman inspect demo
И видим:

Вот и причина — в собранном образе отсутствует утилита curl. Исправим это, добавив в Dockerfile строчку:
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
Пересобираем OCI образ и запускаем:
podman run -d \
--replace \
-p 8080:8080 \
--health-cmd="curl --fail http://localhost:8080/health || exit 1" \
--health-interval=10s \
--health-timeout=3s \
--health-retries=3 \
--name demo \
healthcheck-demo
Я добавил в команду ключ --replace, теперь Podman сам удалит старый контейнер и создаст новый. Если ключ не указать, то получим такую ошибку:
You have to remove that container to be able to reuse that name: that name is already in use, or use –replace to instruct Podman to do so.
Запустилось, смотрим:

Класс, контейнер жив, STATUS = healthy. Смотрим podman inspect demo:


Ожидаемый результат получен, curl успешно отрабатывает. На хостовой машине выполняем еще одну проверку:

Тоже все в порядке, мы видим {“status”:”ok”}.
Ключи запуска
Теперь давайте разберем ключи запуска:
health-interval=10s— каждые 10 секунд выполняется health-cmd.health-timeout=3s— если curl не успел выполниться за три секунды → ошибка.health-retries=3— три неудачных проверки подряд → контейнер становится unhealthy (именно подряд, а не суммарно).
Dev
Для dev-окружений обычно выставляют:
--health-interval=5s,--health-retries=1.
Сразу и быстро всплывают проблемы, не нужно ничего ждать.
Prod
Для production-окружений:
--health-interval=30s,--health-retries=3-5.
Получаем устойчивость к кратким лагам.
Вообще эти параметры докручиваются индивидуально под потребности проекта, у каждого будет своя «золотая середина». В большинстве случаев на небольших пет-проектах healthcheck вообще не используют, а зря.
Systemd
Итого получается, что Podman healthcheck — это не часть образа, а часть конфигурации контейнера. Чтобы Podman вел себя как «production-ready», обычно используют systemd.
Генерируем unit:
podman generate systemd --name demo --files --new
И получаем предупреждение: DEPRECATED command: It is recommended to use Quadlets for running containers and pods under systemd.
Ладно, как скажете, будем использовать Quadlets.
Quadlet
Quadlet — это способ описать контейнер сразу как systemd unit. Удаляем результат прошлой команды:
rm container-demo.service && systemctl --user daemon-reload
Создаем Quadlets-файл:
mkdir -p ~/.config/containers/systemd
Создаем файл: /etc/containers/systemd/demo.container с содержимым:
[Unit]
Description=Demo container with healthcheck
[Container]
Image=localhost/healthcheck-demo:latest
PublishPort=8080:8080
HealthCmd=curl --fail http://localhost:8080/health || exit 1
HealthInterval=10s
HealthTimeout=3s
HealthRetries=3
[Install]
WantedBy=multi-user.target
Перезагружаем systemd:
systemctl daemon-reexec
systemctl daemon-reload
Запускаем контейнер через systemd:
systemctl start demo.service
systemctl status demo.service

Отлично! На красное внимание не обращайте, это «варнинги». Сообщается, что мы запустили контейнер в developer-режиме, классическое предупреждение от Flask.
Смотрим, что говорит Podman:

Ага, мы только что подняли контейнер с помощью systemd, а healthcheck у нас явно прописан в unit-файле.
Quadlet-сервисы являются сгенерированными unit’ами, поэтому их нельзя напрямую включить через systemctl enable.
Как это работает
systemdстартует,- quadlet-generator читает
/etc/containers/systemd/, - генерирует demo.service,
- смотрит [Install] →
WantedBy=multi-user.target, - автоматически включает unit.
Перезагружаем машину:

Видим, что unit автоматически стартанул наш контейнер. Даже без команды systemctl enable demo.service.
Эта тема довольно неочевидная, но очень интересная. О ней мало подробностей, потому что обычно все довольствуются подходом «Работает, не трогаем. То, что под капотом — это уже не наша забота». Но мне всегда было интересно, как оно устроено внутри, поэтому и вы теперь это знаете.
Насчет compose-файлов сильно погружаться не буду, но скажу так: главное отличие от юнитов в следующем:
docker-compose up→ отдельный процесс,systemd→ управляет контейнером как сервисом.
Почему systemd — это круто? Потому что там есть автозапуск на более низком уровне, логи через journalctl, restart policy, зависимости After / Requires, единый контроль сервисов. В общем, Quadlet — это декларативный способ описания контейнеров для systemd, который частично заменяет Docker-compose в systemd-ориентированных окружениях.
Вернемся к healthcheck
Ключевая идея — healthcheck выполняется внутри контейнера, а не снаружи.

Команда запускается как отдельный процесс, внутри того же неймспейса с тем же filesystem. Healthcheck проверяет внутреннее состояние контейнера, а не доступность сервиса извне.
У healthcheck есть три состояния (STATUS):
- starting,
- healthy,
- unhealthy.
Тут все очевидно: стартуем, живем, упало.
Важно! Healthcheck ≠ restart, Podman не перезапускает контейнер сам, этим он отличается от того же Kubernetes. Чтобы добавить перезапуск, можно подшаманить unit-файл и добавить Restart=always.
Вывод
А для чего вообще нужен healthcheck? Если кратко — чтобы понять, реально ли приложение работает или просто имеется статус «контейнер запущен». Только не тешьте себя напрасными надеждами, что healthcheck добавляет какую-то «магическую» самовосстановляемость (или самовосстанавливаемость?). Нет, это просто честная и прозрачная проверка состояния контейнера. Инструмент подскажет, если что-то пойдет не так, но исправлять ситуацию за вас не станет.
В связке с systemd или Quadlet так вообще красота получается. Можно построить полноценную, управляемую и предсказуемую систему. Без лишней магии, но с полным контролем.