Claude Media
Claude CodeをProxmoxのLXCコンテナで動かす手順

Claude CodeをProxmoxのLXCコンテナで動かす手順

ProxmoxのLXCコンテナにClaude Codeは入ります。メモリ4GB以上のunprivileged CTを作り、公式の1行で導入し、ブラウザ無しでログインするまでの手順です。

Proxmox LXCでClaude Codeを動かすとは

結論から言うと、ProxmoxのLXCコンテナでClaude Codeは動きます。Debian・Ubuntu・Alpineのテンプレートが公式の対応OSに入っているためです。メモリは4GB以上を割り当て、権限は既定のunprivilegedのまま、curl -fsSL https://claude.ai/install.sh | bashで導入します。ブラウザが無いので、ログインはURLとコードの受け渡しかclaude setup-tokenで済ませます。

Proxmox VEのLXCは、ホストのカーネルを共有するOSレベルのコンテナです。仮想マシンのようにCPUやデバイスをエミュレートしないため、起動が速くメモリの無駄も少なくなります。Claude Codeを動かすだけの軽量な作業環境が欲しいとき、VMより手早く用意できる選択肢です。

コンテナ内で動く実体はLXCでもDockerでも同じ「Claude Codeのネイティブバイナリ」です。違いはホストとの分離の仕方だけなので、インストール自体はLinux版のインストーラーがそのまま使えます。VMで同じバイナリが無出力で固まる報告はProxmox VMでの切り分けにあります。

ただしLXCはDockerとは別物です。Debian・Ubuntu・Alpineなどフルディストリビューションのテンプレートからコンテナを作る点が、アプリケーション単位のイメージを積むDockerと違います。この違いが、後述するインストール手順の分岐に直結します。

前提条件 — コンテナ種別とリソースを決める

Claude Codeのシステム要件は次の通りです。

  • ハードウェア: 4GB以上のRAM、x64またはARM64
  • 対応OS: Ubuntu 20.04以降、Debian 10以降、Alpine Linux 3.19以降
  • ネットワーク: インストーラーと自動更新が到達できるインターネット接続

Proxmox VEのテンプレート一覧にはDebian・Ubuntu・Alpineがすべて含まれるので、対応OSの条件はテンプレート選びの時点で満たせます。残る判断はコンテナの権限モードです。

Proxmox VEはコンテナ作成時に「unprivileged(非特権)」と「privileged(特権)」を選べます。作成時の既定はunprivilegedで、コンテナ内のroot(UID 0)はホスト側の権限のない一般ユーザーにマッピングされます。Proxmoxのwikiは、コンテナ内で権限を奪われても影響はホストの一般ユーザー相当に留まると説明しています。

Claude Codeを動かすだけなら、特権モードにする理由はありません。unprivilegedのまま進めて、Dockerを入れ子にするなど必要が出た時点で個別の機能(後述のつまずき節)を足します。

メモリには2つの数字があります。公式のシステム要件は4GB以上で、これは実行時の要件です。インストール自体に要るのは約512MBの空きメモリで、不足するとKilledで止まります。pct createの--memoryの既定は512なので、何も指定せずに作ると要件の1/8です。

CT作成時は4GB以上を割り当てます。既に作った後で足りないと分かったら、pct set <CTID> --memory 4096で増やしてから再試行できます。

手順1 — LXCコンテナを作成する

まずテンプレートを取得します。Proxmox VEのGUIからでもコマンドラインからでも可能です。ファイル名のバージョン部分は取得時期で変わるので、pveam availableの出力にある名前に合わせます。

# テンプレート一覧を更新して確認
pveam update
pveam available --section system
 
# 使いたいテンプレートをローカルストレージへダウンロード
pveam download local debian-12-standard_12.7-1_amd64.tar.zst

テンプレートを取得したら、pct create でコンテナを作ります。

pct create 200 local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \
  --hostname claude-code \
  --cores 2 \
  --memory 4096 \
  --swap 512 \
  --net0 name=eth0,bridge=vmbr0,ip=dhcp \
  --rootfs local-lvm:8 \
  --unprivileged 1

CTID(この例では200)はクラスタ内で一意な番号です。--rootfsのディスクサイズは8GBを例にしています。作成後はpct start 200で起動し、pct enter 200でコンテナのシェルに入れます。

