# 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 зомби-процессов (``: `[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/с — отдельная утечка, не блокирующая данный фикс.