天上不会掉下免费的龙虾。但是,稍许花点时间和精力,我们可以寻找到不少免费的资源,整合在一块就可以搭建一个属于你自己的AI智能体OpenClaw了。例如,底层使用开源的Docker部署,反向代理使用开源的Nginx,不仅性能强大而且免费获取TLS证书并自动更新,另外也有获取各大模型供应商的免费Token(词元)额度的方法。在这篇文章中,带你从头走到底,零元把虾养起。当然,至少得有一个服务器,养鱼养虾总得有个池子吧。
之所以选择在 Docker 中安装 OpenClaw,主要基于四点考虑:
1、环境隔离更安全
容器化部署将 OpenClaw 与宿主机系统严格隔离,即使容器被入侵,攻击者也难以直接访问宿主系统的敏感目录和文件。而且,这也是给自己准备的“后悔药”,君不见有人忙着安装,而装完后因为使用复杂、费用激增等问题又匆匆卸载,上船容易下船难,劳民伤财不说,破坏了宿主机原有的运行环境,可能引发一系列其它问题。容器化部署,随时装随时卸。
2、部署运维更简单
传统部署方式常因 Python 版本不兼容、数据库驱动缺失等问题卡住。Docker 将 OpenClaw 及其所有依赖打包在标准镜像中,真正实现“一次构建,处处运行”。而且可以使用固定版本标签(如 v2026.3.13-1)而非 latest,避免意外升级破坏稳定性,即便问题出现,也可通过 docker stop 和 docker start 秒级切换到旧版本。
3、访问控制更灵活
可为不同渠道(例如Telegram、飞书、微信等)或不同业务场景运行独立的 OpenClaw 容器,互不干扰。某个容器出问题时,不会影响其他服务。
4、资源管理更高效
通过 Docker Compose 可以限制 CPU 和内存上限,防止单个 Agent 耗尽主机资源。Docker 镜像同时支持 x86_64 和 ARM64 架构,无论是 Intel、 Apple Mac、Windows WSL2 还是 Linux 服务器,行为完全一致。这意味着你在一个平台部署成功后,可以较为容易地转移到其它平台。
一句话总结:Docker 让 OpenClaw 从“能跑起来”升级为“能稳定、安全、可控地长期运行”。
言归正传,以下以Ubuntu 24.04.4 LTS 为示例安装环境,带你手把养虾。
一、安装Docker及Docker-compose
sudo apt install docker.io docker-compose
查看版本以确认安装成功:
docker --version
docker-compose --version
二、使用Docker-compose安装OpenClaw
我选择了OpenClaw 的中国IM平台整合Docker版本,其预装并配置了飞书、钉钉、QQ机器人、企业微信等国内主流IM软件的插件,可以快速部署多个国内IM平台,大大方便了后期单独配置的难度。我们使用预构建镜像的方式部署,直接使用 docker-compose.yml 与 .env.example 即可启动。
1、新建并进入工作目录
mkdir docker-openclaw && cd docker-openclaw
2、获取配置文件
wget https://raw.githubusercontent.com/justlovemaki/OpenClaw-Docker-CN-IM/main/docker-compose.yml
wget https://raw.githubusercontent.com/justlovemaki/OpenClaw-Docker-CN-IM/main/.env.example
3、初始化环境变量
cp .env.example .env
4、至少配置以下三个变量:
| 环境变量 | 说明 | 示例 |
|---|---|---|
| MODEL_ID | 默认模型 ID | gpt-4 |
| BASE_URL | 模型服务地址 | https://api.openai.com/v1 |
| API_KEY | 模型服务密钥 | sk-xxx |
一个最小化的示例如下:
MODEL_ID=gpt-4
BASE_URL=https://api.openai.com/v1
API_KEY=sk-xxx`
API_PROTOCOL=openai-completions
CONTEXT_WINDOW=1000000
MAX_TOKENS=8192
5、修改Docker-compose.yml以匹配所需
nano docker-compose.yml
version: '3.8'
x-openclaw-common-env: &openclaw-common-env
TZ: Asia/Shanghai
HOME: /home/node
TERM: xterm-256color
# 配置同步开关
SYNC_OPENCLAW_CONFIG: ${SYNC_OPENCLAW_CONFIG}
# 插件同步配置
SYNC_EXTENSIONS_ON_START: ${SYNC_EXTENSIONS_ON_START}
SYNC_EXTENSIONS_MODE: ${SYNC_EXTENSIONS_MODE}
# 模型配置
SYNC_MODEL_CONFIG: ${SYNC_MODEL_CONFIG}
MODEL_ID: ${MODEL_ID}
PRIMARY_MODEL: ${PRIMARY_MODEL}
IMAGE_MODEL_ID: ${IMAGE_MODEL_ID}
BASE_URL: ${BASE_URL}
API_KEY: ${API_KEY}
API_PROTOCOL: ${API_PROTOCOL}
CONTEXT_WINDOW: ${CONTEXT_WINDOW}
MAX_TOKENS: ${MAX_TOKENS}
# 提供商 2 (可选)
MODEL2_NAME: ${MODEL2_NAME}
MODEL2_MODEL_ID: ${MODEL2_MODEL_ID}
MODEL2_BASE_URL: ${MODEL2_BASE_URL}
MODEL2_API_KEY: ${MODEL2_API_KEY}
MODEL2_PROTOCOL: ${MODEL2_PROTOCOL}
MODEL2_CONTEXT_WINDOW: ${MODEL2_CONTEXT_WINDOW}
MODEL2_MAX_TOKENS: ${MODEL2_MAX_TOKENS}
# 提供商 3 (可选)
MODEL3_NAME: ${MODEL3_NAME}
MODEL3_MODEL_ID: ${MODEL3_MODEL_ID}
MODEL3_BASE_URL: ${MODEL3_BASE_URL}
MODEL3_API_KEY: ${MODEL3_API_KEY}
MODEL3_PROTOCOL: ${MODEL3_PROTOCOL}
MODEL3_CONTEXT_WINDOW: ${MODEL3_CONTEXT_WINDOW}
MODEL3_MAX_TOKENS: ${MODEL3_MAX_TOKENS}
# 提供商 4 (可选)
MODEL4_NAME: ${MODEL4_NAME}
MODEL4_MODEL_ID: ${MODEL4_MODEL_ID}
MODEL4_BASE_URL: ${MODEL4_BASE_URL}
MODEL4_API_KEY: ${MODEL4_API_KEY}
MODEL4_PROTOCOL: ${MODEL4_PROTOCOL}
MODEL4_CONTEXT_WINDOW: ${MODEL4_CONTEXT_WINDOW}
MODEL4_MAX_TOKENS: ${MODEL4_MAX_TOKENS}
# 提供商 5 (可选)
MODEL5_NAME: ${MODEL5_NAME}
MODEL5_MODEL_ID: ${MODEL5_MODEL_ID}
MODEL5_BASE_URL: ${MODEL5_BASE_URL}
MODEL5_API_KEY: ${MODEL5_API_KEY}
MODEL5_PROTOCOL: ${MODEL5_PROTOCOL}
MODEL5_CONTEXT_WINDOW: ${MODEL5_CONTEXT_WINDOW}
MODEL5_MAX_TOKENS: ${MODEL5_MAX_TOKENS}
# 提供商 6 (可选)
MODEL6_NAME: ${MODEL6_NAME}
MODEL6_MODEL_ID: ${MODEL6_MODEL_ID}
MODEL6_BASE_URL: ${MODEL6_BASE_URL}
MODEL6_API_KEY: ${MODEL6_API_KEY}
MODEL6_PROTOCOL: ${MODEL6_PROTOCOL}
MODEL6_CONTEXT_WINDOW: ${MODEL6_CONTEXT_WINDOW}
MODEL6_MAX_TOKENS: ${MODEL6_MAX_TOKENS}
# 通道配置
DM_POLICY: ${DM_POLICY}
GROUP_POLICY: ${GROUP_POLICY}
ALLOW_FROM: ${ALLOW_FROM}
# 电报机器人配置
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN}
TELEGRAM_DM_POLICY: ${TELEGRAM_DM_POLICY}
TELEGRAM_ALLOW_FROM: ${TELEGRAM_ALLOW_FROM}
TELEGRAM_GROUP_POLICY: ${TELEGRAM_GROUP_POLICY}
# 飞书机器人配置
FEISHU_DEFAULT_ACCOUNT: ${FEISHU_DEFAULT_ACCOUNT}
FEISHU_APP_ID: ${FEISHU_APP_ID}
FEISHU_APP_SECRET: ${FEISHU_APP_SECRET}
FEISHU_NAME: ${FEISHU_NAME}
# 飞书机器人多账号 JSON
FEISHU_ACCOUNTS_JSON: ${FEISHU_ACCOUNTS_JSON}
FEISHU_GROUPS_JSON: ${FEISHU_GROUPS_JSON}
FEISHU_DM_POLICY: ${FEISHU_DM_POLICY}
FEISHU_ALLOW_FROM: ${FEISHU_ALLOW_FROM}
FEISHU_GROUP_POLICY: ${FEISHU_GROUP_POLICY}
FEISHU_GROUP_ALLOW_FROM: ${FEISHU_GROUP_ALLOW_FROM}
# 飞书机器人插件配置
FEISHU_OFFICIAL_PLUGIN_ENABLED: ${FEISHU_OFFICIAL_PLUGIN_ENABLED}
FEISHU_STREAMING: ${FEISHU_STREAMING}
FEISHU_REQUIRE_MENTION: ${FEISHU_REQUIRE_MENTION}
# 钉钉配置
DINGTALK_CLIENT_ID: ${DINGTALK_CLIENT_ID}
DINGTALK_CLIENT_SECRET: ${DINGTALK_CLIENT_SECRET}
DINGTALK_ROBOT_CODE: ${DINGTALK_ROBOT_CODE}
DINGTALK_DM_POLICY: ${DINGTALK_DM_POLICY}
DINGTALK_GROUP_POLICY: ${DINGTALK_GROUP_POLICY}
DINGTALK_ALLOW_FROM: ${DINGTALK_ALLOW_FROM}
DINGTALK_CORP_ID: ${DINGTALK_CORP_ID}
DINGTALK_AGENT_ID: ${DINGTALK_AGENT_ID}
DINGTALK_MESSAGE_TYPE: ${DINGTALK_MESSAGE_TYPE}
DINGTALK_CARD_TEMPLATE_ID: ${DINGTALK_CARD_TEMPLATE_ID}
DINGTALK_CARD_TEMPLATE_KEY: ${DINGTALK_CARD_TEMPLATE_KEY}
DINGTALK_MAX_RECONNECT_CYCLES: ${DINGTALK_MAX_RECONNECT_CYCLES}
DINGTALK_DEBUG: ${DINGTALK_DEBUG}
DINGTALK_JOURNAL_TTL_DAYS: ${DINGTALK_JOURNAL_TTL_DAYS}
DINGTALK_SHOW_THINKING: ${DINGTALK_SHOW_THINKING}
DINGTALK_THINKING_MESSAGE: ${DINGTALK_THINKING_MESSAGE}
DINGTALK_ASYNC_MODE: ${DINGTALK_ASYNC_MODE}
DINGTALK_ASYNC_ACK_TEXT: ${DINGTALK_ASYNC_ACK_TEXT}
# 钉钉多机器人 JSON
DINGTALK_ACCOUNTS_JSON: ${DINGTALK_ACCOUNTS_JSON}
# QQ 机器人配置
QQBOT_APP_ID: ${QQBOT_APP_ID}
QQBOT_CLIENT_SECRET: ${QQBOT_CLIENT_SECRET}
QQBOT_DM_POLICY: ${QQBOT_DM_POLICY}
QQBOT_ALLOW_FROM: ${QQBOT_ALLOW_FROM}
QQBOT_GROUP_POLICY: ${QQBOT_GROUP_POLICY}
# QQ 机器人多账号 JSON
QQBOT_BOTS_JSON: ${QQBOT_BOTS_JSON}
# 企业微信配置
WECOM_DEFAULT_ACCOUNT: ${WECOM_DEFAULT_ACCOUNT}
WECOM_ADMIN_USERS: ${WECOM_ADMIN_USERS}
WECOM_COMMANDS_ENABLED: ${WECOM_COMMANDS_ENABLED}
WECOM_COMMANDS_ALLOWLIST: ${WECOM_COMMANDS_ALLOWLIST}
WECOM_DYNAMIC_AGENTS_ENABLED: ${WECOM_DYNAMIC_AGENTS_ENABLED}
WECOM_DYNAMIC_AGENTS_ADMIN_BYPASS: ${WECOM_DYNAMIC_AGENTS_ADMIN_BYPASS}
# 企业微信单账号快捷配置(会写入 defaultAccount 指定的账号)
WECOM_BOT_ID: ${WECOM_BOT_ID}
WECOM_SECRET: ${WECOM_SECRET}
WECOM_WELCOME_MESSAGE: ${WECOM_WELCOME_MESSAGE}
WECOM_SEND_THINKING_MESSAGE: ${WECOM_SEND_THINKING_MESSAGE}
WECOM_DM_POLICY: ${WECOM_DM_POLICY}
WECOM_ALLOW_FROM: ${WECOM_ALLOW_FROM}
WECOM_GROUP_POLICY: ${WECOM_GROUP_POLICY}
WECOM_GROUP_ALLOW_FROM: ${WECOM_GROUP_ALLOW_FROM}
WECOM_WORKSPACE_TEMPLATE: ${WECOM_WORKSPACE_TEMPLATE}
WECOM_AGENT_CORP_ID: ${WECOM_AGENT_CORP_ID}
WECOM_AGENT_CORP_SECRET: ${WECOM_AGENT_CORP_SECRET}
WECOM_AGENT_ID: ${WECOM_AGENT_ID}
WECOM_WEBHOOKS_JSON: ${WECOM_WEBHOOKS_JSON}
WECOM_DM_CREATE_AGENT_ON_FIRST_MESSAGE: ${WECOM_DM_CREATE_AGENT_ON_FIRST_MESSAGE}
WECOM_GROUP_CHAT_ENABLED: ${WECOM_GROUP_CHAT_ENABLED}
WECOM_GROUP_CHAT_REQUIRE_MENTION: ${WECOM_GROUP_CHAT_REQUIRE_MENTION}
WECOM_GROUP_CHAT_MENTION_PATTERNS: ${WECOM_GROUP_CHAT_MENTION_PATTERNS}
WECOM_NETWORK_EGRESS_PROXY_URL: ${WECOM_NETWORK_EGRESS_PROXY_URL}
WECOM_NETWORK_API_BASE_URL: ${WECOM_NETWORK_API_BASE_URL}
# 企业微信多账号 JSON
WECOM_ACCOUNTS_JSON: ${WECOM_ACCOUNTS_JSON}
# NAPCAT 配置
NAPCAT_REVERSE_WS_PORT: ${NAPCAT_REVERSE_WS_PORT}
NAPCAT_DM_POLICY: ${NAPCAT_DM_POLICY}
NAPCAT_ALLOW_FROM: ${NAPCAT_ALLOW_FROM}
NAPCAT_GROUP_POLICY: ${NAPCAT_GROUP_POLICY}
NAPCAT_HTTP_URL: ${NAPCAT_HTTP_URL}
NAPCAT_ACCESS_TOKEN: ${NAPCAT_ACCESS_TOKEN}
NAPCAT_ADMINS: ${NAPCAT_ADMINS}
# 工作空间配置
OPENCLAW_WORKSPACE_ROOT: ${OPENCLAW_WORKSPACE_ROOT}
# Gateway 配置
OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN}
OPENCLAW_GATEWAY_BIND: ${OPENCLAW_GATEWAY_BIND}
OPENCLAW_GATEWAY_PORT: ${OPENCLAW_GATEWAY_PORT}
OPENCLAW_BRIDGE_PORT: ${OPENCLAW_BRIDGE_PORT}
OPENCLAW_GATEWAY_MODE: ${OPENCLAW_GATEWAY_MODE}
OPENCLAW_GATEWAY_ALLOWED_ORIGINS: ${OPENCLAW_GATEWAY_ALLOWED_ORIGINS}
OPENCLAW_GATEWAY_ALLOW_INSECURE_AUTH: ${OPENCLAW_GATEWAY_ALLOW_INSECURE_AUTH}
OPENCLAW_GATEWAY_DANGEROUSLY_DISABLE_DEVICE_AUTH: ${OPENCLAW_GATEWAY_DANGEROUSLY_DISABLE_DEVICE_AUTH}
OPENCLAW_GATEWAY_AUTH_MODE: ${OPENCLAW_GATEWAY_AUTH_MODE}
# 插件控制
OPENCLAW_PLUGINS_ENABLED: ${OPENCLAW_PLUGINS_ENABLED}
# 工具配置
OPENCLAW_TOOLS_JSON: ${OPENCLAW_TOOLS_JSON}
# 沙箱配置
OPENCLAW_SANDBOX_MODE: ${OPENCLAW_SANDBOX_MODE}
OPENCLAW_SANDBOX_SCOPE: ${OPENCLAW_SANDBOX_SCOPE}
OPENCLAW_SANDBOX_WORKSPACE_ACCESS: ${OPENCLAW_SANDBOX_WORKSPACE_ACCESS}
OPENCLAW_SANDBOX_JOIN_NETWORK: ${OpenClaw_SANDBOX_JOIN_NETWORK}
OPENCLAW_SANDBOX_DOCKER_IMAGE: ${OPENCLAW_SANDBOX_DOCKER_IMAGE}
OPENCLAW_SANDBOX_JSON: ${OPENCLAW_SANDBOX_JSON}
AGENT_REACH_ENABLED: ${AGENT_REACH_ENABLED}
AGENT_REACH_USE_CN_MIRROR: ${AGENT_REACH_USE_CN_MIRROR}
networks:
nginx-network:
external: true
name: nginx-network
services:
openclaw-gateway:
container_name: docker-openclaw
image: ${OPENCLAW_IMAGE}
cap_add:
- CHOWN
- SETUID
- SETGID
- DAC_OVERRIDE
# 可选:指定容器运行 UID:GID(例如 1000:1000)
# 默认保持 root 启动,以便 init.sh 自动修复挂载卷权限后再降权运行网关
user: ${OPENCLAW_RUN_USER:-0:0}
environment: *openclaw-common-env
volumes:
- ${OPENCLAW_DATA_DIR}:/home/node/.openclaw
- ${OPENCLAW_DATA_DIR}:/home/node/.openclaw/extensions
# 沙箱支持:如需启用 Docker沙箱,请取消下面一行的注释并确保 .env 中 OPENCLAW_SANDBOX_MODE 不为 off
# - /var/run/docker.sock:/var/run/docker.sock
ports:
- "${DOCKER_BIND:-0.0.0.0}:${OPENCLAW_GATEWAY_PORT}:${OPENCLAW_GATEWAY_PORT}"
- "${DOCKER_BIND:-0.0.0.0}:${OPENCLAW_BRIDGE_PORT}:${OPENCLAW_BRIDGE_PORT}"
init: true
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
compress: "true"
networks:
- nginx-network
restart: unless-stopped
openclaw-installer:
container_name: docker-openclaw-installer
image: ${OPENCLAW_IMAGE}
profiles:
- tools
user: ${OPENCLAW_RUN_USER:-0:0}
environment: *openclaw-common-env
volumes:
- ${OPENCLAW_DATA_DIR}:/home/node/.openclaw
- ${OPENCLAW_DATA_DIR}:/home/node/.openclaw/extensions
entrypoint: ["tail", "-f", "/dev/null"]
init: true
networks:
- nginx-network
restart: 'no'
ports: []
stdin_open: true
tty: true
cap_add:
- CHOWN
- SETUID
- SETGID
- DAC_OVERRIDE
以上docker-compose.yml文件是运行基本部署所需,其中:
(1)行1-177:声明模型及渠道所用的变量,无需更改。
(2)行179-182:此处需要填写OpenClaw和Nginx共同所在的网络名称。我已部署了Nginx在名为“nginx-network”的网络中,所以填写在此处,后面Nginx也会部署在这个网络中。如果没有,可以使用以下命令新建:
docker network create nginx-network
6、运行安装
docker-compose up -d
7、查看安装日志,确保成功安装
docker logs -f docker-openclaw
安装过程中会从其它网站下载所需的一些组件,时间约1-3分钟,视网络情况而定。最终,会呈现类似的结果如图:



三、为什么配置Nginx作为反向代理
为 OpenClaw 配置反向代理(如 Nginx或Caddy)是从个人实验走向稳定、安全、公网可访问的生产环境的关键一步。这不仅能有效解决其默认仅限本地访问的限制,更能带来多方面的显著收益:
1、强化系统安全,构建防线
(1)隐藏真实端口,降低攻击风险:OpenClaw默认运行在如18789或8080等特定端口上。反向代理可以将服务“藏”在标准的443(HTTPS)端口后,并可以关闭原始端口的外部访问,避免被扫描工具发现和恶意攻击。尤其是在当下养虾热度大增的趋势下,各类恶意扫描及泄露事件屡见报端,个人信息一旦泄露则存在重大的安全风险。
(2)实现安全的HTTPS加密:反向代理(配合Certbot等工具)可以轻松为你的域名配置并自动续签SSL/TLS证书,启用强加密的HTTPS访问。这不仅保护了数据传输过程中的隐私,也是现代Web应用的基本要求。
(3)支持高级认证与访问控制:你可以在代理层添加额外的安全层。例如,配置HTTP基本认证(用户名/密码)、基于IP地址的访问白名单、或集成OAuth等身份代理(IdP),实现企业级单点登录。
(4)扮演“信任代理”:OpenClaw官方支持“信任代理”认证模式。在此模式下,你完全可以将用户认证的职责交给反向代理,OpenClaw仅通过代理传递的请求头(如x-forwarded-user)来识别用户,实现认证与业务逻辑的解耦,进一步提升安全性。
2、突破访问限制,实现公网访问
(1)打破本地枷锁:出于安全考虑,OpenClaw的Web UI在原生状态下通常只监听本地地址(localhost或127.0.0.1),这意味着你无法从外部网络直接访问。
(2)开启公网访问之门:反向代理作为沟通内外网的桥梁,将公网请求转发到内网的OpenClaw服务上。这样,无论你身在何处,都可以通过互联网安全地访问和管理自己的OpenClaw实例。这对于将OpenClaw集成到飞书、钉钉、企业微信等需要公网回调地址的IM机器人中至关重要。
3、增强服务稳定性与性能
(1)负载均衡,支持高并发:如果你的OpenClaw服务需要处理大量请求,反向代理可以将流量分发到后端的多个OpenClaw实例上。这不仅能避免单个实例过载,还能实现高可用性,即使某个实例宕机,服务也不会中断。
(2)连接管理与优化:代理服务器可以优化与管理客户端与后端服务之间的连接。例如,通过正确配置Upgrade头来支持WebSocket连接,这对于OpenClaw Control UI等需要实时通信的功能至关重要。同时,你还可以配置超时、缓冲区和重试策略,提升访问体验。
(3)请求限流与防护:可以在代理层轻松配置请求速率限制(rate limiting),例如限制单个IP每秒的请求数,防止API被滥用或遭受暴力破解攻击。
4、简化运维与架构
(1)集中式日志与监控:Nginx等代理服务器可以生成详细的访问日志(包括请求来源、响应状态、耗时等),方便你进行审计、分析和调试。
(2)统一证书与入口管理:所有Web服务的TLS证书都可以在代理层统一管理和续签,而无需在每个后端应用中单独配置,极大简化了运维复杂度。
(3)平滑的架构演进:采用代理模式后,你的应用架构清晰分层。未来如需升级、替换或扩展后端服务,只需调整代理配置即可,对前端完全透明,非常利于架构的演进。
综上,采用反向代理是构建理想架构的一个重要考量。而至于为什么采用老牌王者Nginx而不是新秀Caddy,此话暂且留在文末,还是先来看如何配置Nginx。
四、安装及配置Nginx
在很长一段时间以来,TLS证书的管理都是Nginx比较头痛的地方,不光要自己去第三方购买证书,还要手工配置,而且需要定期更新。这也是能够全自动申请免费TLS证书的后起之秀Caddy获得一席之地的原因之一。
但是自从Nginx支持原生HTTP-01 ACME功能后,情况则不一样了。籍此文之力,我们得以成功实施。
在此之前,你需要有自己的域名,且指向服务器所在的公网IP地址的A记录已生效。因为安装Nginx时会同时申请免费的TLS证书,需要验证域名的有效性。以上确认无误后,执行以下操作:
1、克隆 nginx-acme 和 nginx 源码仓库
git clone https://github.com/nginx/nginx-acme.git
git clone https://github.com/nginx/nginx.git
2、将 Nginx Git 仓库检出至 1.28 版本
cd nginx/
git checkout release-1.28.0
cd ../
3、创建一个包含以下指令的 Dockerfile
nano Dockerfile
# [Stage 1] Build the dynamic Nginx module - ngx_http_acme_module.so
FROM rust:1.89-bookworm AS nginx-http-acme-mod
RUN apt-get update && apt-get install --yes --no-install-recommends --no-install-suggests \
libclang-dev \
libpcre2-dev \
libssl-dev \
zlib1g-dev \
pkg-config \
git \
grep \
gawk \
gnupg2 \
sed \
make \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /nginx
COPY ./nginx /nginx
COPY ./nginx-acme /mod
WORKDIR /nginx
RUN auto/configure \
--with-compat \
--with-http_ssl_module \
--add-dynamic-module=/mod \
&& make modules
# [Stage 2] Build the release image with the added ACME module
FROM nginx:1.28-bookworm AS uservice-proxy
LABEL org.opencontainers.image.authors="cgoesc2@wgu.edu"
LABEL maintainer="Christian Goeschel Ndjomouo <cgoesc2@wgu.edu>"
LABEL description="uServices Nginx Reverse Proxy"
WORKDIR /
RUN apt-get update && apt-get install --yes --no-install-recommends --no-install-suggests \
nginx \
bash \
libssl-dev
# Delete the apt repository caches
RUN rm -rf /var/lib/apt/lists/*
# Copy nginx HTTP ACME module to release image
COPY --from=nginx-http-acme-mod /nginx/objs/ngx_http_acme_module.so /usr/lib/nginx/modules/
# Expose HTTPS port for all the configured vhosts
EXPOSE 443/tcp
# Port 80 is needed so that the ACME module can process
# the ACME HTTP-01 challenges
EXPOSE 80/tcp
# Start Nginx in the foreground
# Will show log output with 'docker logs -f container_name'
CMD ["nginx", "-g", "daemon off;"]
4、建立映像
docker build -t nginx-with-acme-mod .
5、部署模块
nano docker-compose.yml
services:
docker-nginx:
build:
context: .
dockerfile: Dockerfile
target: uservice-proxy
container_name: docker-nginx
networks:
- nginx-network
ports:
- "443:443/tcp"
- "80:80/tcp"
volumes:
- ./nginx-proxy.conf:/etc/nginx/conf.d/default.conf:ro
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./acme-data:/etc/nginx/acme:rw
restart: unless-stopped
networks:
nginx-network:
external: true
6、在同一目录下创建一个名为 “acme-data” 的目录,用于存放 ACME 账户密钥、PKIX 证书及私钥。
mkdir acme-data/
7、创建首个配置文件 nginx.conf
nano nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /run/nginx.pid;
load_module modules/ngx_http_acme_module.so;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
8、创建第二个配置文件 nginx-proxy.conf
nano nginx-proxy.conf
resolver 127.0.0.11 ipv6=off valid=5s;
acme_issuer letsencrypt {
uri https://acme-v02.api.letsencrypt.org/directory;
contact youremail@youremail.com; #真实可用的邮箱地址
state_path /etc/nginx/acme;
accept_terms_of_service;
ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
ssl_verify off;
}
acme_shared_zone zone=ngx_acme_shared:256k;
server {
# listener on port 80 is required to process ACME HTTP-01 challenges
listen 80;
location / {
return 404;
}
}
# 假设你的域名是openclaw.yourdomain.com
server {
set $forward_scheme http;
set $server "docker-openclaw";
set $port 18789;
listen 443 ssl;
server_name openclaw.yourdomain.com; # 替换为你的域名
client_max_body_size 100M; # 根据需求调整,支持文件上传
# SSL 证书(使用 acme 自动申请)
ssl_certificate_cache max=2;
acme_certificate letsencrypt key=rsa;
ssl_certificate $acme_certificate;
ssl_certificate_key $acme_certificate_key;
# SSL 协议配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# 基础代理头
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
# WebSocket 支持(OpenClaw所需)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 超时设置(如果OpenClaw处理长任务,可灵活调整)
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 缓冲设置(适合 API 响应)
proxy_buffering off;
proxy_cache off;
location / {
proxy_pass $forward_scheme://$server:$port;
# 可选:如果 OpenClaw 有静态文件路径,可以单独处理
# location /static/ {
# proxy_pass $forward_scheme://$server:$port/static/;
# expires 30d;
# }
}
# 健康检查端点
location /health {
proxy_pass $forward_scheme://$server:$port/health;
access_log off;
}
}
9、运行docker-compose以安装
docker-compose up -d
五、调试OpenClaw
如果以上一切顺利,且一些基本的参数已配置,如模型所需的API信息等真实有效,那么正常来讲就搭建好了。当然,这只是一个基本框架,象房子一样,你还要进一步装修。这其中会涉及一些技巧或踩过的坑,在此列举如下:
1、关于养虾成本
装虾几百块是一次性费用,看看这篇教程就省下了,那后续养虾的费用可是绵延无绝期。其实也是有办法解决的,免费 token还是有的,只需花些时间注册即可,参照此文亲测可用。在做此篇教程时所用的API即出自Groq、智谱AI两家,当然,也需要注意每家都会有API并发限值,测试阶段问题不大。
2、首次登录时,可能会遇到“pairing required”报错,原因是需要对首次连接的设备进行批准。可以执行以下操作:
(1)查看Request
docker exec -it docker-openclaw openclaw devices list
(2)复制Request并批准。例如Request为33776de5-18bd-47ea-b4d4-ec627422a75c,
docker exec -it docker-openclaw openclaw devices approve 33776de5-18bd-47ea-b4d4-ec627422a75c
六、再来说一说前文提及的选择Nginx的Caddy问题
很长一段时间以来,我都是采用Nginx作为前端代理。自从接触Caddy后,对其自动获取TLS证书、极简的配置印象深刻,遂将一些后端应用逐渐转移到了Caddy,此次部署OpenClaw的实验也是最先在Caddy上搭建的。
其中需要考虑的是OpenClaw有对WebSocket的要求。Nginx需要对WebSocket进行相应的配置。而Caddy原生支持WebSocket,无需任何特殊配置,开箱即用。Caddy会自动检测 WebSocket 升级标头并正确处理。再也不用费力地在文档中查找正确的代理标头了,社区问答中对此也作出了解释:WebSocket upgrades are automatic. Remove all the @websocket stuff in your config… and just have your client connect with wss:// and it will be proxied to your backend as a websocket.”。
这也使我相信Caddy+OpenClaw是一个可行的方案,而经过反复多天的实验,最终也没能成就这个方案,反而是Nginx救我于水火。对照分别部署在Nginx和Caddy上的OpenClaw,可以看出它们的不同行为。
(1)深色背景图为Caddy所为,报错为“disconnected (1006): no reason”,比较笼统的没有原因。WebSocket请求显示为“Finished”,但是并没有显示响应代码。“finished” 状态不代表请求成功,只代表请求/响应过程在浏览器/客户端层面已结束。

(2)浅色背景图为Nginx所为,WebSocket显示成功向后端请求,且响应代码为“101 Switching Protocols”,此代码是WebSocket 握手成功的关键标志。

经过分析,感觉此问题可能与以下两个或其一相关:
(1)Caddy配置:Caddy 默认启用 HTTP/2,而 WebSocket 握手最初是 HTTP/1.1 格式。Caddy 严格的 HTTP/2 检查机制可能会在升级完成前就终止连接。实验所使用的Caddy版本为v2.10.2,有可能产生这方面的问题。
(2)Docker 网络隔离:在 docker-compose.yml 中,如果 OpenClaw 容器绑定了 127.0.0.1,Caddy 容器是无法访问的。即使 Caddy 配置正确,也会因连接被拒而断开。但这仅是猜测,因为在Nginx和Caddy中所使用的docker-compose.yml文件是相同的。
最终,通过再次实验得知,问题与(1)相关。附上通过测试的配置文件供参考:
# 你的域名openclaw.yourdomain.com
openclaw.yourdomain.com {
reverse_proxy docker-openclaw:18789 {
# 强制 HTTP/1.1(WebSocket 必需)
transport http
# 传递标准代理头
header_up Host {host}
header_up X-Real-IP {remote}
header_up X-Forwarded-For {remote}
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-Host {host}
header_up X-Forwarded-Port {server_port}
}
}
最后,感谢文中所涉及资源的创作分享者,你们的分享让价值延续且增值。
龙虾里有句口号很棒,大意是:如果成功了,那就是生产力。如果没有,那就收获了学习和经验。
深以为是。

