Claude Codeプラグインをコンテナへ事前展開する手順
CI・コンテナ環境でマーケットプレイスやプラグインをランタイムでクローンせず起動する、CLAUDE_CODE_PLUGIN_SEED_DIRの設定手順とふるまいをまとめます。
コンテナ起動のたびにクローンしたくない理由
CI環境やコンテナイメージでClaude Codeを動かすとき、起動のたびにマーケットプレイスをgit cloneし、プラグインをインストールするのは無駄が多い作業です。同じリポジトリをジョブごとに取得し直すので、ビルド時間が伸びます。ネットワークが制限された環境では、クローンそのものが失敗の原因になります。
Claude Codeには、この初期化をビルド時に済ませる仕組みがあります。あらかじめ用意したディレクトリを環境変数 CLAUDE_CODE_PLUGIN_SEED_DIR に指定すると、起動時にシード内のマーケットプレイスが登録され、プラグインのキャッシュがクローンなしで読み込まれます。
必要なのは、イメージをビルドできる環境と、ビルド時にClaude Codeを実行できることです。Dockerに限らず、ProxmoxのLXCコンテナでClaude Codeを動かす手順のようにテンプレートから複製する構成でも、同じシードを使えます。
シードの作り方とディレクトリ構造
シードは ~/.claude/plugins と同じ構造のディレクトリです。ビルド時に CLAUDE_CODE_PLUGIN_CACHE_DIR をシードの置き場所に向けて、通常どおりマーケットプレイスの追加とプラグインのインストールを実行します。
CLAUDE_CODE_PLUGIN_CACHE_DIR=/opt/claude-seed claude plugin marketplace add your-org/your-marketplace
CLAUDE_CODE_PLUGIN_CACHE_DIR=/opt/claude-seed claude plugin install code-formatter@your-marketplace名前は「キャッシュ」ですが、この変数が変えるのはプラグイン関連ディレクトリ全体の親です。marketplaces/ とプラグインのキャッシュはどちらもこの配下に作られます。生成されるシードの中身は次のとおりです。
/opt/claude-seed/
known_marketplaces.json
marketplaces/<name>/...
cache/<marketplace>/<plugin>/<version>/...手元で作ってみると見えること
使い捨ての HOME と CLAUDE_CONFIG_DIR に差し替えた作業ディレクトリで、ローカルの小さなマーケットプレイス(プラグイン1つ)を使い、v2.1.287で同じ2コマンドを実行しました。どちらも成功し、シード直下には known_marketplaces.json と marketplaces/ に加えて installed_plugins.json ができました。プラグインは cache/demo-mkt/hello/1.0.0 に入っています。
この2コマンドは、シードとは別に、CLAUDE_CONFIG_DIR 側の settings.json にも extraKnownMarketplaces と enabledPlugins を書き込みました。つまり有効化の設定はシードの中に入りません。シード内のプラグインはそれだけでは有効にならないので、ランタイム側で enabledPlugins を用意します。
一方、ローカルディレクトリを指定して追加したマーケットプレイスは、marketplaces/ にコピーされませんでした。known_marketplaces.json には "source": "directory" と元の絶対パスだけが記録されます。下の例ではパスを仮名にし、lastUpdated などは省略しています。
{
"demo-mkt": {
"source": { "source": "directory", "path": "/path/to/mkt" },
"installLocation": "/path/to/mkt"
}
}ビルド後に元のディレクトリが無くなるイメージでは、この形のシードは自己完結しません。marketplaces/<name>/ までシードに入れたいなら、your-org/your-marketplace のようなGitHubやgitの指定で追加します。なお file:// のURLを渡すと、Invalid marketplace source format. Try: owner/repo, https://..., or ./path と拒否されました。
Dockerfileに組み込む
Dockerfileでは、シードを作る RUN の先頭に CLAUDE_CODE_PLUGIN_CACHE_DIR を前置し、最後に ENV で CLAUDE_CODE_PLUGIN_SEED_DIR を設定します。ビルド用の CLAUDE_CODE_PLUGIN_CACHE_DIR を ENV で宣言すると、実行時のプラグインの置き場所も変わってしまうため、RUN への前置にとどめます。
# ビルド: シードを /opt/claude-seed に作る
RUN CLAUDE_CODE_PLUGIN_CACHE_DIR=/opt/claude-seed \
claude plugin marketplace add your-org/your-marketplace && \
CLAUDE_CODE_PLUGIN_CACHE_DIR=/opt/claude-seed \
claude plugin install code-formatter@your-marketplace
# ランタイム: シードを読ませる
ENV CLAUDE_CODE_PLUGIN_SEED_DIR=/opt/claude-seedシードは、ビルドしたパスとは別の場所にマウントしても使えます。この性質から、ビルド用のステージで作ったシードを最終イメージへ運ぶ構成も成り立つと考えられます。
プライベートリポジトリのマーケットプレイスを入れるCIでは、先にgitの認証が要ります。GitHub Actionsなら、読み取り権限のあるトークンを GH_TOKEN に入れて gh auth setup-git を実行します。既定のワークフロートークンは自分のリポジトリにしか届かないので、別リポジトリのマーケットプレイスにはPATかアプリトークンが必要です。
ランタイムで読ませて有効化する
シードをランタイムで使うまで
- 1
環境変数でシードを指す
コンテナの環境に
CLAUDE_CODE_PLUGIN_SEED_DIR=/opt/claude-seedを設定します。複数のシードはUnixなら:、Windowsなら;で区切り、同じマーケットプレイスやキャッシュは先に見つかったシードが使われます。 - 2
enabledPlugins で有効化する
シードに入れただけではプラグインは有効になりません。使いたいプラグインごとに
enabledPluginsを、管理設定かリポジトリの.claude/settings.jsonに書きます。 - 3
claude -p で読み込み元を確かめる
イメージの中で
claude -pを--output-format stream-json --verbose付きで実行し、initイベントのpluginsを見ます。読み込まれたプラグインのpathが/opt/claude-seed/cache/...のようにシード配下なら、クローンなしで読めています。
enabledPluginsの書き方と、ユーザーの無効化との関係
enabledPlugins は プラグイン名@マーケットプレイス名 をキーにします。管理設定で書くと、次のようになります。
{
"enabledPlugins": {
"code-formatter@your-marketplace": true
}
}管理設定は他のどのスコープよりも優先されます。そのため、管理設定で有効にしたプラグインは、ユーザーが自分のスコープで無効にしても読み込まれ続けます。リポジトリの .claude/settings.json で有効にした場合も、プロジェクト設定がユーザー設定より優先されるので、~/.claude/settings.json の false は上書きされます。手元だけ止めるなら、.claude/settings.local.json に false を書きます。
管理設定の配信方法には、サーバー管理設定・MDM・管理設定ファイルの3通りがあります。通常は優先順位の高い1つだけが適用されます。サーバー管理設定が無関係なキーを1つでも配信していれば、同じマシンのMDMや管理設定ファイルにあるプラグインのキーは無視されます。ただしサーバー管理設定が優先される場合でも、全ての配信元から読まれるキーは例外です。managedSourcesBehavior を "merge" にすると、すべての配信元が適用されます。
共通のベースイメージにチーム共通のシードを置き、上のレイヤーでチーム固有のシードを追加する2段構成も組めます。先勝ちなので、優先させたいチーム固有のシードを CLAUDE_CODE_PLUGIN_SEED_DIR の先頭に置きます。
読み取り専用シードの4つのふるまい
シードのマーケットプレイスに適用される規則
書き込まれない
シードへの書き込みは一切起きず、シード由来のマーケットプレイスは
autoUpdateが強制的にオフです。シードが優先される
起動のたびに、シードで宣言された同名のマーケットプレイスがユーザー側の登録を上書きします。使いたくないプラグインは、マーケットプレイスを消さず
claude plugin disableで止めます。更新と削除は失敗する
claude plugin marketplace update <name>と、--scopeなしのremoveは、シードのディレクトリ名を示すメッセージで失敗します。ポリシーが適用される
許可リスト・拒否リストは、シードのマーケットプレイスが記録している取得元にも適用されます。シードの作成元を許可しておく必要があります。
読み取り専用で優先されるので、コンテナを作り直しても同じ構成に揃います。
許可リストがあるとき、シードの作成元をどう書くか
管理設定の strictKnownMarketplaces で許可リストを置いている組織では、シードのマーケットプレイスも、記録された取得元を照合されます。照合は厳密で、github と git の指定では、リポジトリまたはURL、ref、path がすべて一致するか、双方に無い必要があります。
{
"strictKnownMarketplaces": [
{ "source": "github", "repo": "your-org/your-marketplace" }
]
}この許可リストは、your-org/your-marketplace を github の指定で追加したシードには通ります。同じリポジトリをクローンする git のURLで作ったシードには通りません。末尾のスラッシュ、.git の有無、https:// と ssh:// の違いも別の値として扱われます。同じマーケットプレイスを複数のURLで取得できる場合は、hostPattern での許可が向いています。シードを作るときの取得元の書き方を、許可リストの書き方に揃えておくのが近道です。
オフライン・エアギャップ環境で足す設定
外向きのgit接続が無い環境では、シードだけでは足りない場面があります。シードに directory や file のマーケットプレイスの取得元を組み合わせ、共有マウントから読ませる構成があります。file は marketplace.json への絶対パスを指定する取得元で、file:// のURLではありません。完全オフラインのデプロイでは、gitのクローンではなくシードを使います。あわせて CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 を設定すると、プラグインの自動更新も止まります。
共有マウントを使う場合、許可リストでは { "source": "directory", "path": "/opt/marketplace/plugins" } のように絶対パスで書きます。パスをまとめて許可するなら pathPattern が使えます。^/opt/approved/ のように先頭を ^ で固定しないと、パスのどこに一致しても通ってしまう点に注意が必要です。
シードにはもう1つ利点があります。gitホストのアカウントを持たない利用者にも、プラグインを渡せることです。
マーケットプレイスの更新が到達できないリモートで失敗するだけなら、CLAUDE_CODE_PLUGIN_KEEP_MARKETPLACE_ON_FAILURE=1 で再クローンを省き、既存のチェックアウトを使い続けられます。クローンのタイムアウトは CLAUDE_CODE_PLUGIN_GIT_TIMEOUT_MS(既定120000ms)で延ばせます。
つまずきを症状から引く
| 症状 | 原因と対処 |
|---|---|
| シードに入れたのにプラグインが動かない | 原因と対処enabledPlugins が未設定の可能性があります。init イベントの plugins に出ているかを見ます |
Marketplace '<name>' is seed-managed で更新が失敗する | 原因と対処不具合ではなく読み取り専用の仕様です。ヒントには管理者への相談が示されます。シードを作るパイプラインを回して、イメージを作り直します。marketplace update を名前なしで実行した場合は、シード由来のものを飛ばして他を更新します |
| 許可リストの警告でプラグインが読み込まれない | 原因と対処シードの作成元がポリシーで許可されていません。許可リストにその取得元を足します |
| シードのプラグインが古いまま | 原因と対処自動更新が無効なので、プラグインを上げたらシードのビルドも一緒に回す運用にします |
| 複数シードで期待と違うバージョンが使われる | 原因と対処claude -p の init イベントで path を見て、どのシードから読まれたかを確かめます |
CIとローカル開発で構成を変える
シードを使う場所・使わない場所
CI・本番コンテナ
必要なマーケットプレイスだけを焼き込み、クローンなしで同じ構成を再現します。
ローカル開発
通常の /plugin marketplace add を使い、新しいプラグインをその都度試します。CLAUDE_CODE_PLUGIN_SEED_DIR の有無は環境変数1つの差なので、起動スクリプトで出し分けられます。
同じ marketplace.json をCIとローカルの両方で参照しておけば、差は主にビルド速度とオフライン耐性、そしてシード側の更新をイメージの再作成で行う点になります。
まとめ
enabledPlugins を管理設定に書けば利用者は止められず、リポジトリの設定に書けば利用者が手元だけ止められます。強制するかどうかで書き場所を選び、許可リストがある組織では、シードを作る取得元の書き方もそれに揃えます。
マーケットプレイス自体の作り方はClaude Codeプラグイン完全ガイド、シードに焼き込む前の検証はマーケットプレイスのstrict modeとvalidateにあります。