零元养虾记—Nginx+OpenClaw容器化部署教程

天上不会掉下免费的龙虾。但是,稍许花点时间和精力,我们可以寻找到不少免费的资源,整合在一块就可以搭建一个属于你自己的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默认模型 IDgpt-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容器化部署教程-1

零元养虾记—Nginx+OpenClaw容器化部署教程-2

零元养虾记—Nginx+OpenClaw容器化部署教程-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” 状态不代表请求成功,只代表请求/响应过程在浏览器/客户端层面已结束。

零元养虾记—Nginx+OpenClaw容器化部署教程-4

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

零元养虾记—Nginx+OpenClaw容器化部署教程-5

经过分析,感觉此问题可能与以下两个或其一相关:

(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}
    }
}

最后,感谢文中所涉及资源的创作分享者,你们的分享让价值延续且增值。

龙虾里有句口号很棒,大意是:如果成功了,那就是生产力。如果没有,那就收获了学习和经验。

深以为是。