pct createが生成するコンテナ設定ファイルの例

pct createの実行結果は/etc/pve/lxc/<CTID>.confに次のような形式で保存されます。

ostype: debian
arch: amd64
hostname: claude-code
memory: 4096
swap: 512
net0: name=eth0,bridge=vmbr0,ip=dhcp
rootfs: local-lvm:vm-200-disk-0,size=8G
unprivileged: 1

このファイルはviやnanoで直接編集もできますが、反映漏れを避けるならpctコマンドかGUI経由で変更するほうが確実です。

手順2 — テンプレート別にClaude Codeをインストールする

pct enterでコンテナのシェルに入ったら、テンプレートの種類で進め方が分かれます。

くらべる

テンプレート別のインストール

標準の1行で入る

Debian 12 / Ubuntu 22.04以降

標準のインストールコマンドがそのまま使えます。公式のaptリポジトリ手順は、まっさらなDebian・Ubuntuにはcurlとgpgが入っていない場合があるとしています。curlがcommand not foundになったら、先にapt install -y curlを実行します。

依存を先に入れる

Alpine 3.19以降

先に依存パッケージを入れてから、インストールに進みます。

DebianとUbuntuでは、次の1行でインストールします。

curl -fsSL https://claude.ai/install.sh | bash

Alpineテンプレートは軽量な分、bashやcurlが最初から入っていません。Alpineや他のmusl/uClibc系ディストリビューションでは、これらが無いとインストールコマンド自体がnot foundエラーで失敗します。先に依存パッケージを入れます。

apk add bash curl libgcc libstdc++ ripgrep

ripgrepはAlpineのcommunityリポジトリに入っています。apkがパッケージを見つけられない場合は、使っているAlpineのバージョンに合わせてリポジトリを追加してください。

echo "https://dl-cdn.alpinelinux.org/alpine/v3.22/community" >> /etc/apk/repositories  # v3.22は例。使用中のAlpineのバージョンに置き換える
apk update
apk add ripgrep

依存を入れたら、USE_BUILTIN_RIPGREPを0にして、同梱のripgrepではなくシステムのripgrepを使わせます。設定場所はsettings.jsonのenvブロックか、シェルの環境変数のどちらでもかまいません。

{
  "env": {
    "USE_BUILTIN_RIPGREP": "0"
  }
}

効いたかどうかはclaude doctorのSearch行で分かります。システムのripgrepのパスが出ていれば成功で、OK (bundled)のままなら設定が反映されていません。

Alpineにはbashとcurlを用意する代わりの経路もあります。Claude Codeはapt・dnf・apkの署名付きリポジトリも配布しており、公式のapkタブはwgetで鍵を取得します。

wget -O /etc/apk/keys/claude-code.rsa.pub \
  https://downloads.claude.ai/keys/claude-code.rsa.pub
echo "https://downloads.claude.ai/claude-code/apk/stable" >> /etc/apk/repositories
apk add claude-code

リポジトリのチャネルはstableとlatestの2つです。stableはおよそ1週間遅れで、重大な退行があった版を飛ばします。latestは出た版をすぐ配ります。

パッケージ経由のインストールはClaude Code自身の自動更新の対象外で、更新はapk update && apk upgrade claude-codeで行います。バージョンを勝手に進めたくないLXCには向いた性質です。Debian・Ubuntuでも更新を手動で管理したい場合は、aptリポジトリ経由で入れる方法があります。

ネイティブインストーラーで入れた場合はバックグラウンドで自動更新されます。止めたいときはsettings.jsonのenvでDISABLE_AUTOUPDATERを"1"にします。更新は続けつつ追従を遅らせたいなら、autoUpdatesChannelを"stable"にします。

公式のapkタブには依存パッケージの追加手順が載っていないので、入れた後は手順3の確認と検索機能の動作で依存の不足がないかを見ます。

同じテンプレートを複数のCTに繰り返し展開する運用なら、プラグインやマーケットプレイスを事前に焼き込んでおく方法がClaude Codeプラグインをコンテナへ事前展開する手順にあります。LXCテンプレートのカスタマイズと組み合わせれば、pct cloneで複製するたびに手作業が減ります。

