FLARES LLC

Technical document

複数worktreeを安全に並行開発するOrbStack・Docker環境分離

OrbStackへ移行するだけでは、複数worktree間の競合は解消しません。コンテナランタイムは共有基盤として一つにしながら、Compose project、コンテナ、ネットワーク、公開ポート、image tag、可変データをworktree単位の名前空間へ分ける必要があります。
開発環境 / Docker約18分公開日 2026年7月19日更新日 2026年7月19日
FLARESの技術文書用OGP画像

Summary

この文書の要点

  • ランタイムをOrbStackへ一本化しても、固定されたCompose project名、公開ポート、volume、image tagが残ればworktree同士は競合します。
  • worktree由来の安定IDと排他的なポート台帳を使い、project、コンテナ、ネットワーク、volume、imageを一つの単位として分離します。
  • 共有するのは不変なimage layerとBuildKit cacheを基本とし、DBやオブジェクトストレージなどの可変データはworktreeごとに持ちます。
  • downはデータを保持し、明示確認付きdestroyだけが厳密に一致するリソースとポート枠を削除する境界にします。

OrbStackへの移行とworktree分離は別の問題です

macOSでDocker DesktopとOrbStackを併存させると、それぞれの仮想環境にimage、volume、build cacheが蓄積します。同じデータを二つのランタイムに持てば、ディスク使用量が増えるだけでなく、現在のDocker contextやBuildx builderがどちらを向いているかも分かりにくくなります。ランタイムを一つにすることは、この重複を減らすための基盤整理です。

一方、複数worktreeや複数のAIコーディングタスクを同時に動かす問題は、ランタイムを替えても残ります。同じCompose project名、container_name、host port、named volume、image tagを使えば、別worktreeのcompose upが既存コンテナを別のbind mountで再作成したり、DBを共有したり、ポート競合で停止したりします。

BuildKitには同じbuild処理を効率化する待ち合わせやcache共有がありますが、アプリケーション全体の安全な順番待ちを保証する仕組みではありません。migration、compose up、テスト、volume更新を別タスク間で調停するものではないため、実行環境の名前空間は別に設計します。

  • 共有基盤: OrbStack上のDocker Engine、image layer、選択したBuildKit cache。
  • worktreeごとの実行環境: Compose project、コンテナ、ネットワーク、公開ポート、volume、image tag。
  • 可変データ: DB、オブジェクト、メール、キュー、migration状態をworktree単位で分離する。

競合要因を名前・ポート・データ・操作に分けて診断します

最初にComposeの最終設定を展開し、固定値を探します。明示されたproject名、container_name、host側ポート、externalまたは固定nameのvolume・network、固定image tag、env内のlocalhost固定ポートが主な確認対象です。Composeファイルだけでなく、上書きファイル、env、Makefile、テスト設定、READMEも同じ前提を持つことがあります。

bind mountはworktreeごとに異なるソースを指します。同じ名前のコンテナを共有したまま別worktreeで再作成すると、見た目は同じサービスでも、読み込むコードだけが別worktreeへ切り替わり得ます。これは単なるポート競合より発見しにくく、片方のタスクがもう片方の変更を実行しているように見える原因になります。

破棄操作も診断対象です。docker system pruneや、対象を指定しないbuilder pruneは、別worktreeや別タスクが利用するcacheやリソースまで削除し得ます。共有ランタイムでは、便利な大域操作ほど影響範囲を確認し、project labelやbuilder名で対象を狭める必要があります。

  • 固定project名とcontainer_nameが、Composeの自動名前空間を無効にしていないか。
  • host portとenv内localhost portが、同じ割り当て規則で解決されているか。
  • named volumeとimage tagが、別worktreeから同じ名前で更新されないか。
  • global pruneやbuilder切替が、別タスクの実行中にも到達できないか。

Docker Desktopからの移行は、棚卸しと照合を先に行います

移行前に、compose、build、migration、コンテナを使うテストを止めます。次にディスク空き容量、Docker context、container、image、volume、Buildx builderを一覧化し、どのデータが再生成可能で、どれが移送必須かを分類します。cacheは再生成できますが、DBやオブジェクトストレージのvolumeは同じ扱いにできません。

