# ADR-059: Increase opencode container limits, add tini reaper and healthcheck

## Статус
Accepted (2026-07-29)

## Контекст
`opencode serve` (веб-UI на `:4096`, проксирован через NPM как `opencode.slaid098.dev`) зависал при активной работе агента — `504 Gateway Time-out`, контейнер не отвечал по IP. Живая диагностика cgroup v2 внутри контейнера:
- `memory.peak` = 5.72 GB из лимита 6 GB (95%), `oom_kill` = 0 → ядро не убивает, а делает direct reclaim (синхронный scan/free страниц в event loop процесса) → Node.js event loop блокируется → serve зависает.
- CPU: `nr_throttled` = 879, `throttled_usec` = 104.7 с — упирался в лимит 3 ядра (`cpu.max=300000/100000`); `cpu.pressure avg300` = 0.11 (11% устойчивый).
- >120 зомби-процессов (`<defunct>`: `[git]`, `[gh]`, `[node]`); opencode — PID 1, не вызывает `wait()` для reaping детей.
- Swap на хосте linux-1 отключён (`Swap: 0B`) — нет буфера при пиках.

Все корневые причины устранимы изменениями в `docker-compose.yml` (область репо). Сервис `dind` использовал 32 MB из 4 GB (0.78%), без троттлинга — не трогаем.

## Решение
Три изменения в сервисе `opencode` (dind — без изменений):

1. **Увеличить лимиты ресурсов** (`deploy.resources.limits`):
   - `memory`: 6G → **8G** (+2.3 GB над наблюдённым пиком 5.72 GB — устраняет direct-reclaim блокировку event loop).
   - `cpus`: '3' → **'4'** (убирает 879 троттлинг-событий; на хосте 6 ядер, запас есть).
   - `pids`: 1024 → **2048** (буфер для зомби; основной фикс — `init: true`, это страховка).

2. **`init: true`** на верхнем уровне сервиса: Docker подставит **tini** как PID 1, который авто-reap'ит осиротевших зомби (процессы, чей родитель умер — переподчиняются init). Прямых детей живого opencode tini не заберёт, но основную массу зомби уберёт. Best practice для контейнеров, порождающих подпроцессы.

3. **`healthcheck`** (после `deploy`): `CMD-SHELL` с `curl` к `localhost:4096`, допускает 200|401 как healthy (`serve` требует basic auth → 401 без credentials). `interval: 30s`, `timeout: 10s`, `retries: 3`, `start_period: 30s`. Независание (HTTP не отвечает) → Docker детектит unhealthy и авто-рестартует через уже существующий `restart: unless-stopped` (НЕ дублировать). Используем `CMD-SHELL` (не `CMD` exec-form) — нужен pipe в grep; `$$` экранирует `$` для shell.

## Альтернативы
- **Swap на хосте** — рассмотрена, но вне репо (ops-задача на linux-1). Даёт буфер при пиках, но не решает direct-reclaim при 95% утилизации и не убирает зомби/троттлинг.
- **cron-рестарт контейнера** — вне репо, реактивный workaround, не детектит зависание (только по расписанию). Healthcheck proactive.
- **Откат версии opencode** — отвергнута: диагностика показала resource-проблемы (память/CPU/зомби), а не регрессию версии. Живой рост RSS ~11 MB/с — отдельная утечка, не блокирующая данный фикс.