1. 为什么要把 OpenClaw 塞进 Docker 再套一层 OpenResty
OpenClaw 这类带网关、带工具调用、还要跑浏览器自动化的服务,直接裸装在宿主机上,最头疼的不是装不上,而是装完之后环境互相污染。它要 Chromium、要 Playwright、要 systemd、要自己的 Docker 守护进程,宿主机上但凡有点别的服务,端口、内核模块、依赖版本就开始打架。OpenClaw-In-Docker 这个开源项目(GitHub 上 cncfstack/openclaw-in-docker)解决的正是这件事:把整套东西封进一个类虚拟机的隔离容器里,容器内自带 systemd、自带独立 Docker、自带 OpenResty,对外只暴露 80 和 443。
但真正让它在公网可用,还差两块拼图。第一块是 HTTPS,项目默认用 OpenSSL 自签证书,浏览器会拦,网关的allowedOrigins也容易对不上;第二块是 OpenResty 反向代理,它不只是转发,还承担了基于 Lua 的登录认证——用户必须先过登录页,才能摸到 OpenClaw 的原生页面。这两块配好,OpenClaw 才算真正“安全、独立、便捷”地跑起来。
而多服务场景下还有个隐性痛点:OpenClaw 网关要 Token,TaoToken 侧要 API Key,如果每个服务各存一份密钥,轮换和审计就是灾难。这篇就按“Docker 部署 → OpenResty 反代 + HTTPS → TaoToken 统一 Key 接入 → curl 验证”的顺序走一遍,命令和配置都能直接抄。
适合谁看:手里有一台能跑 Docker 的机器、想把 OpenClaw 放到公网但不想裸奔、同时希望把模型调用的 Key 收敛到一处的开发者。下面所有操作我都按可复制的粒度写,遇到坑的地方会标出来。
2. TaoToken 前置准备:统一 Key 与接入信息
在动 Docker 之前,先把 TaoToken 这侧的接入信息准备好,不然后面 OpenClaw 网关连上了、模型却调不通,排查会绕远路。TaoToken 在这里扮演的角色是统一的模型调用入口:OpenClaw 内部要调模型时,不再各自散落 Key,而是走同一个 Base URL + 同一个 Key,模型 ID 按需切换。
你需要拿到三样东西,我把它叫“三件套”,后面所有配置都围绕它:
| 项目 | 值 | 说明 |
|---|---|---|
| Base URL | https://taotoken.net/api | 所有请求的根地址,注意不要带多余路径 |
| API Key | 控制台生成 | 形如sk-开头的一串,只显示一次,及时保存 |
| Model ID | 按需选择 | 例如对话类、编码类模型,填进请求体的model字段 |
获取路径很直接:打开官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,进控制台,在 API Keys 页面新建一个 Key。建议按用途命名,比如openclaw-gateway,这样以后要吊销或轮换时一眼能认出来。控制台地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,API Keys 页面是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。
这里有个容易忽略的点:OpenClaw 网关本身用的是 WebSocket 令牌(wss://那套),和 TaoToken 的 API Key 是两码事。网关令牌管的是“浏览器能不能连上 OpenClaw 网关”,TaoToken Key 管的是“OpenClaw 调模型时能不能通过鉴权”。两者不要混。我见过有人把网关 Token 填进模型配置里,结果一直 401,查了半天。
如果你后面还要接 Claude Code 这类编码工具,或者用 Coding Plan 做长期编码任务,Key 的规划可以提前想清楚:一个 Key 对应一类用途,方便在控制台看用量。Coding Plan 的入口在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,模型对话调试入口在 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite 。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,遇到参数不确定时以文档为准。
准备好三件套后,先别急着配 OpenClaw,用一条 curl 确认 Key 本身是通的,这一步能省掉后面大量“到底是网络问题还是 Key 问题”的纠结。命令在第四节给。
3. 可复制配置:docker-compose 与 OpenResty 反代片段
原项目给的是docker run长命令,能跑,但参数一多就难维护,尤其是要挂证书、要改环境变量的时候。我把它整理成docker-compose.yml,同时把 OpenResty 反代和 HTTPS 证书的挂载路径写清楚。注意:容器内已经内置了 OpenResty,所以反代配置是改容器内 OpenResty 的站点配置,而不是在宿主机再起一个 Nginx。
先建目录结构,证书和配置都放进去:
mkdir -p ./data/openclaw01/ssl mkdir -p ./data/openclaw01/openrestydocker-compose.yml如下,路径和原项目保持一致,只做了编排化:
services: openclaw: image: registry.cncfstack.com/cncfstack/openclaw-in-docker:v2026.3.11-v0.1.0 container_name: openclaw-in-docker hostname: openclaw-in-docker privileged: true restart: always ports: - "80:80" - "443:443" volumes: - /lib/modules:/lib/modules:ro - openclaw-storage:/var - ./data/openclaw01:/root/.openclaw - ./data/openclaw01/ssl:/etc/openresty/ssl environment: - OPENCLAW_WEB_URL=https://your.domain.com - OPENCLAW_USER=openclaw - OPENCLAW_PASSWORD=换成你的强密码 volumes: openclaw-storage:几个参数必须说清楚,不然容易踩坑。privileged: true是因为容器内还要跑 Docker,需要挂载内核路径,项目也在逐步收缩这个权限,但目前还得留着。openclaw-storage:/var是命名卷,不是目录挂载,别写成./var,否则容器内 Docker 会出问题。OPENCLAW_WEB_URL一定要和你实际访问的域名一致,它同时决定登录地址、证书生成和allowedOrigins,写成localhost却用域名访问,网关会拒绝连接。
HTTPS 证书部分,把合法证书放到./data/openclaw01/ssl/,并固定命名为cert.pem和cert.key。如果之前已经生成过自签证书,先清掉再放:
rm -f ./data/openclaw01/ssl/* cp /path/to/fullchain.pem ./data/openclaw01/ssl/cert.pem cp /path/to/privkey.pem ./data/openclaw01/ssl/cert.keyOpenResty 反代配置,容器内站点配置一般放在/etc/openresty/conf.d/或项目约定的路径。核心是把 443 的 server 块指向 OpenClaw 上游,并保留 Lua 登录认证。下面是一个可参考的 server 片段,重点是proxy_pass指向本机 OpenClaw 端口、WebSocket 升级头要带上:
server { listen 443 ssl; server_name your.domain.com; ssl_certificate /etc/openresty/ssl/cert.pem; ssl_certificate_key /etc/openresty/ssl/cert.key; location / { access_by_lua_block { -- 这里保留项目自带的登录校验逻辑 } proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }Upgrade和Connection这两行是 WebSocket 能不能连上的关键,网关走的是wss://,少了这两行,页面能打开但网关一直转圈。改完配置重启容器:
docker compose down docker compose up -d docker restart openclaw-in-docker如果你用的是 Cline MCP 或 Codex 的auth.json这类工具,配置思路一样,都是 Base URL + Key + Model ID 三件套,只是文件位置不同。Claude Code 的接入同理,Base URL 填https://taotoken.net/api,Key 填控制台生成的,Model ID 按文档选。ClaudeCodeAnthropic 相关说明在 https://taotoken.net/claudecode-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claudecode-anthropic&utm_campaign=rewrite 。
4. 验证请求:curl 打通接口与网关连接
配置写完不验证,等于没配。分两步验:先验 TaoToken 的 Key 通不通,再验 OpenClaw 网关连不连得上。
第一步,用 curl 打 TaoToken 的接口。这一步和 OpenClaw 无关,纯粹确认三件套正确:
curl -sS https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "你的模型ID", "messages": [{"role": "user", "content": "ping"}] }'把$TAOTOKEN_API_KEY换成你的 Key,model换成实际模型 ID。返回里能看到choices数组就说明 Key 和 Base URL 都对。如果返回 401,先查 Key 有没有复制全、有没有多余空格;如果返回模型不存在,查 Model ID 拼写。
第二步,验 OpenClaw 网关。先拿网关令牌:
cat ./data/openclaw01/openclaw.json | grep 'token' | grep -v mode输出类似"token": "f64687a164a25e500000000c658b3e488660001dc600c273"。然后在浏览器打开https://your.domain.com,用OPENCLAW_USER和OPENCLAW_PASSWORD登录,进入网关页面,WebSocket URL 填wss://your.domain.com,Token 填上面拿到的值,点连接。
新设备第一次连会触发审批。推荐用提示里的命令审批,或者手动跑:
docker exec -i openclaw-in-docker bash -- /usr/local/bin/openclaw-autoapprove-devices.sh审批后等 30 秒或刷新页面。这一步的坑在于:审批脚本会放行所有设备,所以务必确认你的 OpenClaw 只对可信的人开放,别把登录密码设成默认的openclaw。
再补一条容器内验证,确认 OpenClaw 服务本身活着:
docker exec -it openclaw-in-docker /bin/bash openclaw --version能进到 Debian 环境、/app是源码目录、/root/.openclaw/是配置目录,说明容器结构正常。到这里,HTTPS、反代、网关、模型 Key 四条链路都通了。
5. 本篇常见错排查:401、local proxy failed 与证书不匹配
配这套东西,报错基本集中在几个固定位置。我把真实遇到过的对照着写,方便你直接定位。
401 Unauthorized。两种可能:一是 TaoToken Key 错,二是网关 Token 错。区分方法很简单,用第四节第一条 curl 单独测 Key,通了就说明是网关 Token 问题。网关 Token 从openclaw.json里取,注意别把mode字段的值也 grep 进去,所以命令里带了grep -v mode。另外 Key 前后有换行或空格也会 401,复制时留意。
local proxy failed。这个多半出在 OpenResty 反代或 WebSocket 升级头上。检查proxy_set_header Upgrade和Connection "upgrade"两行在不在,proxy_pass指向的端口对不对。还有一种情况是OPENCLAW_WEB_URL和实际访问域名不一致,导致allowedOrigins校验失败,表现也是连接被拒。改完环境变量必须重启容器,光改 compose 文件不重启不生效。
reading choices 报错。这通常是模型返回体解析问题,根源在请求参数。检查model字段是不是有效 ID、messages结构对不对、Content-Type有没有带。如果用的是流式,注意客户端要按 SSE 解析,别当普通 JSON 读。
OAuth 相关报错。如果你接的是 Claude Code 这类走 OAuth 的工具,报错往往出在回调地址或 token 交换环节。确认 Base URL 用的是https://taotoken.net/api,不要多加/v1之外的路径。ClaudeCodeAnthropic 的接入细节以文档为准,别凭记忆填。
证书不匹配 / NET::ERR_CERT。自签证书必然报这个,要么换合法证书,要么在测试环境手动信任。换证书时记得先rm -f ./data/openclaw01/ssl/*再放新证书,文件名必须是cert.pem和cert.key,名字错了 OpenResty 起不来。重启后如果还报旧证书,检查是不是浏览器缓存,换个无痕窗口试。
容器起不来 / Docker 内 Docker 异常。先看openclaw-storage这个命名卷在不在,别误删。privileged没开、/lib/modules没挂,容器内 Docker 会直接失败。升级版本时用新镜像 tag 重新docker compose up -d即可,数据在挂载目录和命名卷里,不会丢。
排查顺序建议固定成:先 curl 验 Key → 再验网关 Token → 再看反代头 → 最后看证书。按这个顺序走,基本不会绕圈。
6. 把 Key 收敛到一处之后
整套跑下来,最省心的其实不是 Docker 那层隔离,而是 Key 不再散落。OpenClaw 网关令牌管访问,TaoToken Key 管模型调用,各司其职,轮换时只动一个地方。证书和反代配好之后,OpenClaw 就能以一个相对干净的姿态挂在公网,容器内那套独立 Docker 也不会污染宿主机。
后续如果要扩,比如再加一个编码 Agent 或换模型,直接复用同一个 Base URL 和 Key,只改 Model ID 就行。需要长期跑编码任务的,可以看 Coding Plan;只是临时验证模型的,用模型对话页面更快。接入过程中卡在参数上的,翻接入文档比搜帖子靠谱。