本記事は Ubuntu 24.04 + VSCode Dev Containers を前提としています。
きっかけ
devcontainer をリビルドするたびに Claude Code に再ログインするのが地味にストレスでした。さらに、ホスト側で進めていた会話の続きが devcontainer 側からは見えず、「あの修正の文脈どこに残してたっけ」となりがちでした。
ゴール構成
- ホスト・devcontainer のどちらで起動しても 同じ認証情報 を使える
- ホストで途中まで進めた会話を devcontainer 側でも そのまま継続 できる
- リビルドしても再ログイン不要
Claude Code が何を見ているかを把握する
設定の前に、Claude Code が以下の 2 つを参照していることを押さえておきます。
| 種類 | 場所 | 役割 |
|---|---|---|
| 認証情報・設定 | ~/.claude.json | API キーなど |
| セッション履歴 | ~/.claude/projects/<エンコードしたパス>/ | プロジェクトごとの会話履歴 |
ポイントは、セッション履歴の格納先が プロジェクトの絶対パスをスラッシュ → ハイフンに変換した名前 になっていることです。
例えばホスト側で /home/<username>/<project_name> を開いていた場合は、
~/.claude/projects/-home-<username>-<project_name>/
の下に履歴が溜まります。
つまり、履歴を共有したいなら ホストとコンテナで「ワークスペースの絶対パス」を一致させる必要がある ということです。~/.claude だけ bind mount しても、コンテナ側のワークスペースパスがホストと違えば別フォルダ扱いになり、履歴は共有されません。
設定する 2 つのマウント
必要なマウントは性質の違う 2 種類になります。
1. .claude / .claude.json のマウント (= コンテナ側ユーザーの home に合わせる)
Claude Code は そのプロセスの $HOME/.claude を読みに行きます。devcontainer のデフォルトユーザーは vscode なので、~ は /home/vscode です。したがってターゲットも /home/vscode/.claude にします。
2. ワークスペースのマウント (= ホスト側の絶対パスに合わせる)
こちらはセッション履歴のキーになる「プロジェクトの絶対パス」を一致させるのが目的なので、ターゲットを ホスト側と同じパス にします。ホストの ~/<project_name> を開いているなら、コンテナ内でも /home/<username>/<project_name> にマウントするということです。
ターゲットの基準が違う (ユーザー基準 vs ホスト基準) のがややこしい部分なので、表にしておきます。
| マウント | ターゲット | 基準 |
|---|---|---|
~/.claude ~/.claude.json | /home/vscode/.claude /home/vscode/.claude.json | コンテナ内ユーザー の home |
| ワークスペース | /home/<username>/<project_name> 等 | ホスト側 の絶対パス |
devcontainer.json の例
上記を踏まえた .devcontainer/devcontainer.json の抜粋です。
{
"name": "my-devcontainer",
"build": {
"dockerfile": "Dockerfile",
"args": {
// ホストの $HOME を build arg として Dockerfile に渡す
// (workspace の親ディレクトリを事前に作るために使う。後述)
"WORKSPACE_PARENT": "${localEnv:HOME}"
}
},
// ワークスペースのマウント先をホスト側の絶対パスと一致させる。
// フォルダ名は localWorkspaceFolderBasename で動的解決するので、
// ホスト側を rename しても devcontainer.json の編集は不要。
"workspaceMount": "source=${localWorkspaceFolder},target=${localEnv:HOME}/${localWorkspaceFolderBasename},type=bind,consistency=cached",
"workspaceFolder": "${localEnv:HOME}/${localWorkspaceFolderBasename}",
"mounts": [
// .claude / .claude.json はコンテナ内ユーザー (vscode) の home にマウント
"source=${localEnv:HOME}/.claude,target=/home/vscode/.claude,type=bind,consistency=cached",
"source=${localEnv:HOME}/.claude.json,target=/home/vscode/.claude.json,type=bind,consistency=cached"
],
"remoteUser": "vscode"
}
${localEnv:HOME} は ホスト側 の $HOME (例: /home/<username>)、${localWorkspaceFolderBasename} は VSCode が開いているフォルダ名 (例: <project_name>) に展開されます。両者を組み合わせることで、ホストで開いているパスと完全に同じ絶対パスをコンテナ内に作れます。
Dockerfile 側で親ディレクトリを用意しておく
workspaceFolder に指定したパスの 親ディレクトリ がコンテナ内に存在しないと、Dev Containers の起動チェックで「ワークスペースが存在しません」とエラーになります。bind mount はマウント時にターゲットを作ってくれますが、Dev Containers のチェックがそれより先に走るためです。
そこで Dockerfile 側で親ディレクトリだけ先に作っておきます。
FROM mcr.microsoft.com/devcontainers/base:bookworm
# devcontainer.json の workspaceFolder (= ホストの $HOME/<フォルダ名>) と
# 同じ絶対パスをコンテナ内に用意しておく。
ARG WORKSPACE_PARENT=/home/vscode
RUN mkdir -p "${WORKSPACE_PARENT}"
ARG のデフォルト値を /home/vscode にしておけば、build arg を渡し忘れても既存ユーザーのホームが使われるだけで build が壊れることはありません。
ユーザーは vscode のままでよい
「ワークスペースが /home/<username>/<project_name> なのに whoami すると vscode になっている」というアンバランスさが気になるかもしれませんが、機能上は問題ありません。
- devcontainers/base の
vscodeユーザーは UID 1000 - ホスト側の Linux ユーザーも通常 UID 1000
UID が一致していれば bind mount したファイルにそのまま読み書きできるためです。vscode ユーザーが自分の home (/home/vscode/) 以外の場所で作業すること自体は何も問題を起こしません。
ユーザー名まで揃えようとすると base image の改造が必要になり、devcontainer features が前提にしている vscode ユーザーが消えて壊れがちです。ホストと揃えるのは「絶対パス」だけで十分 と割り切るのが楽です。
注意点
- ホスト側に
~/.claudeと~/.claude.jsonが存在している必要があります。なければ bind mount に失敗します。ホスト側でも一度 Claude Code を起動して認証を済ませておくのが手っ取り早いです。 - セッション履歴を ホストと同時に書き込まない よう注意してください。両方から同じプロジェクトを同時に触ると、ファイルロック等の衝突で履歴が壊れることがあります。基本「どちらか一方で作業する」運用が安全です。
- 設定変更後は VSCode の Dev Containers: Rebuild Container でリビルドしないと反映されません。
まとめ
- Claude Code のセッション履歴は プロジェクトの絶対パスをキー にして格納される。
- なので「ホストとセッション共有したい」ならコンテナ内のワークスペースパスをホスト側と完全一致させる必要がある。
- マウント先は 2 系統で基準が違う:
.claude/.claude.jsonは コンテナ内ユーザーの home に合わせる- ワークスペースは ホスト側の絶対パス に合わせる
- ワークスペースの親ディレクトリは Dockerfile 側で
mkdir -pしておく必要がある。 - フォルダ名は
${localWorkspaceFolderBasename}で動的解決すれば、rename にも追従する。