再生成できるcacheは、利用中のbuilderと保持量を確認してから対象を限定して回収します。その後、関連コンテナを止め、volumeを移します。OrbStackの自動移行を使う場合も、完了表示だけで判断せず、移行元に存在したvolumeがすべて移行先にあるかを照合します。手動コピーでは、巨大なtarをホストへ保存せず、移行元volumeから標準出力へ流したarchiveを移行先volumeへ直接展開すると、一時領域を抑えられます。

主要volumeは、ファイル数とlogical bytesを移行前後で比較し、DBは代表queryやDB固有の整合性検査も加えます。macOS由来の補助metadataやソケットなど、完全一致を期待しない項目は例外として先に記録します。必要なimageだけを移送するか再buildし、Docker contextをOrbStackへ切り替え、custom builderは新しいendpointで作り直します。health checkとsmoke testを通してから旧ランタイムを削除します。

旧ランタイムの削除後は、docker、docker compose、docker buildxの実体とリンクを確認します。OrbStack公式は、既存のCLIが別の入手元から入っている場合は自動で置き換えず、古いCLIを除いた後にアプリを再起動する手順を案内しています。context、CLI、Compose、Buildxの四つを別々に確認すると、移行後の『一部のコマンドだけ消えた』状態を切り分けやすくなります。macOSの保護metadataで削除エラーが出た場合も、データ移行の失敗と同一視せず、公式のuninstall手順に沿って切り分けます。

  • 停止 → 棚卸し → 対象限定のcache回収 → volume移送 → 内容照合 → image準備の順に進める。
  • context切替とcustom builder再作成後に、health checkとsmoke testを行う。
  • 旧ランタイムの削除は、移送データと主要サービスの起動を確認した後に行う。
  • OS固有metadataなどの例外は、照合から黙って除外せず記録する。

安定IDとポート台帳から解決済みComposeを生成します

worktreeごとの安定IDは、worktreeの正規化したpathとbranch情報から作ります。人が読める短い接頭辞に短いhashを加え、同名branchや長いpathでも衝突しにくくします。このIDをCompose project、network、volume、image tagの共通scopeとして使います。

公開ポートは、共有台帳から連続したslotまたはblockを割り当てます。台帳更新はfile lockや原子的な更新で排他し、同時にprepareしても同じ枠を受け取らないようにします。割り当て結果はstatusやportコマンドで取得し、READMEやテストが固定ポートを前提にしない構成へ変えます。

起動前にベースComposeを読み、container_nameを除去し、公開ポート、env内のlocalhostポート、named volume、image tagをscopeした解決済みComposeを生成します。生成物はリポジトリ配下のGit管理対象外ディレクトリに置き、認証情報を含み得るためpermissionを0600にします。利用者は生成物を直接編集しません。

入口はprepare、up、down、destroy --yes、status、compose、port、services、doctorに絞ります。wrapperはDocker contextがOrbStackを指すことと、DOCKER_HOSTやDOCKER_CONTEXTによる接続先の上書きがないこと、期待するproject IDを確認してから処理します。ベースCompose側にもproject ID未設定時に失敗するguardを置き、生のdocker composeが固定名で起動できないようにします。downはコンテナとネットワークを止めてもvolumeとslotを保持し、完全破棄だけをdestroyへ分けます。

  • prepare: 安定ID、ポート枠、解決済みCompose、環境ファイルを準備する。
  • up / compose: 必ずwrapper経由で、解決済み設定とscopeを使う。
  • status / port / services / doctor: URL、割り当て、起動状態、guardを機械的に確認する。
  • down: 可変データとslotを保持する。destroy --yes: 厳密一致する専用リソースだけを完全破棄する。

可変データの共有は、内部namespaceまで分けられる場合だけ成立します

DB、オブジェクトストレージ、メール確認、キューを一つだけ起動して共有すれば、メモリを節約できます。しかし、接続先が同じでもDB名またはschema、bucket、queue、mailbox、migration履歴がworktreeごとに分かれていなければ、実行環境の分離は途中で破れます。AのmigrationがBのテーブルを変える構成は、コンテナ名を分けても安全ではありません。

