AI時代の並列開発:Gitワークツリーのポート衝突問題をドメインで解決する

こんにちは、橋本です。
この記事では、AIエージェント時代の開発において、Gitワークツリーを最大限活かすための開発環境構成を紹介します。

いきなりですが、みなさんはコーディングにおいて、どのAIツールを使っていますか? 私はClaude Code を使っています。もはやAIエージェントなしでは開発できないくらい、ほとんどのタスクで使用しています。

そしてClaude Codeやその他のAIエージェントを用いて開発していると、次のような欲が出てきます。

  • 同時に機能開発やバグ修正をしたい
  • AIが生成したコードを複数パターンで検証したい
  • メインタスクのブランチを汚さずに別PRのレビューや動作確認をしたい

いちいちgit stashや無理にgit commitしたくないですよね...

AI x Gitワークツリーは便利

このような開発環境において、Gitワークツリー が強力な武器になります。
これはClaude Codeの公式ドキュメントでも推奨されている手法です。

# Gitワークツリー で複数ブランチを物理的に同時展開
git worktree add ../feature-auth -b feature/auth
git worktree add ../feature-payment -b feature/payment
git worktree add ../fix-bug123 -b fix/bug-123
~/projects/
├── myapp/              # main ブランチ
├── feature-auth/       # feature/auth ブランチ
├── feature-payment/    # feature/payment ブランチ
└── fix-bug123/         # fix/bug-123 ブランチ

このようにすれば、各ディレクトリで独立した開発サーバーを起動できるため、ブランチ切り替えなしで複数の機能を同時に開発・動作確認できます。

課題

しかし、複数環境を立ち上げると面倒なことがあります。
それは、ブランチごとにポートを変更して覚えておくのが大変ということです。 例えばDocker Composeでポートマッピングしている場合、ホスト側で同じポートを使おうとして衝突してしまいます。それを避けるために環境変数などでポートを変えても、結局どのポートがどのブランチ用か分からなくなってしまいます。

# 各ディレクトリでサーバーを起動すると...
~/myapp/           → localhost:3000
~/feature-auth/    → localhost:3001  # ポートの衝突を避けるため変更
~/feature-payment/ → localhost:3002
~/fix-bug123/      → localhost:3003

# => どのポートがどのブランチ用か分からなくなる

OrbStack の Container domain names 機能

ここではまず、macOS ではここまで快適にできるという理想形として OrbStack を紹介します。 (Windows も含めた解決方法は後述します)

OrbStack とは

OrbStack is a fast, light, and simple way to run containers and Linux machines. It's a supercharged alternative to Docker Desktop and WSL, all in one easy-to-use app.

引用元: https://docs.orbstack.dev/

Google翻訳すると

OrbStackは、コンテナとLinuxマシンを高速、軽量、そしてシンプルに実行できるツールです。Docker DesktopやWSLに代わる強力な代替手段であり、オールインワンの使いやすいアプリです。

今回ご紹介するContainer domain names 機能を使わずとも、非常に魅力的なツールです。

Container domain names 機能

OrbStack にはContainer domain names 機能というものがあり、コンテナごとにドメインを自動で割り当ててくれます。 設定ゼロで利用可能で、以下のような特徴があります。

  • docker-compose.yamlでポートマッピングしなくても、自動的に <container-name>.orb.local<service>.<project>.orb.local でアクセス可能
  • hosts ファイルの編集不要
  • HTTPS対応も自動

設定例

docker-compose.yaml

services:
  backend:
    build: ./backend
    volumes:
      - ./backend:/app
      - /app/node_modules

  frontend:
    build: ./frontend
    volumes:
      - ./frontend:/app
      - /app/node_modules
    depends_on:
      - backend

通常の Docker Compose では ports: - "3000:3000" のようにポートマッピングが必要ですが、OrbStack では不要です。

frontend/.env.example

API_BASE_URL=https://backend.orbstack-sample.orb.local

動作確認

# コンテナ起動
% cd ~/project-name
% docker compose up -d

# APIの確認
% curl https://backend.orbstack-sample.orb.local/api/hello
{"message":"Hello from API 1"}

# ブラウザでも確認
% open https://frontend.orbstack-sample.orb.local/

とても良いですね。Gitワークツリーを用いて複数ブランチでサーバーを起動しても、同じようにアクセスできます。
ちなみに、ドメインはOrbStackのコンテナのInfoから確認できます。

macOS 以外の環境でも同じ体験を再現したい

OrbStack の Container domain names 機能は非常に快適ですが、macOS 以外の環境では利用できません。チーム開発においても困ります。
そこでOrbStackを使わずに同じ体験を再現できないか?と考え、たどり着いたのが *.localhost サブドメイン + リバースプロキシ でした。

*.localhostの補足

The domain "localhost." and any names falling within ".localhost." are special in the following ways:

引用元: https://datatracker.ietf.org/doc/html/rfc6761

localhostRFC 6761 で特別なドメインとして予約されているため、*.localhost はhosts ファイルの編集なしで 127.0.0.1 に解決されます。

DNS 解決とポート解決は別問題

しかし、DNS 解決だけでは解決になりません。「サブドメインでアクセスできる」だけでは不十分で、「どのコンテナのどのポートに流すか」を解決する必要があります。
そこで Caddy の出番です。
(もちろん Nginx でも可能ですが、Caddy は開発用証明書の自動生成も担ってくれるため、HTTPSにも対応しやすくて便利です)

Caddy x Docker Compose

caddy-docker-proxy を使い、OrbStackのContainer domain names 機能を模倣します。

ファイル構成

