1. 目标与约束
1.1 目标
- 使用 Codex 桌面 App(非 VS Code)进行开发
- 代码与命令运行在 Docker 容器内,而非宿主机 shell
- 容器已在运行,不能随意重建
1.2 环境概览(脱敏)
| 组件 | 说明 |
|---|---|
| 本机 | Windows,OpenSSH 客户端 |
| 远程宿主机 | dgx-server-01(IP:192.168.206.xxx),用户 user_host |
| 容器 | 名称 my-dev-container,镜像 custom:dev,网络模式 host |
| 项目挂载 | 宿主机 /mnt/shared/project → 容器 /workspace |
| SSH 密钥 | 本机 ~/.ssh/id_rsa,注释 user@local-pc |
2. 第一性原理:Codex 远程连接到底在连什么?
2.1 Codex App 的远程模型
Codex App 不支持「Attach to Running Container」这类 Docker 原生操作。它的远程链路是:
本机 Codex App
│ OpenSSH
▼
远程目标(必须是一个完整的 SSH 登录环境)
│ 启动 codex app-server(通过 login shell)
▼
在该环境中读写文件、执行命令
推论 1: 容器必须对 Codex 呈现为一个 可 SSH 登录的端点,而不是「宿主机 + docker exec」。
推论 2: 远程环境中必须存在 codex CLI ,且版本满足 App 最低要求;App 通过 login shell 的 PATH 找到它。
推论 3: Codex App 只读取本机 ~/.ssh/config 中的 具体 Host 别名 ,不会读取 VS Code 的 devcontainer.json。
2.2 两种网络模式的分叉
| 网络模式 | 容器 IP | 连接方式 |
|---|---|---|
| bridge(默认) | 有独立 IP(如 172.17.0.x) |
本机 → SSH 宿主机(ProxyJump)→ SSH 容器 IP |
| host | 无独立 IP | 容器与宿主机共享网络栈,sshd 监听端口即宿主机端口 |
本案例容器为 host 模式,因此:
- 不能在
HostName里填「容器 IP」(为空) - 应在宿主机 IP 上监听非 22 端口 (如
2222),避免与宿主机 sshd 冲突
2.3 与 VS Code Dev Containers 的区别
| 能力 | VS Code | Codex App |
|---|---|---|
| Attach 已运行容器 | �7�3 | �7�4 |
| 读取 devcontainer.json | �7�3 | �7�4 |
| SSH 远程项目 | �7�3(Remote-SSH) | �7�3(Settings → Connections) |
若坚持用 Codex 桌面 App ,必须走 SSH 进容器 路线。
3. 整体架构(本案例最终方案)
Windows(Codex App + ~/.ssh/config)
│
│ ssh sd-jgy → 192.168.206.xxx:2222 user: root
▼
宿主机(host 网络)
│ 端口 2222 由容器内 sshd 监听
▼
Docker 容器 my-dev-container
├── /workspace(项目目录)
├── /usr/sbin/sshd(Port 2222)
└── codex CLI ≥ 0.141.0(~/.codex 存会话与配置)
4. 实施步骤
4.1 侦察:确认容器网络与挂载
在宿主机执行:
bash
# 网络模式与 IP
docker inspect my-dev-container --format \
'NetworkMode={{.HostConfig.NetworkMode}} IP={{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
# 挂载路径
docker inspect my-dev-container --format \
'{{range .Mounts}}{{.Destination}} <- {{.Source}}{{"\n"}}{{end}}'
本案例结果:
NetworkMode=host,IP=(空)/workspace <- /mnt/shared/project
4.2 在运行中的容器内配置 sshd
原则: 不停止容器,通过 docker exec 在内部安装并配置 SSH。
bash
# 安装 openssh-server
docker exec -u root my-dev-container bash -c '
apt-get update && apt-get install -y openssh-server
mkdir -p /var/run/sshd /root/.ssh
chmod 700 /root/.ssh
'
# 清理 Port 行,只保留 Port 2222(避免 sed 重复替换产生 Port 222222)
docker exec -u root my-dev-container bash -c '
sed -i "/^Port /d" /etc/ssh/sshd_config
sed -i "/^#Port /d" /etc/ssh/sshd_config
sed -i "1i Port 2222" /etc/ssh/sshd_config
grep -q "^PermitRootLogin" /etc/ssh/sshd_config && \
sed -i "s/^PermitRootLogin.*/PermitRootLogin prohibit-password/" /etc/ssh/sshd_config || \
echo "PermitRootLogin prohibit-password" >> /etc/ssh/sshd_config
grep -q "^PubkeyAuthentication" /etc/ssh/sshd_config || \
echo "PubkeyAuthentication yes" >> /etc/ssh/sshd_config
'
# 验证配置语法
docker exec -u root my-dev-container sshd -t
# 创建 privilege separation 目录(否则 sshd 拒绝启动)
docker exec -u root my-dev-container bash -c '
mkdir -p /run/sshd /var/run/sshd
chmod 755 /run/sshd /var/run/sshd
'
# 写入本机公钥(整行替换为实际 id_rsa.pub 内容)
docker exec -u root my-dev-container bash -c 'cat >> /root/.ssh/authorized_keys << "EOF"
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQD****...**** user@local-pc
EOF
chmod 600 /root/.ssh/authorized_keys'
# 启动 sshd
docker exec -u root my-dev-container bash -c 'pkill sshd 2>/dev/null; sleep 1; /usr/sbin/sshd'
# 宿主机本地验证
ssh -p 2222 -o StrictHostKeyChecking=accept-new root@127.0.0.1 "echo OK && ls /workspace"
注意: 在宿主机上
ssh -p 2222 root@127.0.0.1若提示密码,是因为使用的是宿主机本机密钥 ,未必在authorized_keys中。以 Windows 本机ssh sd-jgy测试为准。
4.3 本机 Windows SSH 配置
文件:C:\Users\<用户名>\.ssh\config
text
Host dgx-server-01
HostName 192.168.206.xxx
User user_host
IdentityFile ~/.ssh/id_rsa
IdentitiesOnly yes
Host sd-jgy
HostName 192.168.206.xxx
Port 2222
User root
IdentityFile ~/.ssh/id_rsa
IdentitiesOnly yes
StrictHostKeyChecking accept-new
关键原则:
Host sd-jgy的HostName必须是宿主机 IP ,不能写dgx-server-01别名去「引用」上一个 Host 块------SSH 不会自动继承。- host 网络模式下,
Port 2222即容器 sshd 在宿主机上的监听端口。 IdentitiesOnly yes强制使用指定密钥,避免 Windows 试错其他密钥后回落到密码认证。
验证:
powershell
ssh sd-jgy "echo OK && ls /workspace"
4.4 Codex App 连接
- 设置 → 连接 → 启用 SSH 主机
sd-jgy - 项目 → 远程目录选择
/workspace(或子目录) - 若列表中无该主机,保存 config 后完全重启 Codex App
4.5 docker端口持久化
- 在宿主机 dgx1-10 上查看入口
bash
docker inspect SD_jgy --format 'Entrypoint={{json .Config.Entrypoint}} Cmd={{json .Config.Cmd}}'
- 在容器里加启动钩子
bash
docker exec -u root SD_jgy bash -c '
ENTRYPOINT="/opt/nvidia/nvidia_entrypoint.sh"
HOOK="/usr/local/bin/00-start-sshd.sh"
# 写 sshd 启动脚本
cat > "$HOOK" << "EOF"
#!/bin/bash
mkdir -p /run/sshd /var/run/sshd
if [ -x /usr/sbin/sshd ]; then
if ! ss -tlnp 2>/dev/null | grep -q ":2222"; then
/usr/sbin/sshd
fi
fi
EOF
chmod +x "$HOOK"
# 在 nvidia entrypoint 开头注入调用(仅注入一次)
if [ -f "$ENTRYPOINT" ] && ! grep -q "00-start-sshd.sh" "$ENTRYPOINT"; then
cp "$ENTRYPOINT" "${ENTRYPOINT}.bak"
sed -i "2i /usr/local/bin/00-start-sshd.sh" "$ENTRYPOINT"
fi
'
5. 问题排查记录
5.1 sshd 配置 Port 格式错误
现象:
text
/etc/ssh/sshd_config line 14: Badly formatted port number.
Connection refused
原因: 多次 sed 's/Port 22/Port 2222/' 把已有 2222 变成 222222,且存在多行 Port。
解决: 删除所有 Port 行,只保留一行 Port 2222。
5.2 sshd 无法启动:缺少 /run/sshd
现象:
text
Missing privilege separation directory: /run/sshd
解决:
bash
mkdir -p /run/sshd && chmod 755 /run/sshd
/usr/sbin/sshd
5.3 宿主机 SSH 要密码,Windows 是否正常?
| 测试位置 | 使用的密钥 | 预期 |
|---|---|---|
宿主机 → 127.0.0.1:2222 |
宿主机 ~/.ssh/ |
可能需密码(未添加宿主机公钥时) |
Windows → sd-jgy |
本机 id_rsa |
应免密成功 |
容器默认无 root 密码 ,提示密码说明公钥认证未匹配,不是「需要设置密码」。
5.4 Codex CLI 版本不满足
现象(Codex App):
text
最低要求版本:0.141.0
当前安装版本:0.130.0
原理: App 通过 SSH 启动远程 codex app-server,CLI 与 App 存在最低版本契约。
升级注意:
- 重装 CLI 不会 删除
~/.codex/(聊天记录、登录态、配置保留) - 不要 执行
rm -rf ~/.codex(除非刻意清空)
5.4.1 npm 升级失败:ENOTEMPTY
现象:
text
npm error ENOTEMPTY: directory not empty, rename '.../@openai/codex' -> '.../.codex-xxxxx'
原因: 上次升级中断,npm 无法重命名旧目录,不是 npm 版本过低的典型表现。
解决:
bash
npm uninstall -g @openai/codex
rm -rf /usr/local/nvm/versions/node/v18.x.x/lib/node_modules/@openai/codex
rm -rf /usr/local/nvm/versions/node/v18.x.x/lib/node_modules/@openai/.codex-*
npm cache clean --force
npm install -g @openai/codex@0.141.0
bash -lc 'codex --version'
5.4.2 npm 包损坏:缺少平台二进制
现象:
text
Error: Missing optional dependency @openai/codex-linux-x64.
Reinstall Codex: npm install -g @openai/codex@latest
原因: 部分安装导致 JS 包装存在,但 @openai/codex-linux-x64 未下载完整。
备选方案:使用独立二进制(推荐,绕过 npm)
bash
cd /tmp
curl -fsSL -o codex.tar.gz \
"https://github.com/openai/codex/releases/latest/download/codex-x86_64-unknown-linux-musl.tar.gz"
tar -xzf codex.tar.gz
install -m 0755 codex-x86_64-unknown-linux-musl /usr/local/bin/codex
export PATH="/usr/local/bin:$PATH"
grep -q '/usr/local/bin' ~/.bashrc || echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.bashrc
bash -lc 'which codex && codex --version'
确保 which codex 指向 /usr/local/bin/codex,而非 nvm 下损坏的 npm 版。
6. 数据与隐私边界
| 路径 | 内容 | 重装 CLI 是否影响 |
|---|---|---|
~/.codex/ |
登录态、配置、会话/聊天记录 | �7�4 不影响(不手动删除时) |
node_modules/@openai/codex |
CLI 程序本体 | �7�3 会被卸载/覆盖 |
~/.ssh/authorized_keys |
SSH 公钥 | �7�4 不影响 |
| Codex App 本机数据 | 本地项目与线程 | �7�4 与容器 CLI 重装无关 |
7. 运维:容器重启后恢复 SSH
host 模式下,容器内 sshd 配置与 authorized_keys 通常持久保留 (未重建容器时),但 sshd 进程可能停止:
bash
docker exec -u root my-dev-container bash -c '
mkdir -p /run/sshd
pkill sshd 2>/dev/null
sleep 1
/usr/sbin/sshd
'
docker exec -u root my-dev-container ss -tlnp | grep 2222
Windows 验证:
powershell
ssh sd-jgy "echo OK"
8. 决策树(速查)
要在 Codex App 里用容器环境?
├─ 否 → VS Code Dev Containers: Attach to Running Container
└─ 是 → 容器能否配置 sshd?
├─ 否 → 暂不可行(或换可 SSH 的容器镜像)
└─ 是 → 查网络模式
├─ bridge → HostName=容器IP, ProxyJump=宿主机
└─ host → HostName=宿主机IP, Port=2222(容器 sshd)
→ 本机 config + Codex App 启用该 Host
→ 容器 codex CLI ≥ App 要求版本
→ 项目路径 /workspace
9. 最终检查清单
-
docker inspect确认网络模式(host / bridge) - 容器内
sshd -t配置合法,Port 仅一行 -
/run/sshd存在,2222 端口 LISTEN -
authorized_keys含本机公钥,权限 600 - Windows
ssh sd-jgy "echo OK"免密成功 -
bash -lc 'codex --version'≥ 0.141.0 - Codex App 设置 → 连接 启用
sd-jgy - 项目目录
/workspace可访问
10. 参考
- OpenAI Codex Remote Connections
- OpenAI Codex Changelog(CLI 版本)
- VS Code: Attach to a running container(对比用,Codex App 不适用)
文档生成日期:2026-06-26 · 隐私信息已脱敏,部署时请替换占位符为实际值。