• トップ
  • ブログ一覧
  • Claude Code × Git Worktree × Supersetで実現する並列開発
  • Claude Code × Git Worktree × Supersetで実現する並列開発

    はじめに

    Claude Code で並列開発をしたいけれど、アプリケーションの動作確認はローカルサーバーのポートが競合してできない、というもどかしさを感じた方は多いのではないでしょうか?
    特に Docker Compose を使っているプロジェクトでは、docker compose up を 2 つ走らせると即座にポートかコンテナ名で衝突します。
    本記事ではこの問題を、git worktree + 自前スクリプト + AI 統合開発環境 Superset の組み合わせで解いた手順を残します。

    なぜ並列開発したいのか

    動機 1: タスクを並行して進めたい

    Claude Code に作業を投げたあとの「待ち時間」は、開発内容によっては結構長いです。
    この間をぼーっとお茶しているわけにもいかないので、積まれている他の作業を進めたいですよね。

    動機 2: PR レビューする時の準備が面倒くさい

    作業中にレビュー依頼が来たときは、以下のような作業をするかと思います。

    • 自分の作業差分をスタッシュ
    • PR のブランチにチェックアウトして動作確認
    • PR に指摘コメントを投稿する
    • 自分の作業ブランチに戻ってスタッシュを適用して、作業再開
    • PR の指摘内容が修正されたため、再度スタッシュして、PR のブランチにチェックアウトして動作確認

    この毎回スタッシュする工程が非常に面倒でした。
    コミットすればスタッシュの工程はスキップできますが、コミットは自分のタイミングでしたいですよね。

    素朴な解: git worktree 単体

    最初の発想は素朴で、git worktree を使えば 1 つのリポジトリから複数の作業ディレクトリを生やせます。
    ブランチを切り替えずに別ブランチを編集できるので、これだけで「checkout で作業を中断する」問題は消えるはずです。

    ただしなにも対処をしていないと、作成したワークツリー内でコンテナを立ち上げると、メインツリーで起動しているサーバーのポートと衝突して起動できません。
    ワークツリーを作成しただけでは、Docker Compose の世界では並列起動できる状態になっていません
    静的レビューやサーバーへのアクセスを必要としない作業であれば問題ないかもしれませんが、ほとんどの場合そうではありません。

    解決策: 自動ポート割当スクリプト

    全体像

    ポートが競合するのであれば、ワークツリーごとにポートを変更できるようにすれば解決です。
    前提として、本プロジェクトの docker composeRails / MySQL / Redis / memcached の 4 コンテナを 1 セットで立ち上げる構成になっています。
    Rails コンテナはアプリケーション本体で、起動時に MySQL(DB 永続化)・Redis(キャッシュ)・memcached(セッション・短期キャッシュ)の 3 つにネットワーク越しにアクセスしています。

    実際の docker-compose.yml(抜粋)は次のような形です。ホストポートとコンテナ名を環境変数で外から差し込めるようにしてあるのがポイントです。
    「変更するポートが 1 つだけならワークツリーごとに手動で書き換えても手間にはならないのでは?」と思う方もいるかもですが、下記は例であり実際には 3 ポートあり、コンテナ名も書き換えないといけないので手動で毎回変更するのは面倒です。

    1services:
    2  memcached:
    3    container_name: memcached
    4    image: memcached:latest
    5    networks: [my-app-network]
    6
    7  redis:
    8    container_name: redis
    9    image: redis:latest
    10    networks: [my-app-network]
    11
    12  db:
    13    container_name: db
    14    image: mysql:8.0
    15    environment:
    16      MYSQL_DATABASE: myapp
    17    networks: [my-app-network]
    18
    19  my-app:
    20    container_name: ${CONTAINER_NAME:-my-app}
    21    build: { context: ., dockerfile: Dockerfile }
    22    environment:
    23      MYSQL_HOST: db          # ← Rails から DB へはサービス名で参照
    24    ports:
    25      - "${HOST_PORT:-3013}:3000"   # ← ここを .env で差し替える
    26    networks: [my-app-network]
    27    depends_on:
    28      redis: { condition: service_started }
    29      db:    { condition: service_healthy }
    30
    31networks:
    32  my-app-network:
    33    external: true            # ← 共有ネットワーク(後述)

    注目すべきは次の 2 点です。

    • HOST_PORTCONTAINER_NAME を環境変数化している点。これを .env 経由でワークツリーごとに差し替えることで、同じ compose ファイルを複数ワークツリーで使い回せます。
    • networks.my-app-network.external: true。Compose 内で作るのではなく、外部に既に存在するネットワークに繋ぐ宣言です。これがリンクワークツリーから「メイン側の MySQL / Redis / memcached」に繋ぎに行く土台になります(詳細は後述)。

    このコンテナ群をポートの衝突なしに立ち上げるには、異なるポートで立ち上げる必要があります。
    ただし、毎回起動ポートを手動で変更して立ち上げるのは面倒なので自動化させたいです。

    ここでまず考えなくてはいけないのは、「MySQL / Redis / memcached」をワークツリー間で共有するかどうかです。
    共有しない場合、ワークツリーごとに毎回これらを起動しなおさなくてはいけません。
    軽量であればいいのですが、DBのデータ量が多い場合などで毎回起動するまでに時間を要するのは避けたいです。
    共有する場合、1 つのワークツリー内での変更が全ワークツリーに波及するデメリットはありますが、起動に時間を要するデメリットと比べると妥協できるレベルだったので共有する方針としました。

    そのため、ポートの競合で解決しなくてはいけないのは Rails サーバー周りのポートだけになりました。

    手順は以下の 3 ステップになります。

    • ポートをワークツリーごとに自動で別の値に割り当てる
    • .env にそれを書き込む(既存の手書き設定は壊さない)
    • ここまでの作業を 1 コマンド で完了させる

    これから登場するスクリプトの最終的な配置は次のようになります。

    1my-app/
    2├── docker-compose.yml
    3├── .env
    4└── scripts/
    5    └── worktree/
    6        ├── port.sh        # ポートを算出
    7        ├── setup.sh       # .env の管理ブロックを書き換え
    8        └── docker-up.sh   # setup.sh + docker compose up を 1 コマンドに

    ポートをパスから決定的に割り当てる

    ポート割当の要件はシンプルに 2 つです。

    • 衝突しない: 他のワークツリーと被らない
    • メインツリーのポートは固定にしたい: 本プロジェクトでは Rails は API サーバーのため、フロント開発者も立ち上げて API 通信をします。その時にポートが固定化されていないと、API サーバーのポートに合わせてフロントの通信先を変更する必要が出てきてしまいます。そのためメインツリーは固定にする必要があります。

    自動ポートの設定方法に決まりはないですが、AI が提案してきたやり方にしました。
    やっていることは以下です。

    • 絶対パスを取得
    • メインツリーは「3013」を指定
    • リンクツリー(派生したワークツリー)のポートは、パスから「10000〜19999」のいずれかのポートを算出

    実装は scripts/worktree/port.sh という単機能スクリプトに切り出していて、算出したポート番号を標準出力に echo するだけにしています。.env への書き込みは後述の setup.sh$(...) で受け取る役割分担です。

    1TOPLEVEL="$(git rev-parse --show-toplevel)"
    2
    3# メインワークツリーは .git が「ディレクトリ」、リンクは「ファイル」
    4if [ -d "$TOPLEVEL/.git" ]; then
    5  echo "3013"
    6else
    7  # パスの MD5 から数字だけ抽出 → 先頭4桁 → 1000019999 へ写像
    8  echo "$TOPLEVEL" | md5 | tr -d -c '0-9' | head -c 4 | \
    9    awk '{port = ($1 % 10000) + 10000; print port}'
    10fi

    .env の管理ブロック方式

    ポートが決まったら、それを .env に書き出して docker compose に読ませます。
    ただし注意点があり、スクリプトが .env を毎回上書きすると、.env にすでに設定している手書きの変数が消えてしまいます。

    ここもAIと相談し マーカーで囲まれた管理ブロックだけを書き換える やり方を採用しました。
    実装は scripts/worktree/setup.sh の中にあります(以下はその管理ブロック処理部分の抜粋)。

    1ENV_FILE="$TOPLEVEL/.env"
    2MARKER_START="# === scripts/worktree/setup.sh managed (do not edit) ==="
    3MARKER_END="# === end scripts/worktree/setup.sh ==="
    4
    5# 既存 .env から旧管理ブロックだけ除去
    6awk -v s="$MARKER_START" -v e="$MARKER_END" '
    7  $0 == s {skip=1; next}
    8  $0 == e {skip=0; next}
    9  !skip {print}
    10' "$ENV_FILE" > "$TMP_FILE"
    11
    12# 新しい管理ブロックを追記
    13cat >> "$TMP_FILE" <<EOF
    14$MARKER_START
    15COMPOSE_PROJECT_NAME=$PROJECT_NAME
    16HOST_PORT=$HOST_PORT
    17EOF
    18
    19mv "$TMP_FILE" "$ENV_FILE"

    awk で旧マーカーブロックをスキップしながら一時ファイルに転記し、末尾に新ブロックを追記してから mv で差し替えています。
    一時ファイルを挟むのは、書き込み中にスクリプトが落ちても元の .env を壊さないためです。

    1 コマンドで起動するラッパー

    ここまでで .env は自動生成できますが、毎回「setup.shdocker compose up -d」と 2 行打つのは地味に面倒です。
    そこで scripts/worktree/docker-up.sh というシンプルなラッパーを置きます。

    1TOPLEVEL="$(git rev-parse --show-toplevel)"
    2
    3# .env を生成
    4bash "$TOPLEVEL/scripts/worktree/setup.sh"
    5
    6# 生成された .env を読みつつ起動
    7(cd "$TOPLEVEL" && docker compose up -d)

    使う側はこれだけで済みます。

    1# ワークツリー追加
    2git worktree add ../my-app-feature-x feature/x
    3cd ../my-app-feature-x
    4
    5# .env 生成 + コンテナ起動(1 コマンド)
    6bash scripts/worktree/docker-up.sh

    setup.sh を独立して残しているのは、「ポート再計算だけしたい」「.env の中身だけ確認したい」というケースで単独実行できるようにするためです。
    ラッパーは「便利のため」であって、setup.sh がコア機能を担うという役割分担を意識しています。

    注意点

    運用して気づいた落とし穴を 3 つ紹介します。

    1. メインを先に起動する

    リンクワークツリーは共有ネットワーク・共有ボリュームに繋ぎに行くため、メイン側が起動していないと network not found で失敗します。
    チーム内で一番ハマるのがこれです。

    2. 並列開発の構成をAIに伝える

    ここまで解説した並列開発の方法はAIエージェントに伝えておく必要があります。
    理由として、AIエージェントが実装した後に自動でテストがパスするまで確認することもあるかと思います。
    その時にAIエージェントが並列開発の構成を知らないとリンクワークツリーで作業しているのにメインワークツリーでテスト実行してしまう可能性があります。
    そうならないように、例えばclaude codeを扱っている場合などは、CLAUDE.mdに記載しておくことをオススメします。

    Superset で さらに快適に

    ここまでで並列開発は動きます。
    ですがまだターミナルで git worktree add コマンドを手動実行する面倒さが残ります。
    この最後の手間を解消し、さらにAIエージェントをより快適に扱えるようにしてくれるのがSuperset(AI 統合開発環境)です。

    私はまだ使いこなせていませんが、ざっと良かった点を以下に記載します。

    • プロジェクトをタブで管理でき、さらにプロジェクト内にワークツリーを数クリックで作成でき、ワークツリーごとにターミナルを何個でも起動することができる
    • 作成したワークツリーがカレントディレクトリになっている状態のVS Codeをワンクリックで起動できる
    • アプリ内でブラウザを開ける(ただしブラウザの挙動がおかしい時があるため今後に期待)

    料金プラン

    気になる料金ですが、公式の料金ページによれば、現時点では以下の 3 プランが用意されています(執筆時点・税別)。

    • Free: $0。1 ユーザーまで、ローカルワークスペース・デスクトップアプリ・GitHub 連携が使えます。本記事で紹介した「ワークツリー作成 + 複数 Claude Code セッション」のような個人検証用途であれば、まずは Free から試せます。
    • Pro: 月額 $20/ユーザー、年契約なら $15/ユーザー(25% オフ)。ユーザー数無制限で、リモートワークスペースや Linear 連携が解放されます。
    • Enterprise: 要問い合わせ。SSO/SAML・監査ログ・SLA・専任サポートなど。

    私自身フリープランを使用していますが、特に不自由はありませんでした。
    Pro以降に上げてどう運用していくかはあまり想像ついていないですが、単純に並列開発をしたいのであればフリーで十分かなと思います。

    まとめ

    いかがでしたでしょうか?
    快適な環境を手にいれるための実装が思ったよりも面倒でした。
    ただ一度環境を作ってしまえばあとは快適に開発サイクルを回すことができます。
    本ブログの方法以外にも色々な方法があると思いますので、ご自身のプロジェクトにあったやり方を探してみることをオススメします。

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!カジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    はっと(エンジニア)
    はっと(エンジニア)
    Show more...

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background