標準設計では、DBやストレージを含む可変データをworktree専用volumeへ置きます。共有するのはimmutableなimage layerと、内容が壊れても再生成できるBuildKit cacheに限定します。ディスク使用量は増えますが、破棄範囲と再現性が明確になります。

共有DBが成立するのは、worktree IDからDBまたはschemaを必ず解決し、接続権限もそのnamespaceだけに限定し、migration lockと履歴、seed、queue、bucket、メールまで同じIDで分けられる場合です。どれか一つでも固定値へ戻るなら、共有案ではなく専用volumeへ戻す方が安全です。

  • 共有候補ごとに、名前空間、権限、migration、削除単位を確認する。
  • 接続URLが異なるだけでなく、保存される可変状態の全経路が分かれていることを条件にする。
  • production相当のcredentialや実データを、開発用worktreeへ安易に複製しない。

destroyはproject IDとlabelが一致する時だけ削除します

完全破棄は、project IDの形式、現在のworktreeとの対応、Compose label、volume labelを順に検証してから実行します。明示的な--yesがなければ対象一覧を表示して停止し、確認後も一致するリソースだけを削除します。名前の前方一致や曖昧なglobを削除条件にしません。

ポートslotはdownでは解放しません。データを保持したまま再開するworktreeが別のポートを取得すると、保存済みURLやテスト設定とずれるためです。slotを解放するのは、volumeを含む完全破棄が成功した時だけにします。途中失敗した場合は台帳に状態を残し、doctorで回復できるようにします。

共有ランタイムでは、docker system pruneや全builderを対象にしたcache削除を通常手順へ入れません。削除例を示す場合も、先に対象projectまたはbuilderを一覧し、他のworktreeが使用中でないことを確認する手順とセットにします。

  • 対象特定: current worktree、project ID、Compose label、volume labelをすべて照合する。
  • 明示確認: destroy --yesだけがvolume削除へ進める。
  • 削除範囲: 一致したcontainer、network、volume、scope済みimageだけに限定する。
  • 台帳更新: 完全破棄の成功後にだけport slotを解放する。

移行の完全性と二つのworktreeの独立性を別々に検証します

ある検証端末では、空き容量が数GB程度まで減った状態から、再生成可能なcacheの回収、必要データの移送、旧ランタイムの削除を経て、空き容量が300GBを超える状態まで回復しました。これは重複データ量と端末構成に依存する観測値であり、OrbStackへ替えれば同じ容量が必ず空くという意味ではありません。前後のdisk usageを記録し、何を削除した結果かを分けて評価します。

移行検証では、棚卸ししたvolumeの欠落が0であることと、主要volumeのファイル数・logical bytesが移行前後で一致することを確認しました。imageは必要なものが起動または再buildできること、custom builderは新しいcontext上でinspectと試験buildが通ることを確認します。

分離検証では、二つの一時worktreeを同時にprepareして起動します。project ID、host port block、network、volume、image tagが異なることをComposeの最終設定とDocker labelから確認します。片方のDBにだけ作ったtableとdataが他方に存在しないことも、接続先の文字列ではなく実データで確かめます。

最後に、片方をdownして再起動した時にdataが残ること、destroy --yes後に一致するリソースだけが消えること、slotが解放されることを確認します。context guard、raw compose guard、shell構文、Compose config、statusで得たURLのsmoke testまで通して、設計上の分離と操作上の分離を両方確認します。

  • 移行完全性: volume欠落0、主要volumeのファイル数・logical bytes一致。
  • 同時起動: 異なるproject、host port block、network、volume、image tag。
  • データ独立性: Aだけに作ったtableとdataがBには存在しない。
  • ライフサイクル: down後は保持、destroy後は完全削除、成功後にslot解放。
  • guard: context、raw compose、shell、Compose config、status URLを確認。

失敗例は、どの境界が不足していたかで整理します

volumeを扱うだけのmigration commandでも、補助コンテナのlocal-only imageを自動でpullしようとして失敗することがあります。移行元にしかないimageを前提にせず、利用可能な小さなimageを明示するか、移行元と移行先のvolume間をstream copyするfallbackを用意します。