docker-sample/
├── docker-compose.yaml           # アプリケーションサービス
├── docker-compose.network.yaml   # Caddy
├── setup-worktree.sh             # Gitワークツリーのセットアップスクリプト
├── .env                          # サブドメイン設定
├── .env.example                  # サブドメイン設定テンプレート
│
├── backend/
│   ├── Dockerfile
│   └── src/index.ts
│
└── frontend/
    ├── Dockerfile
    ├── .env
    ├── .env.example
    └── src/App.tsx

docker-compose.network.yaml(インフラ用)

caddy-docker-proxyのREADME.md を参考に設定します。

services:
  caddy:
    image: lucaslorentz/caddy-docker-proxy:ci-alpine
    ports:
      - 80:80
      - 443:443/tcp
      - 443:443/udp
    environment:
      - CADDY_INGRESS_NETWORKS=docker-sample-shared-net
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - caddy_data:/data
    networks:
      - shared_net
    restart: unless-stopped

networks:
  shared_net:
    name: docker-sample-shared-net
    external: true

volumes:
  caddy_data: {}

docker-compose.yaml(アプリケーション用)

  • labels で Caddy のルーティングを宣言的に設定
  • ${SUBDOMAIN:-develop} により、環境変数でサブドメインを動的に変更可能
  • external: true で既存のネットワークに参加
services:
  backend:
    build: ./backend
    volumes:
      - ./backend:/app
      - /app/node_modules
    networks:
      - shared_net
    labels:
      caddy: "${SUBDOMAIN:-develop}.api.localhost"
      caddy.reverse_proxy: "{{upstreams 3000}}"

  frontend:
    build: ./frontend
    volumes:
      - ./frontend:/app
      - /app/node_modules
    networks:
      - shared_net
    labels:
      caddy: "${SUBDOMAIN:-develop}.localhost"
      caddy.reverse_proxy: "{{upstreams 3333}}"
    depends_on:
      - backend

networks:
  shared_net:
    name: docker-sample-shared-net
    external: true

setup-worktree.sh

Gitワークツリーを本格的に使い始めると、 ブランチごとに .env を書き換える作業自体が新たなストレスになります。 そこで、Gitワークツリーの作成から環境構築までを一気に行うスクリプトを用意しました。

利用イメージ

% ./setup-worktree.sh feature/auth

処理の流れ

  1. 指定したブランチ名からディレクトリ名を生成(/- に変換)
  2. 指定したブランチが存在しない場合は新規作成
  3. Gitワークツリーでディレクトリを作成
  4. .envfrontend/.env を設定
  5. コンテナを起動
#!/bin/bash

# Cross-platform sed -i
sed_i() {
  if [[ "$OSTYPE" == "darwin"* ]]; then
    sed -i '' "$@"
  else
    sed -i "$@"
  fi
}

BRANCH_NAME=$1
DIR_NAME=${BRANCH_NAME//\//-}

# Create worktree (create branch if it doesn't exist)
if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then
  git worktree add "../$DIR_NAME" "$BRANCH_NAME" || { return 1 2>/dev/null || exit 1; }
else
  git worktree add -b "$BRANCH_NAME" "../$DIR_NAME" || { return 1 2>/dev/null || exit 1; }
fi
cd "../$DIR_NAME" || { echo "❌ Failed to cd into $DIR_NAME"; return 1 2>/dev/null || exit 1; }

# Set up environment files
cp .env.example .env
sed_i "s|SUBDOMAIN=.*|SUBDOMAIN=$DIR_NAME|" .env

cp frontend/.env.example frontend/.env
sed_i "s|API_BASE_URL=.*|API_BASE_URL=https://$DIR_NAME.api.localhost|" frontend/.env

# Start Docker containers
docker compose up -d

echo "✅ Ready at https://$DIR_NAME.localhost"

起動手順

初回セットアップ

# 共有ネットワークを作成
% docker network create docker-sample-shared-net
fcfaaca6b972cbf477603a0acc77d855f6b295e55f0379467842a7f6d9a2ca18

# Caddy コンテナを起動
% docker compose -f docker-compose.network.yml up -d
[+] Running 1/1
 ✔ Container docker-sample-caddy-1  Started     

# Caddy のルート証明書をインストール(HTTPS 用)
# ブラウザで警告なしにアクセスするため、ローカルCAの証明書を信頼させます。
# 証明書をコンテナからコピー
% docker cp docker-sample-caddy-1:/data/caddy/pki/authorities/local/root.crt ./root.crt
Successfully copied 2.56kB to /Users/{username}/Documents/personal/docker-sample/root.crt

macOS の場合、ダウンロードしたroot.crtをFinderなどからダブルクリックすると、キーチェーンアクセスが開くので、証明書を「常に信頼 (Always Trust)」に設定します。

設定が成功すると、以下のように表示されます。

開発環境を立ち上げる

% docker compose up -d
[+] Running 2/2
 ✔ Container docker-sample-backend-1   Started
 ✔ Container docker-sample-frontend-1  Started

新しいブランチで開発環境を立ち上げる

% . ./setup-worktree.sh feature/sample2
Preparing worktree (checking out 'feature/sample2')
HEAD is now at bd32d1e feat: Add cross-platform worktree setup script
[+] Running 2/2
 ✔ Container feature-sample2-backend-1   Started
 ✔ Container feature-sample2-frontend-1  Started
✅ Ready at https://feature-sample2.localhost

動作確認

どちらもきちんと動作しました。
(差分をわかりやすくするため、ソースコードを少し変更しています)

  • https://develop.localhost にアクセス

  • https://feature-sample2.localhostにアクセス

最後に

OrbStackもCaddyも素晴らしいですね。実際に試してみてとても良い体験だったので、弊社の開発環境にも導入できないか、試してみようと思います。

Legalscapeでは開発環境やCI/CDの改善にも継続的に取り組んでいますので、ご興味ある方はまずはぜひカジュアル面談をさせていただけると嬉しいです。お待ちしております!