手順3 — インストールを確認する

インストールが終わったら、まずバージョンを確認します。

claude --version

v2.1.289では2.1.289 (Claude Code)と表示されます。バージョン番号が出れば成功です。command not foundになる場合は、インストーラーが~/.local/bin/claudeに置くため、PATHにそのディレクトリがないのが原因です。次のコマンドで確かめます。

echo $PATH | tr ':' '\n' | grep -Fx "$HOME/.local/bin"

何も出なければ、~/.bashrcにexport PATH="$HOME/.local/bin:$PATH"を追記してシェルを開き直します。

もう一段詳しい診断が要るときはclaude doctorを使います。

claude doctor

claude doctorはセッションを開始せず、インストールの状態と設定ファイルの検証エラーを読み取り専用で表示します。修正まで行う場合は、セッション内の/doctorを使います。v2.1.214より前は、シェル設定ファイルのパスにディレクトリがあるとclaude doctorとclaude updateが固まりました。古い版を入れてしまった場合は、インストールコマンドを実行し直して更新します。

手順4 — ヘッドレス環境でログインを完了する

LXCコンテナにはブラウザがありません。ブラウザが無い環境でのログインには、2つの道があります。

くらべる

ログインの2つの道

対話で使う人向け

コンテナ内でログイン

手元にブラウザのあるPCやスマホがあり、対話で使うCTに向きます。

無人で動かす人向け

手元でトークンを発行

ログインの対話を挟めない、無人で動かすCTに向きます。

コンテナ内で対話ログインする

通常のOAuthフローは、ログイン後にブラウザがlocalhostのコールバックサーバーへ自動で戻ります。コンテナの中ではブラウザがコールバックサーバーに届かないので、戻る代わりにログインコードが画面に出ます。公式は、WSL2・SSH経由のリモート・コンテナで起きる現象としています。

手順

コンテナ内ログインの流れ

  1. 1

    claudeを起動する

    コンテナのシェルでclaudeを実行します。ブラウザが開かないと分かったら、cキーでログインURLをクリップボードにコピーできます。

  2. 2

    URLを手元のブラウザで開く

    コンテナの外にあるPCやスマホのブラウザで、表示されたURLを開いてサインインします。

  3. 3

    コードを貼り付ける

    ブラウザに出たログインコードをコピーし、コンテナ側のPaste code here if promptedのプロンプトへ貼り付けます。

claude auth loginでログインだけを単独で実行する経路もあります。v2.1.289でclaude auth login --helpを実行すると、次の出力になります。

claude auth login --help
Usage: claude auth login [options]
 
Sign in to your Anthropic account
 
Options:
  --claudeai       Use Claude subscription (default)
  --console        Use Anthropic Console (API usage billing) instead of Claude
                   subscription
  --email <email>  Pre-populate email address on the login page
  -h, --help       Display help for command
  --sso            Force SSO login flow

既定はClaudeのサブスクリプションでのサインインです。APIの従量課金で使うなら--console、SSOを強制するなら--ssoを付けます。GitHub Codespacesのような使い捨てのクラウド開発環境でも同じ発想のヘッドレスログイン手順が使われているので、見比べると理解が早いです。

無人運用ではトークンを渡す

ログインの対話を挟めないCTには、claude setup-tokenで作る長期トークンが向きます。このコマンドは通常のログインと同じブラウザ認可を開くので、ブラウザのある手元のマシンで実行します。トークンは端末に表示されるだけで、どこにも保存されません。

コンテナ側では、表示されたトークンを環境変数に設定します。

export CLAUDE_CODE_OAUTH_TOKEN=your-token

このトークンはPro・Max・Team・Enterpriseのいずれかのプランが必要です。使えるのはモデルへのリクエストだけで、Remote Controlのセッション確立とclaude.aiコネクタの取得はできません。ローカルに設定したMCPサーバーは従来どおり動きます。

ログイン情報の保存先とCTの複製

Linuxでは、ログイン情報は~/.claude/.credentials.jsonにファイルモード0600で保存されます。コンテナのrootfsの中にあるファイルなので、ログイン後にpct cloneでCTを複製したり、vzdumpでバックアップしたりすると、ログイン情報も一緒に入ります。