Compose project名だけを変えても、container_name、host port、external volume、固定image tagが残れば不十分です。反対に、一つのコンテナを複数worktreeで共有すると、compose upによる再作成時にbind mountだけが別worktreeへ切り替わる危険があります。projectは単なる接頭辞ではなく、実行時リソース全体のscopeとして扱います。

共有DBやstorageは、内部namespaceがないままではデータ競合を起こします。固定ポートを前提にしたREADMEやE2Eテストも、別worktreeでは誤った環境へ接続します。URLとportはstatusから動的に取得し、テスト対象を明示します。global pruneは別agentや別worktreeのcacheと実行環境を壊し得るため、日常的な回復手段にしません。

  • 失敗したmigrationが暗黙のpullをしていないかを確認し、stream copyへ切り替えられるようにする。
  • project名だけでなく、container、port、volume、image、envまでscopeする。
  • 共有する可変サービスは、内部namespaceと削除単位まで検証する。
  • 固定URLではなく、statusから取得した接続先をdocsとtestsへ渡す。

環境分離が解決しない競合と、増えるコストがあります

worktreeを分離しても、初回のimage pullとbuildは必要です。full stackを複数起動すればメモリも増えます。日常作業では必要なserviceだけをupできるようにし、内部portとhost公開portを区別します。コンテナ間通信はservice名と内部port、ブラウザやホスト側テストは割り当て済みhost portを使います。

Gitのmerge conflict、同じ番号のmigration追加、外部APIの共有sandbox、同じbranchへのpush競合はDockerの名前空間では解決しません。コードと外部システムには、それぞれ別の調停規則が必要です。環境分離を『並行開発の全問題を解く仕組み』として扱わないことが重要です。

ポート台帳にも障害はあります。書き込み中断、古いslot、削除済みworktreeの残骸を検出するdoctorと、lockのtimeout、所有者情報、回復手順が必要です。また、credentialやproductionに近いdataをworktreeごとに複製すると漏えい面が広がるため、匿名のfixtureや最小データを既定にします。

  • 初回build・pullと、full stack多重起動のメモリ消費は残る。
  • 必要なserviceだけを起動し、内部portとhost portを混同しない。
  • Git merge、migration番号、外部sandboxの競合は別のルールで解く。
  • port registryの排他、stale entry検出、復旧手順を用意する。

適用時は『runtimeは一つ、実行環境と可変データはworktreeごと』で確認します

中心となる判断は、runtimeは一つ、実行環境はworktreeごと、可変データもworktreeごと、です。共有範囲を広げる場合は、速さや省メモリだけで決めず、名前空間、権限、migration、削除単位を説明できることを条件にします。

実装の完成条件は、wrapperがあることではありません。二つのworktreeを同時に動かし、片方のup、migration、down、destroyがもう片方のコンテナ、port、volume、dataへ影響しないことを観測できて初めて分離が成立します。

  • Docker contextとBuildx builderが、採用した一つのruntimeを指している。
  • worktree IDからproject、network、volume、image tag、port blockを一意に解決できる。
  • 生のCompose起動と誤contextをguardで止め、URLはstatusから取得する。
  • DB、storage、mail、queue、migrationの可変状態がworktree間で混ざらない。
  • downはdataを保持し、destroy --yesだけが厳密一致した専用リソースを削除する。
  • 二つの一時worktreeによる同時起動、data独立性、保持、完全破棄、slot解放を検証する。

製品固有の挙動は公式資料で確認します

以下は2026年7月19日時点で確認した公式一次資料です。OrbStackの自動移行、context、CLIの扱いと、Docker Composeのproject名、service定義、Buildx builder、pruneの対象は更新される可能性があるため、実施前に現行資料を確認します。volume間stream copy、file count・logical bytes照合、worktree用port台帳は、本稿の運用設計として追加した検証手順です。

Technical documents

技術文書を増やしていきます。

AI、クラウド、業務アプリ開発、要件定義、運用設計に関する考え方を、今後も文書として整理します。

技術文書一覧へ