# BRD: Healthcheck enduro-trails-app падает: в контейнере нет curl

**Work Item:** ET-015
**Тип:** Bugfix / Infrastructure
**Приоритет:** Низкий (приложение работает) / Важно для мониторинга
**Дата:** 2026-06-05
**Запросил:** Слава

## 1. Контекст

Контейнер `enduro-trails-app-1` (запускается из репозитория `enduro-trails`)
на тестовой среде `mva154` (https://openclaw.mva154.duckdns.org/enduro/)
показывает в Docker статус `unhealthy` уже ~31 час с `FailingStreak=3762`,
при том что само приложение работает:

- `curl снаружи :5556 → HTTP 200` (~7 мс отклик);
- в логах живой трафик `200 OK`;
- `RestartCount=0` (контейнер не перезапускался).

## 2. Корень проблемы

В `docker-compose.yml` healthcheck настроен как:

```yaml
healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:5556/api/health"]
```

Базовый образ — `python:3.12-slim` (см. `Dockerfile`). В `slim`-варианте
**нет** утилиты `curl`. Каждый цикл healthcheck завершается:

```
exec: "curl": executable file not found in $PATH
exit code = -1
```

Docker трактует это как «проверка провалена» и через `retries=3` помечает
контейнер `unhealthy`. На самом деле приложение здорово.

Дополнительный факт: эндпоинт `/api/health` **существует** в коде
(`src/api/main.py:1224`, отдаёт `{"status": "ok", ...}`), так что
двойной поломки (несуществующий путь) нет — проблема исключительно
в отсутствии `curl`.

## 3. Бизнес-проблема

1. **Ложные алерты в мониторинге.** Любая система оповещений, опирающаяся
   на `docker inspect ... .State.Health.Status`, будет постоянно кричать
   об инциденте, который не существует.
2. **Эрозия доверия к мониторингу.** Если `unhealthy` всегда ложный, его
   игнорируют — и пропустят настоящий инцидент, когда он случится.
3. **Невозможность построения SLO/SLA.** Метрика «доступность контейнера»
   деградирована и непригодна для отчётности.

## 4. Цель

Healthcheck контейнера `app` должен **честно** отражать состояние
приложения: `healthy`, когда HTTP-эндпоинт `/api/health` на `:5556`
отвечает `200 OK`; `unhealthy`, когда не отвечает.

## 5. Стейкхолдеры

| Роль | Имя / Группа | Интерес |
|------|--------------|---------|
| Заказчик | Слава | Корректный мониторинг тестовой и будущей prod-среды |
| Исполнитель | claude-bot | Реализация фикса |
| Эксплуатация | mva154 host owner | Минимальный размер образа, никаких лишних пакетов |

## 6. Ограничения и нефункциональные требования

- **Размер образа** не должен заметно расти. Добавление `curl` через
  `apt-get install` тянет ~10 МБ зависимостей + слой APT-кэша →
  нежелательно. Предпочтительно использовать то, что уже есть в образе
  (Python).
- **Время выполнения healthcheck** не должно превышать `timeout: 5s`
  (текущее значение в compose). Реальное время отклика `/api/health`
  ~7 мс, запас огромный.
- **Совместимость** с Docker Engine ≥ 20.10 (на mva154 стоит свежий).
- **Никаких изменений** в логике приложения — эндпоинт `/api/health`
  уже существует и его поведение менять не нужно.

## 7. Out of scope

- Доработка содержимого `/api/health` (например, добавление проверки
  OSRM, тайлов, диска) — отдельный work item, если понадобится.
- Healthcheck для сервиса `gps-collector` (batch profile) — у него
  нет открытого порта и `restart: "no"`, healthcheck неуместен.
- Healthcheck-настройки на стороне Gitea Actions / CI.

## 8. Сценарий «как должно стать»

1. Образ собирается без добавления `curl`.
2. `docker compose up -d app` поднимает контейнер.
3. Через ≤ `interval * retries` (= 30s × 3 = 90s, с учётом
   `start_period` если задан) `docker inspect ... .State.Health.Status`
   возвращает `healthy`.
4. Если приложение «зависает» (порт не отвечает) — healthcheck
   честно фиксирует `unhealthy` за то же окно.

## 9. Связи

- Затрагивает: `Dockerfile`, `docker-compose.yml`.
- Не затрагивает: `src/api/`, `src/web/`, БД, тайлы.
- Соседние ADR: глобальных архитектурных решений не требует —
  локальное инженерное решение, оформляется в `06-adr/` work-item.
