Dev Container 中使用 1Password SSH Agent
本文由 Codex 根据一次真实的 Dev Container 排障过程整理生成。
最近在本地用 Dev Container 跑项目时,遇到一个看起来很绕的问题:宿主机已经启用了 1Password SSH Agent,容器里也能看到 SSH_AUTH_SOCK,但 ssh-add -l 仍然失败。
这篇记录一下问题现象、背后的原理,以及一套比较稳的修复方法。
问题现象
最开始 Dev Container 启动时出现过这样的错误:
1 | Container server: Remote to local stream terminated with error: { |
修了一轮之后,容器里已经能看到 VS Code / Dev Containers 转发出来的 socket:
1 | root@container:/data/autotest# echo "$SSH_AUTH_SOCK" |
但是继续执行:
1 | ssh-add -l |
却得到:
1 | error fetching identities: communication with agent failed |
这说明问题已经不是“容器里没有 socket”,而是“容器内的转发 socket 连回宿主机 agent 时失败”。
原理:Dev Container 不是直接使用 macOS 的 agent.sock
在 macOS 上,1Password SSH Agent 的真实 socket 一般在:
1 | ~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock |
但是 Linux 容器不能直接理解这个 macOS 路径。尤其路径里还有空格,很多工具链里如果写成 ~/Library/Group\ Containers/...,~ 并不会被展开成 /Users/xxx。
Dev Containers 的正常做法不是把这个 macOS socket 原样挂进容器,而是:
- VS Code / Dev Containers 在宿主机读取
SSH_AUTH_SOCK。 - 它在容器里创建一个临时 socket,例如
/tmp/vscode-ssh-auth-xxxx.sock。 - 容器里的 SSH 客户端连接这个临时 socket。
- Dev Containers 再把请求转发回宿主机的真实 SSH Agent。
- 1Password 弹窗授权或返回 key 列表。
所以容器里看到的路径应该是:
1 | /tmp/vscode-ssh-auth-xxxx.sock |
而不应该是:
1 | /Users/xxx/Library/Group Containers/... |
也不应该是:
1 | ~/Library/Group Containers/... |
如果容器里已经出现 /tmp/vscode-ssh-auth-xxxx.sock,说明 Dev Containers 的转发层已经建起来了。此时 communication with agent failed 往往表示 VS Code / Dev Containers 进程拿到的宿主机 SSH_AUTH_SOCK 是错的、旧的,或者对应的 1Password agent 当前不可用。
修复方法
1. 在宿主机创建一个稳定的短路径
1Password 的原始路径包含空格,不适合到处配置。建议在宿主机建立一个稳定软链接:
1 | mkdir -p "$HOME/.1password" |
这样之后统一使用:
1 | /Users/<username>/.1password/agent.sock |
路径更短,也没有空格。
2. 验证 1Password Agent 本身可用
在 macOS 宿主机终端执行:
1 | SSH_AUTH_SOCK="$HOME/.1password/agent.sock" ssh-add -l |
如果这里失败,说明还没到 Dev Container 层,应该先检查 1Password:
- 打开 1Password。
- 进入
Settings -> Developer。 - 确认
Use the SSH Agent已开启。 - 确认对应 SSH key 已经加入 1Password。
宿主机这一步必须先成功。
3. 配置 SSH_AUTH_SOCK
如果平时从终端启动 VS Code / Codex,最自然的做法是在 ~/.zshrc 里配置:
1 | echo 'export SSH_AUTH_SOCK="$HOME/.1password/agent.sock"' >> "$HOME/.zshrc" |
然后验证:
1 | echo "$SSH_AUTH_SOCK" |
如果你是从 Dock、Finder、Spotlight 这类 GUI 入口启动 VS Code / Codex,需要额外注意:GUI App 不一定继承当前终端里的环境变量。即使你在 shell 里配置了 export SSH_AUTH_SOCK=...,GUI 进程也可能拿不到。
这时可以把变量写入 macOS 的 launchd 环境:
1 | launchctl unsetenv SSH_AUTH_SOCK |
最后一行应该输出类似:
1 | /Users/baymax/.1password/agent.sock |
注意,输出里不能有 ~。
如果你始终从终端启动,比如:
1 | code . |
或者从终端启动 Codex,那么只配置 ~/.zshrc 通常就够了。
4. 重启 VS Code / Codex
这一步很关键。
如果 VS Code / Codex 已经启动,它已经读到了旧环境变量。即使你后来 launchctl setenv,当前进程也不会自动更新。
所以需要:
- 完整退出 VS Code / Codex。
- 重新打开项目。
- Rebuild / Reopen Dev Container。
5. 容器内验证
进入容器后执行:
1 | echo "$SSH_AUTH_SOCK" |
正常情况下,第一条命令应该输出类似:
1 | /tmp/vscode-ssh-auth-xxxx.sock |
然后:
1 | ssh-add -l |
应该能列出 1Password 中的 SSH key,或者触发 1Password 授权。
最后可以测试 GitHub:
1 | ssh -T git@github.com |
如果配置正确,会看到 GitHub 的 SSH 认证提示。
不建议在 devcontainer.json 里直接挂载 1Password socket
一种看似直接的做法是把宿主机的 1Password socket mount 到容器里:
1 | { |
这在某些 Linux 宿主机上可行,但在 macOS + Docker Desktop / Colima 这类环境里并不稳。
原因是容器实际运行在 Linux VM 里,不是直接运行在 macOS 内核上。macOS 的 Unix socket 不一定能像普通文件一样被正确转发到 VM 再进入容器。路径里还有空格和 ~ 展开问题,排障成本会更高。
更稳的方案是让 Dev Containers 自己做 SSH agent forwarding。也就是:
- 宿主机
SSH_AUTH_SOCK指向真实可用的 1Password agent。 - GUI App 从 launchd 环境拿到正确值。
- 容器内使用 Dev Containers 自动生成的
/tmp/vscode-ssh-auth-xxxx.sock。
检查 ~/.ssh/config
还有一个容易忽略的坑:~/.ssh/config 里的 IdentityAgent。
如果你写了:
1 | Host * |
容器内 SSH 客户端会尝试直接访问这个 macOS 路径。这个路径在容器里不存在,也不会自动展开。
推荐改成:
1 | Host * |
或者干脆去掉 IdentityAgent,让 SSH 使用环境变量里的 SSH_AUTH_SOCK。
如果只想对 GitHub 使用 1Password agent,可以写得更收敛:
1 | Host github.com |
最终检查清单
宿主机:
1 | test -S "$HOME/.1password/agent.sock" |
容器内:
1 | echo "$SSH_AUTH_SOCK" |
预期结果:
- 宿主机
SSH_AUTH_SOCK是绝对路径。 - 容器内
SSH_AUTH_SOCK是/tmp/vscode-ssh-auth-xxxx.sock。 ssh-add -l能列出 1Password 管理的 key。ssh -T git@github.com能触发或通过 SSH 认证。
小结
这个问题的关键判断点是:
ENOENT ~/Library/.../agent.sock:宿主机 agent 路径没有被正确展开。- 容器里没有
SSH_AUTH_SOCK:Dev Containers 没有建立 SSH agent forwarding。 - 容器里有
/tmp/vscode-ssh-auth-xxxx.sock,但communication with agent failed:转发层存在,但 VS Code / Codex 背后的宿主机 agent 不可用或路径仍是旧值。
最终不要把 macOS 的 1Password socket 当成普通文件直接塞进容器。让宿主机提供一个稳定、绝对、无空格的 SSH_AUTH_SOCK,再让 Dev Containers 负责转发,才是更省心的方式。✨