複製して配る用のテンプレートCTは、Claude Codeを入れるところまでにして、ログインは複製先のCTごとに行います。

よくあるつまずき

インストールがKilledで止まる。Linuxのメモリ不足でOOM Killerがインストールプロセスを終了させたサインです。公式のインストーラーは、次のメッセージを出して終了コード137で止まります。

Installation was killed before it could finish (exit code 137). This usually means the system ran out of memory.
Claude Code needs roughly 512MB of free memory to install.

公式の対処はスワップを足すか、他のプロセスを閉じて空きを作ることです。LXCならホスト側からCTに直接メモリを足せるので、pct set 200 --memory 4096のように増やしてpct reboot 200する手が手早い方法です。

libstdc++.so.6やlibgcc_s.so.1が見つからないと言われる。glibcベースのシステムにmusl向けの互換パッケージが入っていると、インストーラーがmuslと誤判定してmusl向けのバイナリを選ぶことがあります。ldd --versionでGNU libcと出ればglibc、muslと出ればAlpineなどのmusl系です。glibcなのに誤ってmusl版が入ったなら、インストールを消してやり直します。Alpineテンプレートで本当にmuslだった場合は、手順2のapk add libgcc libstdc++ ripgrepで解決します。

rootのまま/直下でインストールすると固まる。公式が挙げているのはDockerコンテナの例で、rootで/からcurl | bashを実行するとインストーラーがファイルシステム全体を走査してハングします。LXCでも同じ条件を作らないよう、実行前にpwdを確かめ、/ならcd /rootしてからインストールします。

コンテナの中でDockerも動かしたい。Proxmoxのfeaturesには、keyctlとnestingの2つがあります。どちらも既定は0(無効)です。

  • keyctlはunprivilegedコンテナ専用で、既定ではkeyctl()システムコールが「存在しないもの」として見えます。Dockerを動かすには有効にする必要があります。この設定はsystemd-networkdのための回避策でもあり、Proxmoxのマニュアルは「systemd-networkdを動かすか、Dockerを動かすかを選ぶことになる」と説明しています。
  • nestingはホストのprocfsとsysfsの中身をゲストに見せる設定です。systemdがサービスを分離するのにも使われます。マニュアルは、追加のIDマッピングを加えたunprivilegedコンテナで使うのが向くとしています。

両方を有効にするコマンドは次のとおりです。

pct set 200 --features keyctl=1,nesting=1

共有ホストで動かす場合は、ホストのprocfs・sysfsが見える影響を理解したうえで使う設定です。MCP経由でコンテナを操作する構成を検討しているなら、Docker MCP ToolkitでClaude Codeからコンテナを操作する設定が具体的な接続手順を扱っています。

ホストのディレクトリをbind mountしたらパーミッションエラーが出る。Proxmoxのwikiは、unprivilegedコンテナでbind mountを使うと、UIDマッピングが原因の権限問題に当たることがあるとしています。コンテナ内のroot(UID 0)がホスト側の権限のない一般ユーザーに変換されるため、ホスト側のファイル所有者と噛み合わなくなります。

wikiはbind mountの元を/mnt/bindmountsのような専用の階層に置き、システムディレクトリは決してマウントしないよう勧めています。設定は/etc/pve/lxc/<CTID>.confに次の形で入ります。

mp0: /mnt/bindmounts/shared,mp=/shared

認証情報をホストから遮断しつつ安全にマウントを設計する考え方はClaude CodeをDevContainerで安全に動かす完全実装でも扱っているので、LXCでの権限設計に流用できます。

まとめ

複製して配るテンプレートCTは、Claude Codeを入れたところで止めます。ログイン情報はrootfsに残るため、ログインは複製先のCTごとに行います。インストールが止まったときは、割り当てたメモリ(4GB以上)と、muslとglibcの取り違えを先に疑います。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/setup
  2. code.claude.com/docs/en/troubleshoot-install
  3. code.claude.com/docs/en/troubleshooting
  4. code.claude.com/docs/en/authentication
  5. code.claude.com/docs/en/cli-reference
  6. pve.proxmox.com/wiki/Linux_Container
  7. pve.proxmox.com/pve-docs/pct.conf.5.html
この記事を共有:XはてブLinkedIn