前置き
この記事は私がClaude Codeで個人開発をするにあたりChatGPTと壁打ちした記録を基にAIライティングしたものです。あくまで私が行き着いた方法に過ぎないことをご容赦ください。
概要
DevContainer で GitHub へ SSH 接続するために、これまではホストマシン側の .ssh/agent 以下にエージェント用の秘密鍵を置き、そのディレクトリを DevContainer に bind mount していました。
しかし、Claude Code の DevContainer ドキュメント には、~/.ssh やクラウド認証情報ファイルなどのホストシークレットをコンテナにマウントすることは避けるべき、という趣旨の警告があります。
この警告を見て、「DevContainer から読める場所に秘密鍵を置くのは避けたい」と感じました。
そこで今回は、秘密鍵を 1Password に残したまま、DevContainer からは 1Password SSH Agent 経由で認証する構成に整理しました。
以前の試行錯誤については、過去に Claude Code × DevContainer における ssh / Git 設定まわりの気づき という記事にもまとめています。この記事は、その後の追加整理という位置づけです。
今回の結論
最終的には、次の構成にしました。
秘密鍵
1Password 側で管理
公開鍵
ホストマシン側の .ssh/agent 以下に配置
DevContainer には .ssh/agent を bind mount して見せる
SSH Agent
ホスト側の 1Password SSH Agent を利用
DevContainer
/tmp/1password-agent.sock 経由で 1Password SSH Agent に接続
重要だった点は、~/.ssh/config の IdentityFile に 秘密鍵ではなく公開鍵を指定できる ことです。
1Password の Advanced use cases には、1Password アプリから公開鍵をダウンロードし、その公開鍵を IdentityFile に指定する手順が記載されています。秘密鍵は 1Password に残せます。
また、OpenSSH の ssh_config のマニュアル にも、秘密鍵がローカルにない場合、ssh-agent に読み込まれている対応する秘密鍵を使うために公開鍵ファイルを指定できる旨が記載されています。
前提環境
この記事では、主に次の環境を前提にしています。
注意:前回の記事ではVS Codeで検証していましたが、今回はZedのDevContainerで試行しました。
macOS
Docker Desktop
DevContainer
mcr.microsoft.com/devcontainers/javascript-node:24-bookworm
Zed
1Password SSH Agent
/tmp/1password-agent.sock 方式では、macOS 上の 1Password SSH Agent の Unix socket を DevContainer に bind mount します。
Windows / WSL2 / Linux では socket の場所や接続方法が異なるため、そのまま使えるとは限りません。
ホスト側の環境変数
まず、ホストマシン側で 1Password SSH Agent の socket パスを環境変数に逃がしました。
export ONEPASSWORD_SSH_AUTH_SOCK="$HOME/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
直接 devcontainer.json に macOS 固有のパスを書くこともできます。
しかし、それだと devcontainer.json が macOS 専用になってしまうため、今回は ONEPASSWORD_SSH_AUTH_SOCK という環境変数を挟みました。
Zed などを GUI から起動している場合、シェルで設定した環境変数がアプリ側に渡らないことがあります。
その場合は、環境変数を設定したターミナルから起動するか、macOS 側で GUI アプリ向けに環境変数を渡す必要があります。
zed .
devcontainer.json の設定
今回うまくいった設定は、runArgs で -v を指定する方法です。
{
"runArgs": [
"-v",
"${localEnv:ONEPASSWORD_SSH_AUTH_SOCK}:/tmp/1password-agent.sock"
],
"remoteEnv": {
"SSH_AUTH_SOCK": "/tmp/1password-agent.sock"
},
"postStartCommand": "sudo chown node:node /tmp/1password-agent.sock && sudo chmod 600 /tmp/1password-agent.sock"
}
ポイントは、ホスト側の 1Password SSH Agent の socket を、コンテナ内の /tmp/1password-agent.sock として見せていることです。
ホスト側
$ONEPASSWORD_SSH_AUTH_SOCK
コンテナ側
/tmp/1password-agent.sock
postStartCommand では、DevContainer の node ユーザーから socket に接続できるように所有者と権限を調整しています。
SSH config の設定
ホストマシン側の~/.ssh/agent/config は次のようにしました。
Host github-main
HostName github.com
User git
IdentityAgent /tmp/1password-agent.sock
IdentityFile ~/.ssh/id_ed25519_github.pub
IdentitiesOnly yes
IdentityAgent には、DevContainer 内から見える 1Password SSH Agent の socket を指定します。
IdentityFile には、秘密鍵ではなく公開鍵を指定します。
ここが今回一番意外だった点です。
IdentitiesOnly yes は、agent が持っている鍵を片っ端から試させないための設定です。
1Password に複数の SSH key が登録されている場合、これを指定しないと複数の identity が試行されることがあります。
公開鍵ファイルの権限
1Password アプリから SSH key の公開鍵をダウンロードすると、私の環境ではファイルのパーミッションが 600 になっていました。
既に .pub ファイルを持っている場合も、IdentityFile として指定する公開鍵は 600 にしておくのが無難です。
chmod 600 ~/.ssh/agent/id_ed25519_github.pub
通常、公開鍵は 644 で運用している人も多いと思います。
しかし、IdentityFile として扱う場合は SSH クライアント側のチェック対象になるため、私の環境では 600 にする必要がありました。
この点は、かなり見落としやすいポイントだと思います。
動作確認
DevContainer 内で、まず agent が見えているか確認します。
echo "$SSH_AUTH_SOCK"
ls -l /tmp/1password-agent.sock
ssh-add -L
ssh-add -L で 1Password に登録している公開鍵が表示されれば、agent への接続は成功しています。
次に、SSH の設定解決結果を確認します。
ssh -G github-main | grep -Ei 'hostname|user|identityagent|identityfile|identitiesonly'
期待する結果は、おおむね次のような内容です。
hostname github.com
user git
identityagent /tmp/1password-agent.sock
identityfile ~/.ssh/id_ed25519_github.pub
identitiesonly yes
最後に GitHub への接続を確認します。
ssh -T git@github-main
Docker Desktop 公式方式での検証
Docker Desktop には、ホスト側の SSH Agent をコンテナから使うための公式方式があります。
Docker Desktop の SSH agent forwarding では、/run/host-services/ssh-auth.sock を bind mount し、コンテナ内の SSH_AUTH_SOCK に指定する方法が案内されています。
{
"mounts": [
"source=/run/host-services/ssh-auth.sock,target=/run/host-services/ssh-auth.sock,type=bind"
],
"remoteEnv": {
"SSH_AUTH_SOCK": "/run/host-services/ssh-auth.sock"
}
}
ただし、私の環境ではこの方式はうまくいきませんでした。
最初は、DevContainer の node ユーザーから socket に接続できず、次のエラーになりました。
Error connecting to agent: Permission denied
chown により socket の権限エラーは回避できました。
しかし、その後も ssh-add -L は次の結果になりました。
The agent has no identities.
つまり、/run/host-services/ssh-auth.sock には接続できているものの、1Password SSH Agent の identity が見えていない状態でした。
この状態で IdentityFile に公開鍵を指定したまま ssh -T を実行すると、次のエラーになりました。
Load key "/home/node/.ssh/id_ed25519_github.pub": error in libcrypto
git@github.com: Permission denied (publickey).
これは libcrypto 自体が原因というより、対応する秘密鍵を agent から取得できないため、OpenSSH が .pub を秘密鍵として読み込もうとして失敗しているように見えます。
今回の環境では、Docker Desktop 公式方式では 1Password の identity が見えなかったため、不採用にしました。
mounts 方式での検証
ONEPASSWORD_SSH_AUTH_SOCK を使って、mounts で直接指定する方法も試しました。
{
"mounts": [
"source=${localEnv:ONEPASSWORD_SSH_AUTH_SOCK},target=/tmp/1password-agent.sock,type=bind"
],
"remoteEnv": {
"SSH_AUTH_SOCK": "/tmp/1password-agent.sock"
}
}
しかし、Zed の DevContainer 環境では、Docker 側で /socket_mnt/... に変換されたパスが存在しない扱いになり、次のようなエラーになりました。
invalid mount config for type "bind":
bind source path does not exist:
/socket_mnt/Users/.../Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock
そのため、今回の環境では mounts ではなく、runArgs の -v を使う形にしました。
参考記事
/tmp/1password-agent.sock に 1Password SSH Agent の socket を bind mount する方法は、DevContainerから1Password SSH Agentを使う方法 を参考にしました。
この記事では、runArgs で 1Password の Unix socket を /tmp/1password-agent.sock にマウントする構成が紹介されています。
私の環境でも、この方向性が最も安定しました。
今回の追加ポイントは、そこに加えて IdentityFile に公開鍵を指定し、IdentitiesOnly yes で使用する identity を固定した点です。
まとめ
今回の整理で、DevContainer に秘密鍵を見せずに GitHub へ SSH 接続できるようになりました。
秘密鍵は 1Password に残す
DevContainer には公開鍵だけを見せる
1Password SSH Agent の socket を /tmp/1password-agent.sock として bind mount する
IdentityFile には公開鍵を指定する
IdentitiesOnly yes で余計な identity を試させない
特に印象的だったのは、IdentityFile に公開鍵を指定できる点です。
普段は IdentityFile = 秘密鍵のパス と覚えているため、かなり意外でした。
一方で、Docker Desktop 公式の /run/host-services/ssh-auth.sock 方式は、今回の環境では 1Password の identity が見えませんでした。
また、Zed DevContainer では mounts 経由の socket mount も失敗しました。
結果として、今回の環境では次の組み合わせが最も安定しました。
runArgs + ONEPASSWORD_SSH_AUTH_SOCK + /tmp/1password-agent.sock + IdentityFile 公開鍵指定
Claude Code の DevContainer ドキュメントにある「ホストシークレットをコンテナにマウントしない」という警告をきっかけに見直しましたが、結果的にかなり納得感のある構成にできたと思います。