概要
devbase で起動される Docker コンテナの PID 1 が tail -f /dev/null であり、orphan プロセスの reap を行わないため、ゾンビプロセスが蓄積する。
Docker Compose の init: true を設定することで、tini が PID 1 として挿入され、ゾンビが自動 reap される。
再現手順
# devbase コンテナ内で
ps -p 1 -o comm=
# → tail (proper init ではない)
# バックグラウンドプロセスを起動して終了させる
nohup sleep 1 &
disown
sleep 3
# ゾンビが残る
ps aux | grep 'Z.*defunct'
# → [sleep] <defunct>
影響
ndf:cross-review skill は codex/gemini CLI を nohup ... & disown で起動し、monitor.py で監視する。レビュー完了後にプロセスがゾンビ化し:
- 監視の誤動作:
kill -0 がゾンビに対しても成功するため、monitor.py が「まだ実行中」と判定 → hard timeout (420s) まで待たされる
- ゾンビ蓄積: PID 1 が reap しないため、コンテナ再起動まで蓄積し続ける
関連 issue: devbasex/ai-plugins#21
根本原因
| 通常の Linux |
Docker (現状) |
| PID 1 = systemd → SIGCHLD で自動 reap |
PID 1 = tail -f /dev/null → reap しない |
提案: generate_scaled_compose() に init: true を注入
lib/devbase/volume/compose.py の generate_scaled_compose() 内、dev サービスの複製ループで init: true を設定する:
for i in range(1, scale + 1):
service = _deep_copy(dev_service)
service['container_name'] = f"${COMPOSE_PROJECT_NAME}-{dev_service_name}-{i}"
# ゾンビ防止: tini を PID 1 として挿入
service.setdefault('init', True)
# ... 既存処理
この方法の利点
- 変更箇所が 1 行: 全プロジェクトに自動適用
- 既存の
init: false 指定を尊重: setdefault なので明示指定があれば上書きしない
- アプリケーションコード変更不要: entrypoint.sh との互換性あり
- Docker 公式推奨: tini は 20KB の軽量 init で、シグナル転送 + ゾンビ reap を行う
non-dev サービスへの適用
mysql, valkey 等の non-dev サービスにも同様に適用すると安全:
for service_name, service_config in services.items():
if service_name != dev_service_name:
copied = _deep_copy(service_config)
copied.setdefault('init', True)
# ...
検証方法
# 修正後
devbase up
devbase login
# PID 1 が tini になっていることを確認
ps -p 1 -o comm=
# → tini
# ゾンビが reap されることを確認
nohup sleep 1 &
disown
sleep 3
ps aux | grep 'Z.*defunct'
# → (なし)
環境
- devbase: latest (2026-05-27 時点)
- 確認コンテナ: ai-plugins 開発環境
PR_SET_CHILD_SUBREAPER は動作するが、Docker init: true の方がシンプルで確実
概要
devbase で起動される Docker コンテナの PID 1 が
tail -f /dev/nullであり、orphan プロセスの reap を行わないため、ゾンビプロセスが蓄積する。Docker Compose の
init: trueを設定することで、tini が PID 1 として挿入され、ゾンビが自動 reap される。再現手順
影響
ndf:cross-reviewskill は codex/gemini CLI をnohup ... & disownで起動し、monitor.pyで監視する。レビュー完了後にプロセスがゾンビ化し:kill -0がゾンビに対しても成功するため、monitor.pyが「まだ実行中」と判定 → hard timeout (420s) まで待たされる関連 issue: devbasex/ai-plugins#21
根本原因
tail -f /dev/null→ reap しない提案:
generate_scaled_compose()にinit: trueを注入lib/devbase/volume/compose.pyのgenerate_scaled_compose()内、dev サービスの複製ループでinit: trueを設定する:この方法の利点
init: false指定を尊重:setdefaultなので明示指定があれば上書きしないnon-dev サービスへの適用
mysql, valkey 等の non-dev サービスにも同様に適用すると安全:
検証方法
環境
PR_SET_CHILD_SUBREAPERは動作するが、Dockerinit: trueの方がシンプルで確実