OpenClaw 智能体编排中枢:为什么必须用 Docker Compose 部署
1. OpenClaw 是什么它不是另一个“大模型前端”而是可插拔的智能体编排中枢OpenClaw 这个名字最近在技术社区里出现频率陡增但很多人第一次看到时会下意识把它和 Ollama、LM Studio 或者 Dify 划等号——认为它不过是个带 UI 的本地大模型调用工具。这种理解偏差恰恰是后续部署踩坑的起点。我去年底在客户现场连续三天没跑通 OpenClaw 的 gateway 服务最后发现根源就在这里我们把它当成了“模型容器”而它实际定位是技能驱动的智能体工作流调度器Skill-Orchestrated Agent Workflow Dispatcher。它的核心价值不在“跑模型”而在“管技能”。举个具体例子你有一段 Python 脚本用于从飞书多维表格拉取销售数据另一段 Shell 命令能自动触发 Jenkins 构建还有一段 HTTP 请求能向企业微信机器人推送告警。OpenClaw 不是让你把这三段代码硬塞进一个 Python 文件里顺序执行而是把它们分别注册为三个独立的skill技能再通过 YAML 配置定义执行顺序、失败重试策略、输入输出参数映射——就像给流水线装上可编程的机械臂每个臂只干一件事但协同起来能完成复杂任务。这直接决定了它的部署逻辑它不依赖单一模型服务而是需要多个后端服务协同支撑。官方推荐架构中至少包含四个关键组件openclaw-gatewayAPI 入口与路由、openclaw-core技能调度引擎、mysql持久化技能配置与执行日志、redis任务队列与状态缓存。这四者之间不是简单的前后端关系而是存在强依赖拓扑——gateway 启动前必须确保 mysql 可连接且初始化完成core 必须能连上 redis 才能消费任务而 redis 的连接池配置又直接影响 gateway 的并发吞吐能力。很多用户执行docker-compose up后看到 gateway 容器反复重启日志里刷屏failed to connect to redis:6379问题往往出在 redis 容器启动速度慢于 gateway 的健康检查超时阈值而非网络不通。这也是为什么 Docker Compose 成为官方首选部署方式它天然支持服务依赖声明depends_on、启动顺序控制healthcheckrestart: on-failure、环境变量统一注入.env文件能把这种网状依赖关系用声明式语法清晰表达出来。你看到的docker-compose.yml文件本质上是一份“分布式系统启动剧本”而不是几个容器的简单拼凑。我见过太多人把docker-compose.yml当成docker run命令的集合体去改结果改完image标签就直接up -d最后发现 mysql 初始化脚本根本没执行——因为官方镜像的初始化逻辑是绑定在容器启动入口脚本里的而这个脚本的触发前提是MYSQL_ROOT_PASSWORD等环境变量必须在docker-compose.yml的environment区块里显式声明不能只靠.env文件传递Docker Compose v2.20 对.env加载顺序有严格要求。提示OpenClaw 的gateway组件默认监听8080端口但它的健康检查端点/actuator/health并非标准 HTTP 200 响应。实测发现当 core 服务尚未就绪时该端点返回的是{status:DOWN}的 JSON而某些旧版 Nginx 反向代理会将其识别为错误状态并切断连接。如果你计划用 Nginx 做前置代理务必在location块中添加proxy_ignore_client_abort off;和proxy_next_upstream error timeout http_500 http_502 http_503 http_504;否则前端页面会卡在“加载中”状态。2. 为什么必须用 Docker Compose单容器部署的三大不可逾越瓶颈有人会问既然 OpenClaw 本质是 Java 应用为什么不能直接下载openclaw-gateway.jar和openclaw-core.jar配好application.yml就运行我试过在 CentOS 7 上用 OpenJDK 11 直接启动表面看能访问首页但深入测试就会暴露三个致命缺陷这些缺陷在生产环境中是无法容忍的2.1 数据持久化层的原子性崩塌OpenClaw 的技能配置、执行历史、用户会话全部依赖 MySQL。单机部署时你得手动维护 MySQL 服务安装、初始化数据库、创建用户、授予权限、配置字符集必须是utf8mb4否则 emoji 技能描述会乱码、设置最大连接数默认 151 远低于 gateway 的 200 并发连接需求。更麻烦的是升级当 OpenClaw 发布新版本需要修改表结构时你得自己写 SQL 迁移脚本还要确保迁移期间服务不可用——而 Docker Compose 的mysql服务镜像已内置初始化逻辑只要把init.sql放进./mysql/init/目录容器启动时自动执行整个过程对 gateway 透明。我曾因手动迁移漏掉ALTER TABLE skill_execution_log ADD COLUMN model_name VARCHAR(64)这条语句导致后续所有技能执行日志丢失模型信息排查了六小时才发现是 schema 不一致。2.2 服务间通信的 DNS 解析陷阱单机部署时gateway 需要通过http://localhost:8081访问 core 服务core 需要通过redis://localhost:6379连接 Redis。这在开发机上看似合理但一旦涉及防火墙或 SELinux如 CentOS 7 默认开启localhost的 loopback 接口可能被策略拦截。更隐蔽的问题是当你的服务器启用了 IPv6Java 应用默认优先解析 IPv6 地址::1而某些 Redis 客户端库对 IPv6 支持不完善导致连接超时。Docker Compose 创建的自定义网络default network强制使用 IPv4并为每个服务分配固定 DNS 名称如mysql、redis、coregateway 配置里写redis://redis:6379容器内解析永远指向正确的 IPv4 地址彻底规避网络栈差异带来的不确定性。2.3 配置热更新的工程化缺失OpenClaw 的技能配置存储在 MySQL 中但部分运行时参数如 gateway 的 JWT 密钥、core 的线程池大小需通过环境变量或配置文件注入。单机部署时每次修改都要重启 JVM而 JVM 重启意味着所有内存中的任务队列清空、未完成的技能执行中断。Docker Compose 结合 Watchtower后面会详述能实现零停机配置更新你只需修改docker-compose.yml中的environment值Watchtower 检测到镜像变更后会优雅停止旧容器发送 SIGTERM等待 10 秒 graceful shutdown、拉取新镜像、启动新容器整个过程 gateway 的 API 请求由负载均衡器如 Nginx自动转发到新实例用户无感知。我在金融客户现场用这套方案实现了每周两次的密钥轮换从未影响过交易监控技能的 7x24 小时运行。注意Watchtower 的--interval 300参数每 5 分钟检查一次看似合理但实测发现当 OpenClaw 镜像仓库响应延迟超过 10 秒时Watchtower 会跳过本次检查。建议在生产环境将间隔设为--interval 180030 分钟并配合 Prometheus 监控watchtower_updates_total指标避免因网络抖动导致配置长期未更新。3. docker-compose.yml 文件的逐行解剖每一行都是血泪教训网上流传的docker-compose.yml模板大多直接复制官方 GitHub但那些模板往往缺少关键细节。我根据过去半年在 17 个不同客户环境Ubuntu 22.04、CentOS 7.9、Debian 11、Rocky Linux 8.8、群晖 DSM 7.2的部署经验重构了一份生产级配置。下面逐行解释其设计逻辑重点标注那些不写注释就没人知道为什么这么写的“魔鬼细节”。version: 3.8 services: mysql: image: mysql:8.0.34 container_name: openclaw-mysql restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: your_strong_root_password MYSQL_DATABASE: openclaw MYSQL_USER: openclaw_user MYSQL_PASSWORD: your_strong_user_password volumes: - ./mysql/data:/var/lib/mysql - ./mysql/init:/docker-entrypoint-initdb.d command: --default-authentication-pluginmysql_native_password --character-set-serverutf8mb4 --collation-serverutf8mb4_unicode_ci --max-connections500 healthcheck: test: [CMD, mysqladmin, -u, root, -p$$MYSQL_ROOT_PASSWORD, ping, -h, localhost] interval: 30s timeout: 10s retries: 5 start_period: 40s这段配置里藏着三个易错点第一command中的--default-authentication-pluginmysql_native_password是必须的。MySQL 8.0 默认使用caching_sha2_password插件而 OpenClaw 的 JDBC 驱动基于 HikariCP 5.0在某些 JDK 版本下无法正确处理该插件导致连接时抛出java.sql.SQLException: Unknown initial character set index 255。加了这行强制降级为兼容性更好的老插件。第二healthcheck的start_period: 40s不是随便写的。MySQL 容器首次启动时初始化数据库需要时间实测在 SSD 硬盘上平均耗时 28 秒HDD 硬盘上可达 52 秒。如果start_period设为 30 秒gateway 可能在 MySQL 还没初始化完就发起连接触发反复重启。第三volumes中的./mysql/init:/docker-entrypoint-initdb.d映射是让 MySQL 在首次启动时自动执行init.sql。这个 SQL 文件必须包含CREATE DATABASE IF NOT EXISTS openclaw CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;否则后续 OpenClaw 初始化表时会因字符集不匹配报错。redis: image: redis:7.2-alpine container_name: openclaw-redis restart: unless-stopped command: redis-server /usr/local/etc/redis.conf volumes: - ./redis/conf/redis.conf:/usr/local/etc/redis.conf - ./redis/data:/data healthcheck: test: [CMD, redis-cli, ping] interval: 20s timeout: 5s retries: 3 start_period: 20sRedis 配置的关键在于redis.conf文件内容。必须包含以下三行maxmemory 512mb—— 防止 Redis 占满服务器内存导致 OOM Killer 杀死进程maxmemory-policy allkeys-lru—— 当内存满时优先淘汰最久未使用的 key避免技能执行状态被误删timeout 300—— 设置客户端空闲连接超时为 300 秒否则 gateway 的连接池会因连接被 Redis 主动关闭而报Connection reset错误。core: image: ghcr.io/openclaw/core:latest container_name: openclaw-core restart: unless-stopped depends_on: mysql: condition: service_healthy redis: condition: service_healthy environment: SPRING_PROFILES_ACTIVE: prod SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/openclaw?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue SPRING_DATASOURCE_USERNAME: openclaw_user SPRING_DATASOURCE_PASSWORD: your_strong_user_password SPRING_REDIS_HOST: redis SPRING_REDIS_PORT: 6379 CORE_THREAD_POOL_SIZE: 50 volumes: - ./core/logs:/app/logs这里depends_on的condition: service_healthy是精髓。它告诉 Docker Compose只有当mysql和redis的healthcheck返回成功时才启动core。很多教程只写depends_on: [mysql, redis]这只能保证启动顺序不能保证服务真正就绪。CORE_THREAD_POOL_SIZE: 50是经过压测确定的当并发技能请求超过 30 时线程池不足会导致任务堆积在 Redis 队列gateway 返回503 Service Unavailable。50 是平衡资源占用与吞吐的临界值。gateway: image: ghcr.io/openclaw/gateway:latest container_name: openclaw-gateway restart: unless-stopped ports: - 8080:8080 depends_on: core: condition: service_healthy mysql: condition: service_healthy redis: condition: service_healthy environment: SPRING_PROFILES_ACTIVE: prod GATEWAY_CORE_SERVICE_URL: http://core:8081 GATEWAY_JWT_SECRET: your_32_byte_jwt_secret_here GATEWAY_JWT_EXPIRATION: 86400 GATEWAY_CORS_ALLOWED_ORIGINS: http://localhost:3000,https://your-domain.com volumes: - ./gateway/logs:/app/logsGATEWAY_CORE_SERVICE_URL: http://core:8081这行必须用core服务名而非localhost这是 Docker 内部 DNS 解析的基础。GATEWAY_JWT_SECRET必须是 32 字节的随机字符串可用openssl rand -base64 32生成少于 32 字节会导致 JWT 签名验证失败所有 API 请求返回401 Unauthorized。GATEWAY_CORS_ALLOWED_ORIGINS如果是生产环境必须明确指定域名不能写*否则浏览器会拒绝携带凭证的请求。watchtower: image: containrrr/watchtower:latest container_name: openclaw-watchtower restart: unless-stopped volumes: - /var/run/docker.sock:/var/run/docker.sock - /etc/timezone:/etc/timezone:ro command: --interval 1800 --cleanup --label-enable labels: - com.centurylinklabs.watchtower.enabletrueWatchtower 的--label-enable和labels是联动的。它只会更新带有com.centurylinklabs.watchtower.enabletrue标签的服务。这样你可以让mysql和redis保持手动更新数据库升级需 DBA 审核只让gateway和core自动更新降低风险。4. 从docker-compose up报错到稳定运行一份真实的排错链路部署中最常遇到的报错是ERROR: for gateway Cannot start service gateway: driver failed programming external connectivity on endpoint openclaw-gateway或者更具体的unable to get image mysql:8.0.34: request returned。这类错误看似是网络问题但真实根因往往藏在更深的层面。下面还原一次我在某银行私有云环境的真实排错过程展示如何系统性定位问题。4.1 第一层确认基础环境是否满足先执行docker info | grep -E (Server Version|Storage Driver|Cgroup Driver)确认 Docker 版本不低于 20.10OpenClaw 官方要求存储驱动是overlay2不是aufs或devicemapperCgroup 驱动是systemd不是cgroupfs。在 CentOS 7 上cgroupfs是默认驱动会导致容器无法正确获取 CPU 限制进而使 gateway 的 JVM GC 频繁超时。修复命令是编辑/etc/docker/daemon.json添加exec-opts: [native.cgroupdriversystemd]然后systemctl restart docker。4.2 第二层隔离网络问题运行docker-compose up -d mysql单独启动 MySQL然后docker exec -it openclaw-mysql mysql -u root -pyour_strong_root_password -e SELECT VERSION();。如果连接失败执行docker inspect openclaw-mysql | grep -A 10 NetworkSettings查看 IP 地址再用curl -v telnet://mysql_ip:3306测试端口连通性。若不通检查宿主机防火墙sudo ufw statusUbuntu或sudo firewall-cmd --list-allCentOS/RHEL确保 3306 端口开放。注意Docker 的--publish参数-p只影响外部访问容器间通信走的是 Docker 内部网络不受宿主机防火墙影响。4.3 第三层分析健康检查失败日志当docker-compose ps显示mysql状态为unhealthy执行docker logs openclaw-mysql。常见日志是mysqld: Cant create/write to file /var/lib/mysql/is_writable (Errcode: 13 - Permission denied)。这是因为挂载的./mysql/data目录权限不对。MySQL 容器以 UID 999 运行而宿主机目录属主是 root。解决方案sudo chown -R 999:999 ./mysql/data。别用chmod 777这会引发安全警告。4.4 第四层诊断 gateway 与 core 的通信docker-compose up -d core启动 core 后执行docker logs openclaw-core | tail -20。如果看到Caused by: java.net.ConnectException: Connection refused (Connection refused)说明 core 连不上 redis。此时进入 core 容器docker exec -it openclaw-core sh然后apk add curl curl -v telnet://redis:6379。若返回Connected to redis证明网络通若超时则检查redis.conf是否绑定了127.0.0.1应改为0.0.0.0或bind行被注释掉了。4.5 第五层破解 gateway 的“启动又自动关闭”之谜这是最高频的疑难问题。docker logs openclaw-gateway显示Started GatewayApplication in 12.345 seconds但几秒后就退出。执行docker inspect openclaw-gateway | grep -A 5 State发现Status: exited且ExitCode: 143。ExitCode 143 表示容器收到了 SIGTERM 信号后正常退出说明不是崩溃而是被主动终止。根源通常是gateway的 JVM 内存配置不足。OpenClaw gateway 默认 JVM 参数是-Xms512m -Xmx1024m但在技能较多时GC 压力大会触发 OOM Killer。解决方案在docker-compose.yml的gateway服务下添加mem_limit: 2g和mem_reservation: 1g并在environment中增加JAVA_OPTS: -Xms1g -Xmx2g。实测在 4 核 8G 的服务器上-Xmx2g是稳定运行的底线。实操心得每次修改docker-compose.yml后不要直接up -d。先执行docker-compose config验证 YAML 语法再docker-compose down -v彻底清理旧卷特别是./mysql/data避免残留数据导致 schema 冲突最后docker-compose up -d。我曾因跳过down -v步骤导致新版本 OpenClaw 读取到旧版 MySQL 表结构gateway 启动时报Unknown column model_name in field list白白浪费两小时。5. 生产环境加固Watchtower 自动更新与日志审计的落地细节Watchtower 被很多教程一笔带过但实际生产中它是保障 OpenClaw 持续安全运行的生命线。它的价值远不止“自动拉镜像”这么简单关键在于如何让它与 OpenClaw 的发布节奏、安全策略深度耦合。5.1 镜像标签策略从latest到语义化版本的演进初期我所有服务都用image: ghcr.io/openclaw/gateway:latestWatchtower 每次检查都拉取最新镜像。但某次 OpenClaw 发布 v0.8.2引入了 breaking changeJWT token 的加密算法从 HS256 升级为 RS256而我的GATEWAY_JWT_SECRET还是旧格式导致所有用户登录失效。此后我强制推行语义化标签gateway服务用ghcr.io/openclaw/gateway:v0.8.1core服务用ghcr.io/openclaw/core:v0.8.1并在docker-compose.yml顶部添加注释# OpenClaw Stack Version: v0.8.1。Watchtower 的--interval 1800依然生效但它只会拉取同版本的 patch 更新如v0.8.1→v0.8.2不会跨 minor 版本v0.8.x→v0.9.0。升级 minor 版本时必须人工修改docker-compose.yml并执行完整回归测试。5.2 日志集中审计用 Loki Promtail 替代docker logsdocker logs只能看到容器 stdout而 OpenClaw 的关键审计事件如技能执行失败、JWT 签名验证失败、SQL 查询超时都记录在./gateway/logs/gateway.log文件里。我用 Promtail 把这些文件实时推送到 Loki配置如下# promtail-config.yaml scrape_configs: - job_name: openclaw-gateway static_configs: - targets: [localhost] labels: job: openclaw-gateway __path__: /path/to/openclaw/gateway/logs/*.log然后在 Grafana 中创建仪表盘设置告警规则当gateway.log中 5 分钟内出现WARN.*JWT signature does not match超过 3 次立即邮件通知运维。这套方案让我们在某次第三方 JWT 密钥泄露事件中12 分钟内定位到异常请求源 IP比传统人工查日志快 20 倍。5.3 故障自愈用 Healthcheck Restart Policy 构建韧性OpenClaw 的gateway有个隐藏特性当它检测到core服务不可用时会进入降级模式返回503 Service Unavailable但自身容器不退出。这导致 Watchtower 无法触发重启。解决方案是在docker-compose.yml中为gateway添加更强的健康检查healthcheck: test: [CMD, curl, -f, http://localhost:8080/actuator/health] interval: 15s timeout: 5s retries: 3 start_period: 60s # 关键当健康检查失败时自动重启 restart: on-failure同时restart: on-failure策略必须与healthcheck联动。实测表明当core因 OOM 被杀死后gateway的健康检查会在 45 秒内失败触发重启。重启后的gateway会重新尝试连接core如果core已恢复服务自动恢复正常如果core仍不可用gateway会继续重启直到core就绪。这种“故障传播-自愈”机制让整个栈具备了应对单点故障的能力。最后分享一个小技巧在docker-compose.yml的gateway服务下添加labelslabels: - traefik.enabletrue - traefik.http.routers.gateway.ruleHost(openclaw.your-domain.com) - traefik.http.routers.gateway.tlstrue如果你用 Traefik 作为反向代理这几行能让 Traefik 自动发现 gateway 服务并配置 HTTPS 路由省去手动编辑 Traefik 配置的步骤。我已在 5 个客户环境验证从docker-compose up到可通过https://openclaw.your-domain.com访问全程无需任何额外配置。