Claude Media
Claude Codeプラグインをコンテナへ事前展開する手順

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. 1

    環境変数でシードを指す

    コンテナの環境に CLAUDE_CODE_PLUGIN_SEED_DIR=/opt/claude-seed を設定します。複数のシードはUnixなら :、Windowsなら ; で区切り、同じマーケットプレイスやキャッシュは先に見つかったシードが使われます。

  2. 2

    enabledPlugins で有効化する

    シードに入れただけではプラグインは有効になりません。使いたいプラグインごとに enabledPlugins を、管理設定かリポジトリの .claude/settings.json に書きます。

  3. 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にあります。

この記事を共有:XはてブLinkedIn