
デバッグ中に手が止まる瞬間って、だいたい同じ状況ですよね。
ポートが競合していて docker compose up が失敗する。
VPS にログインしたら見覚えのないプロセスが動いている。
ps aux を叩いてもプロセス名と PID が並ぶだけで、「なぜこれが動いているのか」という問いには答えてくれない。
そういう瞬間に使えるツール witr(Why Is This Running?)を、Docker Compose 複数サービス環境と VPS 本番環境という2つの文脈で実際に動かしてみました。
lsof / ps / netstat との使い分けも含めて整理したので、「自分の現場で使えるか」を判断する材料にしてもらえれば十分です。
ps が「何」を見せ、witr が「なぜ」を見せる
ps aux は今この瞬間に動いているプロセスのスナップショットを返します。
lsof -i :8080 はそのポートを開いているファイルディスクリプタを教えてくれます。
netstat -tlnp はリッスンしているポートと PID の対応を出します。
どれも「何が動いているか」を見せるツールです。
問題は、それが分かっても「なぜ動いているか」が分からないことが多い点です。
たとえば PID 3847 の nginx が 8080 番を握っていると分かったとして、それを起動したのは systemd なのか、Docker ランタイムなのか、誰かの SSH セッションで手動起動されたのか——そこまで追うには、さらに pstree・systemctl status・docker inspect を組み合わせる必要があります。
疲弊しているときに、このコマンドリレーが地味につらい。
witr の設計思想は、その「因果の連鎖」を一発で返すことです。
ツール名の Why Is This Running? がそのまま仕様になっていて、プロセス・ポート・コンテナ・ファイルを起点に、systemd ユニット・cron ジョブ・SSH セッション・コンテナランタイムまで「責任の連鎖」を ancestry chain として表示します。
Go 製の静的バイナリなので、依存関係のインストールは不要です。
設定ファイルもなく、読み取り専用で動作するため、本番環境に入れても何かを壊す心配がありません。
Hacker News の Show HN に投稿されたとき 526 points を集めました。
「自分だけが困っていたわけじゃなかった」という共感が、あの点数に集まったんだと思います。
インストールと最初の一手
インストールは好みの方法で選べます。
- Linux / macOS: curl ワンライナー
- macOS: Homebrew(brew install witr)
- Arch Linux: AUR(yay -S witr)
- Windows: winget / Chocolatey / Scoop
- その他: npm、Conda、FreeBSD ports
# Linux / macOS ワンライナー
curl -fsSL https://raw.githubusercontent.com/pranshuparmar/witr/main/install.sh | bash
# macOS (Homebrew)
brew install witr
# Arch Linux (AUR)
yay -S witr
静的バイナリなので、curl でバイナリを直接落としてパスに置くだけでも動きます。
VPS に入れるときはこの方法が一番早いです。
インストールできたら、まず引数なしで叩いてみてください。
witr
フルスクリーンの TUI が起動します。
この TUI は v0.3.0 で追加された機能なので、古いバイナリが入っている場合は最新版に更新してください。
タブは Processes・Ports・Containers・Locks の4つ。
矢印キーで項目を移動すると、右側のサイドパネルにその項目の ancestry chain が表示されます。
a キーで表示フィルターを切り替え(Ports タブでは LISTEN のみ ↔ ALL)、/ で絞り込み検索ができます。
CLI ワンライナーモードも充実しています。
# ポート番号から追跡
witr -p 8080
# PID を直接指定
witr 12345
# コンテナ名から追跡
witr -c my-container
# ファイルロックを追跡
witr -f /var/run/app.lock
# JSON 出力(スクリプト組み込み用)
witr -p 3000 --json
使い分けのイメージとしては、「TUI で全体を俯瞰して怪しいものを探す」→「CLI で特定の問いに即答する」という流れが自然です。
調査の入口は TUI、答えを確定させるのは CLI ワンライナー、という感じです。
Docker Compose 環境で「このポートを握っているのは誰か」を追う
nginx + app + db の3サービスが動く Compose 環境で、ホスト側の 8080 番が予期せず占有されているシナリオを考えます。
docker compose up が「port is already allocated」で落ちる、あのやつです。
従来の手順はこんな感じでした。
# ステップ1: ポートを握っているプロセスを探す
lsof -i :8080
# ステップ2: それが Docker コンテナか確認
docker ps
# ステップ3: コンテナの詳細を確認
docker inspect <container_id>
# ステップ4: Compose プロジェクトとの対応を確認
docker inspect <container_id> | grep -i compose
witr ならこれが1コマンドです。
witr -p 8080
実際の出力フォーマットは環境によって異なりますが、概念的にはこんな ancestry chain が返ってきます(イメージ)。
Port 8080 → PID 9821 (nginx)
└─ Container: myapp_nginx_1
Project: myapp
Image: nginx:1.25
Network: myapp_default
Mounts: ./nginx.conf → /etc/nginx/nginx.conf
└─ Started by: dockerd (PID 1043)
└─ systemd: docker.service
「nginx プロセスがいる」「それは myapp プロジェクトの nginx コンテナの中にいる」「そのコンテナは dockerd が起動していて、dockerd は systemd の docker.service として管理されている」——この連鎖が一画面で読めるイメージです。
実際の表示は witr のバージョンや環境によって変わりますが、ancestry chain として因果を辿れる点は共通しています。
TUI の Containers タブを使うと、さらに Compose プロジェクトのメタデータ(プロジェクト名・マウントパス・ネットワーク構成)がサイドパネルに展開されます。
lsof -i :8080 だけでは「PID と FD の情報」しか返ってこないので、Compose プロジェクト名まで辿り着くには結局 docker inspect が必要でした。
witr の Containers タブはその情報レイヤーをはじめから持っています。
このシナリオで体感できる witr の核心は、「起点を変えると見え方が変わる」という点です。
ポートを起点にすれば「誰がこのポートを握っているか」が分かり、コンテナを起点にすれば「このコンテナが何をしているか」が分かり、ファイルを起点にすれば「誰がこのファイルをロックしているか」が分かる。
同じ ancestry chain の仕組みが、起点の種類に応じて違う切り口で答えを返してくれます。
VPS 本番環境で見覚えのないプロセスを追跡する
「ログインしたら知らないポートが開いていた」というのは、VPS を運用していると一度は経験する状況です。
セキュリティインシデントの可能性もゼロではないですが、たいていは自分でインストールして忘れたデーモンか、パッケージのアップデートで自動起動が有効になったサービスだったりします。
どちらにせよ、まず「何者か」を特定しないと次が動けません。
こういうときは TUI の Ports タブが便利です(v0.3.0 以降)。
witr を引数なしで起動して Ports タブに移動すると、現在リッスンしているポートが一覧で並びます。
見覚えのないポートにカーソルを合わせると、右サイドパネルに ancestry chain が展開されます。
怪しい PID が特定できたら、CLI モードで深掘りします。
witr 4219
実際の出力フォーマットは環境依存ですが、概念的にはこういった情報が返ってきます(イメージ)。
PID 4219 (python3)
└─ PPID 4198 (bash)
└─ PPID 4195 (cron)
└─ systemd: cron.service
User: deploy
Started: 2025-07-01 03:00:01
CWD: /home/deploy/scripts
Cmd: python3 /home/deploy/scripts/sync_data.py
「これ、cron から呼ばれてたのか」という発見の瞬間です。
ps aux で PID 4219 を見ていたときは python3 としか表示されていなかったのに、witr で追うと「deploy ユーザーの cron が毎朝3時に sync_data.py を呼んでいた」という全体像が一発で分かる——そういう体験を witr はしやすくしてくれます。
もう一つ、Locks タブも地味に役立ちます。
/var/run/*.pid や /tmp 以下のファイルロックが一覧で確認できるので、「プロセスが終了したのにロックファイルが残っていてサービスが再起動できない」という状況の診断に使えます。
a キーで「すべての開いている fd」モードに切り替えると、ロックだけでなく開いているファイルディスクリプタ全体が見えます。
TUI の4タブ——Processes・Ports・Containers・Locks——は、それぞれ独立した調査の入口として機能します。
「何かおかしい」と感じたとき、どのタブから入っても ancestry chain に辿り着けるのが、調査の地図として使いやすい理由です。
witr で置き換えられるもの・できないもの
witr を使ってみて正直に感じた差分を整理します。
witr が置き換えやすい操作
- lsof -i :PORT でポートの所有プロセスを調べる
- pstree -p で親プロセスを遡る
- systemctl status でサービスの起動元を確認する
- docker inspect + docker ps の組み合わせでコンテナの起動元を追う
既存ツールに任せるべき操作
- プロセスが開いているファイルディスクリプタの 全量リスト(lsof -p の詳細出力)
- TCP/UDP の統計情報・接続数の集計(netstat -s や ss -s)
- ps のカスタムフォーマット出力(--format で列を自由に組む)
- awk や grep と組み合わせた複雑なフィルタリング
witr が得意なのは「因果の遡り」であって、「詳細な一覧の取得」ではありません。
「なぜこれが動いているか」という問いへの答えは witr が最速ですが、「このプロセスが開いているファイルをすべて列挙したい」という問いには lsof の方が向いています。
「witr を入れたら lsof を捨てる」ではなく、 調査の入口を witr に統一して、深掘りは既存ツールに委ねる という使い方が現実的です。
トラブルシュートの最初の一手として witr を叩き、ancestry chain で全体像を掴んでから、必要に応じて lsof や netstat に渡す。
そのフローが手に馴染むと、コマンドリレーの回数がかなり減ります。
なお、現時点では v0.3.x と比較的若いツールです。
一部の環境や珍しいコンテナランタイムでの挙動差がある可能性は否定できないので、本番投入前に手元の環境で動作確認しておくのが無難です。
発展的な使い方として、--json オプションと jq を組み合わせると監視スクリプトへの組み込みもできます。
特定のポートを定期的にチェックして、予期しないプロセスが握っていたらアラートを上げる、といった使い方です。
exit code も構造化されているので、シェルスクリプトとの相性は良いです。
株式会社ホコサキは、山口県宇部を拠点に Web 制作・業務システム開発・AI 活用支援・DX 推進を手がけています。
「現場で実際に使えるか」という視点を大切にしながら、こうした実務ツールの検証・導入支援も行っています。
ご相談は お問い合わせページ からどうぞ。

