複数のAIエージェントを同じプロジェクトディレクトリのdevcontainerで並行起動するPowerShellスクリプト
タイトルからはピンとこないかもしれませんが、ひとつのプルジェクトディレクトリに対してdevcontainerを使って2つのAIエージェントコンテナを起動するという環境を構築したので、誰かの役に立てばぐらいのほぼ個人的メモです
今回使用したのは、Claude CodeとCodex AIですが、他のエージェントを使う際にも参考ぐらいにはなると思います
PowerShell用なので使っている環境は当然Windowsですが、Macでも同様の構成は組めるはずです(本記事中で紹介しているディレクトリ構成はWindowsベースのものです)
まず、なぜdevcontainerを使っているかというところから軽く触れておきます
AIエージェントを使っている際のトラブルとして、ルールを無視してrmとかでファイルを消されてしまう話をよく聞きます
自分も別のアプリケーションでファイルを消されたことがあり、プロジェクト内はバックアップするとかgit連携することで諦めがつくとしても、PC内の予期せぬエリアにまで手を出されると困るので、被害を最小限にするためにコンテナの中に閉じ込めて使いたいと思ったからです
今回紹介する環境の制限事項としては、検証用の環境は基本的にはコンテナ外に用意しています
これはコンテナ内である程度実行できた方が便利ではあるものの、1台のマシンの中に複数のプロジェクトの複数の環境が立ち上がるとポートの競合が起きやすいためこのようにしました
composeを使ったdocker in dockerみたいなことも考えましたが、一度運用してみて手間がかかるようであれば環境を作り直そうと思ったまま使い続けています
また前提として、今回の方法ではプロジェクト x Agentごとにコンテナイメージがビルドされ、コンテナが起動します
もちろん1つのコンテナで全プロジェクトを包括的に管理し、プロジェクトごとの設定を分けたりmemoryを分けたりということもできるのですが、自分が扱っているプロジェクトは複数クライアントのものが混在しており万が一にも内容が混ざったりしてはいけないため敢えてこのような構成としています
たくさんコンテナがあがることでリソース的に無駄では?と思ったのでClaudeに聞いてみると
リソースの無駄感はありますが、クライアント間の情報混在リスクを考えると分離は必須という判断は正しいです。実態としてもClaude Codeコンテナは積極的にCPUを使うわけではなく、作業していない間はほぼアイドル状態なので、複数起動によるリソース消費は思ったより軽微なはずです。
とのことでした
最終的に実現したディレクトリ構成はこのような感じです
C:\scripts\
├── claude
│ ├── Start-ClaudeCode.ps1 # <-- のちほど紹介する参考記事のStart-ClaudeEnvironment.ps1相当
│ └── .devcontainer\
│ ├── devcontainer.json
│ ├── Dockerfile
│ └── init-firewall.sh
└── codex
├── Start-Codex.ps1
└── .devcontainer\
├── devcontainer.json
├── Dockerfile
└── init-firewall.sh
C:\workspace\
├── project-a\
└── project-b\
ということで、もともとこちらの記事を参考にdevcontainerでclaude codeを使っていました
devcontainerでClaude Codeを動かす(Windows)
基本的な環境まわりの設定、たとえばpodmanのセットアップ等はここでは割愛しますので、上記記事をご覧ください
本記事内および公開ソース内ではpodmanのVM名称はclaudeVMではなくcodingAgentVMとしています
実際に使用しているソースをこちらに公開しておきます
ライセンスに関しては、参考にした元記事のままの部分やClaude Code公式が公開している内容も多いため、こちらで勝手に付けるわけにはいかずNo license設定です
AI Agent DevContainer 起動スクリプト (PowerShell用)
Codex CLIをdevcontainerで使う
いったん本題からそれますが、上記記事のClaude Codeの方法を複製して改変することでCodex CLIをdevcontainer化しました
なんとなくスクリプトを複製してCodex用のスクリプトを作成し、このあたりを書き換えれば良いだろうと作ったものなので、不要な記述等も残っているかもしれません
Codexを起動するにあたりいくつかハマったところもあるので、ここでご紹介しておきたいと思います
Codex CLIの認証について
ブラウザを使った認証を最初に試したのですが、Claude Codeとは違いcallbackを受けて認証用のjsonをセットするというフローのようです
コールバックはlocalhost:1455に返ってくるのですが、プロジェクトごとにコンテナを起動しようとするとこれが厄介です
全プロジェクトで同じポートをフォワードすることもできず手動で都度設定するのも大変なので良い解決法はないかとChatGPTに聞くと、このようなケースではデバイス認証を使うべきと言われたのでおとなしくそのとおりにしました
Codex用のiptables設定
ChatGPTにCodex CLIを使うにあたって許可が必要なドメインを聞き設定しようとしたのですが、元々の方法(Anthoropic Claude Codeの公式のスクリプト?)だとIPベースに変換しているので『👎 CDNで即死』(表現そのまま)と言われ、他の方法で実装してもらいました
ところが、Codex CLIのデバイス認証の情報が取得できず、さらに書き換えてもらったのが公開ソースに入っている内容です
が、、いや、、、ポートだけになっててセキュリティガバガバじゃないすか…
とはいえ私個人の環境ではそこまで厳しくする必要もないので、とりあえずそのまま使うことにしました
参考記事のスクリプトをそのまま使うと発生する問題と解決策
これまではClaude CodeとCodex CLIのどちらを使うかはプロジェクトごとに分けていましたが、同じプロジェクトで両方使いたいと思い立って起動スクリプトを修正することにしました
記事で紹介されていたスクリプトを使って2つのエージェントを走らせると何が問題かというと、以下の2点です
- buildされるコンテナイメージがプロジェクトディレクトリをベースとした単位で作られるため、両Agentのものが同一イメージとみなされる
- スクリプト上でコンテナIDを探す部分でプロジェクトディレクトリを使っているため、どちらも同じコンテナIDが返される
ついでにこれまではそれぞれのプロジェクトディレクトリ内にスクリプト等の一式を置いて使っていましたが、ここも非効率なので変更します
- プロジェクトごとではなく共通の起動スクリプトとして作り替える
また、元々の状態で改変していた部分もあります
- .claudeや.codexといったディレクトリをホストマシンからアクセスできるディレクトリに設置してbindするように変更
この4点について解説をしていきたいと思います
Agent固有設定ディレクトリのバインド
これは、認証情報だったりメモリだったりを格納するディレクトリをコンテナ内ではなくホスト側に置いて永続化したいと思ったため改変していた部分です
devcontainer.jsonでマウントされている設定のディレクトリを以下のように書き換えます(参考記事との比較がしやすいようにClaude Codeでの例です)
"source=${localWorkspaceFolder}/.claude,target=/home/node/.claude,type=bind,consistency=cached"
localWorkspaceFolderはdevcontainer.json上で扱える変数となっており、今回のケースではプロジェクトフォルダが設定されます
もしすでに起動中のコンテナに適用したい場合は、コンテナ内から現在の/home/node/.claudeをコピーして持ってきておくことを忘れないようにしましょう
また、devcontainer.jsonの変更なのでコンテナイメージの再buildが必要となります
起動スクリプトの共通化
これに関してはスクリプト内で現在のディレクトリを使っている部分がプロジェクトディレクトリになれば良いわけなので、
- プロジェクトディレクトリを指定できる(絶対パスだけでなく相対パスも対応)
- プロジェクトディレクトリの引数が省略された場合はカレントディレクトリをプロジェクトディレクトリとして使う
という要件でClaudeに書き換えてもらいました
起動スクリプト(拡張子.ps1のもの)の $ProjectPath の記述がこれにあたります
このほか、.devcontainerディレクトリを置く場所もパラメータ化していますが、今回の内容とは直接関係ないため説明は割愛します
複数Agentを同じプロジェクトディレクトリで扱う
ようやくこちらが本題です
ChatGPTにもClaudeにもいろいろ聞きながら改変しましたが、思ったより試行錯誤が多く大変でした
まずはコンテナイメージ名ですが、起動スクリプトからのdevcontainer up時にイメージのタグ指定しようと思いましたがそのような起動オプションはなさそうです
devcontainer.jsonに変数を渡して設定というのも考えましたが、環境変数を介するような構造となりシンプルではありません
通常、devcontainer up(build)時には自動的に vsc-{プロジェクトフォルダ名}-{自動設定されるハッシュ} という名前が付けられます
イメージタグの視認性的には微妙ですが、このハッシュ部分がClaude CodeとCodex CLIで別々のものになってくれれば解決です
自動設定されるハッシュのベースとなっているのがなにかを調べてみると、up時に --id-label という引数を付けてあげれば良さそうです
ということで --id-label に imagelabel=claude のように設定してみたのですが 今度はスクリプト内のコンテナIDを取得するところでエラーになりました
どうやら、 --id-label があると 元のスクリプトにある devcontainer.local_folder が設定されなくなるようです
podman ps --format "{{.Labels}}"
で設定されているラベルを確認できます
そこで、元々 devcontainer.local_folder で取得していたプロジェクトディレクトリ部分も含めて --id-label に設定することにしました
起動スクリプト内の $BuildImageIdLabel あたりがここに関する記述です
これでコンテナIDを探す部分についても同時に解決することができました
手動でリビルドする方法
試行錯誤する中で何度か手動でリビルドすることがありましたので、普段は使わないと思いますがメモとして残しておきます
仮想マシンが起動している状態で、既存コンテナを停止、削除し以下のコマンドを実行します
--id-label の部分がスクリプト内の $BuildImageIdLabel にあたるところで、 project-a-claude のようになります
devcontainer up --workspace-folder . --config ..\..\scripts\codex\.devcontainer\devcontainer.json --id-label imagelabel={project-directory-name}-{agent-name} --docker-path podman --build-no-cache