12 KiB
在 Docker 沙盒中运行 NanoClaw(手动设置)
本指南介绍如何从头开始在 Docker 沙盒中设置 NanoClaw——无需安装脚本,无需预构建的 fork。你将克隆上游仓库,应用必要的补丁,并在完全的 hypervisor 级别隔离下运行 agent(智能体)。
架构
宿主机 (macOS / Windows WSL)
└── Docker 沙盒 (带隔离内核的微 VM)
├── NanoClaw 进程 (Node.js)
│ ├── 频道适配器 (WhatsApp, Telegram 等)
│ └── 容器启动器 → 嵌套 Docker 守护进程
└── Docker-in-Docker
└── nanoclaw-agent 容器
└── Claude Agent SDK
每个 agent 在自己的容器中运行,位于一个与你的宿主机完全隔离的微 VM 内。两层隔离:每个 agent 的容器 + VM 边界。
沙盒在 host.docker.internal:3128 提供一个 MITM 代理,处理网络访问并自动注入你的 Anthropic API 密钥。
注意: 本指南基于在 macOS(Apple Silicon)上使用 WhatsApp 验证过的设置。其他频道(Telegram、Slack 等)和环境(Windows WSL)可能需要针对其特定 HTTP/WebSocket 客户端添加额外的代理补丁。核心补丁(容器运行器、凭据代理、Dockerfile)普遍适用——频道特定的代理配置各有不同。
前提条件
- Docker Desktop v4.40+ 支持沙盒
- Anthropic API 密钥(沙盒代理管理注入)
- 对于 Telegram:来自 @BotFather 的 bot token 和你的 chat ID
- 对于 WhatsApp:一部安装了 WhatsApp 的手机
验证沙盒支持:
docker sandbox version
步骤 1:创建沙盒
在你的宿主机上:
# 创建工作区目录
mkdir -p ~/nanoclaw-workspace
# 创建带工作区挂载的 shell 沙盒
docker sandbox create shell ~/nanoclaw-workspace
如果你使用 WhatsApp,配置代理绕过,使 WhatsApp 的 Noise 协议不被 MITM 检查:
docker sandbox network proxy shell-nanoclaw-workspace \
--bypass-host web.whatsapp.com \
--bypass-host "*.whatsapp.com" \
--bypass-host "*.whatsapp.net"
Telegram 不需要代理绕过。
进入沙盒:
docker sandbox run shell-nanoclaw-workspace
步骤 2:安装前提依赖
在沙盒内:
sudo apt-get update && sudo apt-get install -y build-essential python3
npm config set strict-ssl false
步骤 3:克隆并安装 NanoClaw
NanoClaw 必须位于工作区目录内——Docker-in-Docker 只能从共享的工作区路径进行 bind-mount。
# 先克隆到家目录(virtiofs 可能在克隆期间损坏 git pack 文件)
cd ~
git clone https://github.com/nanocoai/nanoclaw.git
# 替换为你的工作区路径(你传递给 `docker sandbox create` 的宿主机路径)
WORKSPACE=/Users/you/nanoclaw-workspace
# 移入工作区,以便 DinD 挂载生效
mv nanoclaw "$WORKSPACE/nanoclaw"
cd "$WORKSPACE/nanoclaw"
# 安装依赖
pnpm install
pnpm install https-proxy-agent
步骤 4:应用代理和沙盒补丁
NanoClaw 需要多个补丁才能在 Docker 沙盒内工作。这些补丁处理代理路由、CA 证书和 Docker-in-Docker 挂载限制。
4a. Dockerfile——用于容器镜像构建的代理参数
sandbox 内的 docker build 会因为沙盒的 MITM 代理提供自己的证书而出现 SELF_SIGNED_CERT_IN_CHAIN 失败。向 container/Dockerfile 添加代理构建参数:
在 FROM 行之后添加这些行:
# 接受代理构建参数
ARG http_proxy
ARG https_proxy
ARG no_proxy
ARG NODE_EXTRA_CA_CERTS
ARG npm_config_strict_ssl=true
RUN npm config set strict-ssl ${npm_config_strict_ssl}
并在 RUN pnpm install 行之后:
RUN npm config set strict-ssl true
4b. 构建脚本——转发代理参数
补丁 container/build.sh,将代理环境变量传递给 docker build:
在 docker build 命令中添加这些 --build-arg 标志:
--build-arg http_proxy="${http_proxy:-$HTTP_PROXY}" \
--build-arg https_proxy="${https_proxy:-$HTTPS_PROXY}" \
--build-arg no_proxy="${no_proxy:-$NO_PROXY}" \
--build-arg npm_config_strict_ssl=false \
4c. 容器运行器——代理转发、CA 证书挂载、/dev/null 修复
对 src/container-runner.ts 的三处更改:
替换 /dev/null 影子挂载。 沙盒拒绝 /dev/null bind mount。找到 .env 被影子挂载到 /dev/null 的位置,将其替换为一个空文件:
// 创建一个空文件来影子 .env(Docker 沙盒拒绝 /dev/null 挂载)
const emptyEnvPath = path.join(DATA_DIR, 'empty-env');
if (!fs.existsSync(emptyEnvPath)) fs.writeFileSync(emptyEnvPath, '');
// 在挂载中使用 emptyEnvPath 代替 '/dev/null'
将代理环境变量转发到生成的 agent 容器。为 HTTP_PROXY、HTTPS_PROXY、NO_PROXY 及其小写变体添加 -e 标志。
挂载 CA 证书。 如果设置了 NODE_EXTRA_CA_CERTS 或 SSL_CERT_FILE,将证书复制到项目目录并挂载到 agent 容器中:
const caCertSrc = process.env.NODE_EXTRA_CA_CERTS || process.env.SSL_CERT_FILE;
if (caCertSrc) {
const certDir = path.join(DATA_DIR, 'ca-cert');
fs.mkdirSync(certDir, { recursive: true });
fs.copyFileSync(caCertSrc, path.join(certDir, 'proxy-ca.crt'));
// 挂载:certDir -> /workspace/ca-cert(只读)
// 在容器中设置 NODE_EXTRA_CA_CERTS=/workspace/ca-cert/proxy-ca.crt
}
4d. 容器运行时——防止自我终止
在 src/container-runtime.ts 中,cleanupOrphans() 函数通过 nanoclaw- 前缀匹配容器。在沙盒内,沙盒容器本身可能匹配(例如 nanoclaw-docker-sandbox)。过滤掉当前主机名:
// 在 cleanupOrphans() 中,从要停止的容器列表中过滤掉 os.hostname()
4e. 凭据代理——通过 MITM 代理路由
在 src/credential-proxy.ts 中,上游 API 请求需要通过沙盒代理。向出站请求添加 HttpsProxyAgent:
import { HttpsProxyAgent } from 'https-proxy-agent';
const proxyUrl = process.env.HTTPS_PROXY || process.env.https_proxy;
const upstreamAgent = proxyUrl ? new HttpsProxyAgent(proxyUrl) : undefined;
// 将 upstreamAgent 传递给 https.request() 选项
4f. 安装脚本——代理构建参数
补丁 setup/container.ts,传递与 build.sh(步骤 4b)相同的代理 --build-arg 标志。
步骤 5:构建
pnpm run build
bash container/build.sh
步骤 6:添加频道
Telegram
# 应用 Telegram 技能
pnpm exec tsx scripts/apply-skill.ts .claude/skills/add-telegram
# 应用技能后重建
pnpm run build
# 配置 .env
cat > .env << EOF
TELEGRAM_BOT_TOKEN=<your-token-from-botfather>
ASSISTANT_NAME=nanoclaw
ANTHROPIC_API_KEY=proxy-managed
EOF
mkdir -p data/env && cp .env data/env/env
# 注册你的聊天
pnpm exec tsx setup/index.ts --step register \
--jid "tg:<your-chat-id>" \
--name "My Chat" \
--trigger "@nanoclaw" \
--folder "telegram_main" \
--channel telegram \
--assistant-name "nanoclaw" \
--is-main \
--no-trigger-required
查找你的 chat ID: 向你的 bot 发送任意消息,然后:
curl -s --proxy $HTTPS_PROXY "https://api.telegram.org/bot<TOKEN>/getUpdates" | python3 -m json.tool
群组中的 Telegram: 在 @BotFather 中禁用群组隐私(/mybots > Bot Settings > Group Privacy > Turn off),然后移除并重新添加 bot。
重要提示: 如果 Telegram 技能创建了 src/channels/telegram.ts,你需要为代理支持打补丁。添加一个 HttpsProxyAgent 并通过 baseFetchConfig.agent 将其传递给 grammy 的 Bot 构造函数。然后重建。
确保你已先在步骤 1中配置了代理绕过。
# 应用 WhatsApp 技能
pnpm exec tsx scripts/apply-skill.ts .claude/skills/add-whatsapp
# 重建
pnpm run build
# 配置 .env
cat > .env << EOF
ASSISTANT_NAME=nanoclaw
ANTHROPIC_API_KEY=proxy-managed
EOF
mkdir -p data/env && cp .env data/env/env
# 认证(选择一种):
# QR 码——用 WhatsApp 相机扫描:
pnpm exec tsx src/whatsapp-auth.ts
# 或配对码——在 WhatsApp > 已关联设备 > 通过电话号码关联中输入代码:
pnpm exec tsx src/whatsapp-auth.ts --pairing-code --phone <phone-number-no-plus>
# 注册你的聊天(JID = 你的电话号码 + @s.whatsapp.net)
pnpm exec tsx setup/index.ts --step register \
--jid "<phone>@s.whatsapp.net" \
--name "My Chat" \
--trigger "@nanoclaw" \
--folder "whatsapp_main" \
--channel whatsapp \
--assistant-name "nanoclaw" \
--is-main \
--no-trigger-required
重要提示: WhatsApp 技能文件(src/channels/whatsapp.ts 和 src/whatsapp-auth.ts)也需要代理补丁——为 WebSocket 连接添加 HttpsProxyAgent 以及一个支持代理的版本获取。然后重建。
两个频道
应用两个技能,为两者打代理补丁,合并 .env 变量,并分别注册每个聊天。
步骤 7:运行
pnpm start
你不需要手动设置 ANTHROPIC_API_KEY。沙盒代理拦截请求,并自动将 proxy-managed 替换为你的真实密钥。
网络细节
代理如何工作
沙盒的所有流量通过宿主机代理路由,地址为 host.docker.internal:3128:
Agent 容器 → DinD 网桥 → 沙盒 VM → host.docker.internal:3128 → 宿主机代理 → api.anthropic.com
"绕过"并不意味着流量跳过代理。 它意味着代理在不做 MITM 检查的情况下传递流量。Node.js 不会自动使用 HTTP_PROXY 环境变量——你需要在每个 HTTP/WebSocket 客户端中显式配置 HttpsProxyAgent。
DinD 挂载的共享路径
只有工作区目录可用于 Docker-in-Docker bind mount。工作区之外的路径会失败并显示"path not shared":
/dev/null→ 改为项目目录中的空文件/usr/local/share/ca-certificates/→ 将证书复制到项目目录/home/agent/→ 改为克隆到工作区
Git clone 和 virtiofs
工作区通过 virtiofs 挂载。Git 的 pack 文件处理在 virtiofs 上可能在 clone 期间损坏。解决方法:先 clone 到 /home/agent,然后 mv 到工作区。
故障排查
pnpm install 失败并出现 SELF_SIGNED_CERT_IN_CHAIN
npm config set strict-ssl false
容器构建失败并出现代理错误
docker build \
--build-arg http_proxy=$http_proxy \
--build-arg https_proxy=$https_proxy \
-t nanoclaw-agent:latest container/
Agent 容器失败并出现 "path not shared"
所有 bind-mounted 路径必须在工作区目录下。检查:
- NanoClaw 是否克隆到了工作区?(不是
/home/agent/) - CA 证书是否复制到了项目根目录?
- 空的
.env影子文件是否已创建?
Agent 容器无法访问 Anthropic API
验证代理环境变量是否转发到了 agent 容器。检查容器日志中的 HTTP_PROXY=http://host.docker.internal:3128。
WhatsApp 错误 405
版本获取返回了过时的版本。确保已应用支持代理的 fetchWaVersionViaProxy 补丁——它通过 HttpsProxyAgent 获取 sw.js 并解析 client_revision。
WhatsApp 立即显示 "Connection failed"
代理绕过未配置。从宿主机运行:
docker sandbox network proxy <sandbox-name> \
--bypass-host web.whatsapp.com \
--bypass-host "*.whatsapp.com" \
--bypass-host "*.whatsapp.net"
Telegram bot 不接收消息
- 检查 grammy 代理补丁已应用(在
src/channels/telegram.ts中查找HttpsProxyAgent) - 如果在群组中使用,检查 @BotFather 中群组隐私已禁用
Git clone 失败并出现 "inflate: data stream error"
先 clone 到非工作区路径,然后移动:
cd ~ && git clone https://github.com/nanocoai/nanoclaw.git && mv nanoclaw /path/to/workspace/nanoclaw
WhatsApp QR 码不显示
在沙盒内以交互方式运行认证命令(不通过 docker sandbox exec 管道):
docker sandbox run shell-nanoclaw-workspace
# 然后在沙盒内:
pnpm exec tsx src/whatsapp-auth.ts