🔀

git worktree 並列開発でのポート競合を解消するCLI、PortMuxを作りました

に公開

はじめに

Coding Agent でタスクを並列に進めることが一般的になりつつある昨今、git worktree でブランチごとに環境を分け、ローカルで並行開発する場面が増えています。このような開発フローでは、各 worktree で開発サーバーを立ち上げて行き来しながら動作確認する必要があります。

とはいえ、ローカルサーバーの切り替えは手間がかかります。1 つのプロダクトで複数のサーバーを立てていると、毎回 stop / start をやり直したり、ポート競合に引っかかったりしがちです。

この記事では、git worktree 運用時のポート競合と切り替えの手間を減らすために開発した CLI ツール「PortMux」を紹介します。

https://github.com/YTakahashii/portmux

PortMux とは

PortMux は、バックグラウンドプロセスを管理するための CLI ツールです。git worktree を多用する開発スタイルで頻発するポート競合問題の解決を目的として設計しました。

pm2systemd のような本番環境向けのサービス管理ツールとは異なり、ローカル開発を快適にすることに焦点を当てています。

PortMux は、プロセスの状態を git worktree 単位で束ねて扱えるのが特徴です。portmux select コマンドひとつで worktree を横断し、開発サーバーをシームレスに切り替えることができます。

PortMux Concept

主な機能

1. git worktree を意識したプロセス管理

PortMux はプロセスの状態(PID や使用ポートなど)を worktree ごとに管理します。portmux select で別の worktree を選ぶと、現在の worktree のプロセスを止め、選んだ worktree で起動し直すところまで自動でやってくれます。

2. グループ単位でのプロセス管理

フロントエンド、バックエンド API、DB など、複数のプロセスを「グループ」として定義し、一括で起動・停止・再起動できます。これにより、マイクロサービスのような構成のプロジェクトでも、一貫したプロセス管理が可能です。

3. ログ・状態の永続化

プロセスの状態やログは ~/.config/portmux/ にファイルとして残ります。portmux psportmux logs で動作状況をすぐ確認でき、常駐デーモンに依存せずにログと状態を追跡できます。

4. Git 連動のグループ解決

グローバル設定と Git 情報を組み合わせて、リポジトリ内のどのサブディレクトリにいても正しいグループを自動解決します。モノレポや複数パッケージ構成でも、ディレクトリを移動せずに portmux startportmux select を実行できます。

基本的な使い方

実際に PortMux を使った開発の流れを紹介します。

ステップ 1. インストール

まずは PortMux CLI をグローバルにインストールします。
bun が速いのでオススメです。

# npm
npm install -g @portmux/cli@latest

# bun
bun install -g @portmux/cli@latest

ステップ 2. 設定ファイルの生成

プロジェクトのルートディレクトリで portmux init を実行し、設定ファイル portmux.config.json を生成します。

portmux init

生成されたファイルには、以下のようなプロセスグループの定義を記述します。

portmux.config.json
{
  "groups": {
    "app": {
      "description": "Web App and API",
      "commands": [
        {
          "name": "web",
          "command": "pnpm dev",
          "ports": [3000],
          "cwd": "./apps/web"
        },
        {
          "name": "api",
          "command": "pnpm start",
          "ports": [8080],
          "cwd": "./apps/api"
        }
      ]
    }
  }
}

この例では、webapi という 2 つのプロセスを app グループとして定義しています。web はポート 3000 を、api はポート 8080 を使用します。

ステップ 3. portmux select で起動と切り替え

複数の worktree を跨いで開発する場合は、select を軸に動かすのが一番ラクです。まずは別の worktree を作成します。

# feature-Aブランチ用のworktreeを作成
git worktree add ../project-feature-a feature-a

project-feature-a ディレクトリに移動する必要はありません。元のディレクトリのまま portmux select を実行します・

portmux select

すると、以下のように対話的なセレクタが表示されます。

? Select a group to start
--- project ---
> project:feature-a (~/project-feature-a)
  project:main (~/project)

ここで project:feature-a を選択すると、PortMux は自動的に main worktree で実行されていたプロセスを停止し、feature-a worktree で新たにプロセスを起動します。

Stopping running worktree: ~/project [main]
✓ Stopped process "web" (project:main (~/project))
✓ Stopped process "api" (project:main (~/project))
✓ Started process "web" (project:feature-a (~/project-feature-a))
✓ Started process "api" (project:feature-a (~/project-feature-a))

portmux ps で確認すると、プロセスの管轄が feature-a に移っていることがわかります。

$ portmux ps
┌─────────┬───────────────────────┬────────┬────────┬──────────┬──────┐
│ (index) │ Repository            │ Group  │ Process│ Status   │ PID  │
├─────────┼───────────────────────┼────────┼────────┼──────────┼──────┤
│ 0       │ project:feature-a     │ app    │ web    │ Running  │ 54321│
│ 1       │ project:feature-a     │ app    │ api    │ Running  │ 54322│
└─────────┴───────────────────────┴────────┴────────┴──────────┴──────┘
  ✓ project:feature-a (~/project-feature-a)/web (PID: 54321)
  ✓ project:feature-a (~/project-feature-a)/api (PID: 54322)

このように、portmux select を使うだけで、ポート競合を気にせず複数の worktree を行き来できます。

ステップ 4. 個別に起動や確認をしたいとき

単一の worktree だけで動かす場合や、個別のプロセスを明示的に起動したい場合は portmux start を使えます。

portmux start app
✓ Started process "web" (portmux (~/workspace/github.com/YTakahashii/portmux))
✓ Started process "api" (portmux (~/workspace/github.com/YTakahashii/portmux))

portmux ps で現在の状態を確認できます。

❯ portmux ps
┌─────────┬───────────┬────────┬────────┬──────────┬──────┐
│ (index) │ Repository│ Group  │ Process│ Status   │ PID  │
├─────────┼───────────┼────────┼────────┼──────────┼──────┤
│ 0       │ portmux   │ app    │ web    │ Running  │ 92542│
│ 1       │ portmux   │ app    │ api    │ Running  │ 92408│
└─────────┴───────────┴────────┴────────┴──────────┴──────┘
  ✓ portmux (~/workspace/github.com/YTakahashii/portmux)/web (PID: 92542)
  ✓ portmux (~/workspace/github.com/YTakahashii/portmux)/api (PID: 92408)

複数 worktree を跨いでいる場合は、select を使うのが安全かつ便利です。手動で start を叩くと、すでに他の worktree がポートを握っているときに Failed to reserve ports: Port XXXX is already in use で失敗します。

手動で整理したい場合は、競合している worktree に移動するか portmux ps でプロセスを確認した上で portmux stop --all を実行すれば、全グループをまとめて停止できます。

今後の展望

需要がありそうであれば以下の機能も実装していきたいと考えています!

  • 複数 OS 対応:まだ macOS でしか動作を確認していないため、他の OS もサポートしたいです。ただ、筆者は Windows マシンを持っていないため、もし Windows 対応を希望される方はコントリビュートお待ちしております!
  • portmux dashboard: Docker Desktop のような UI ダッシュボードでプロセス管理できたら便利かも?
  • VSCode 拡張: タブから UI で切り替えられると便利かも?

おわりに

PortMux は、git worktree を活用した開発フローで頻発するポート競合問題を解決するための CLI ツールです。

GitHub の README に、より詳細な使い方を記載していますので是非確認してみてください。

この記事を見て良さそうと思って頂けたら ⭐️ お願いします!

https://github.com/YTakahashii/portmux

https://www.npmjs.com/package/@portmux/cli

Discussion