Appearance
第15章 部署与运维
"造就'笔记本上跑得通'与'生产环境中可靠'之间差异的,不是某个大功能,而是一百个小细节——日志脱敏、优雅重启、非 root 执行、供应链验证。每个细节看起来都微不足道,直到你在凌晨三点需要它们。"
本章要点
- 掌握三种部署路径:裸机、Docker 多阶段构建、云平台
- 深入日志系统:结构化日志、敏感信息脱敏、滚动策略
- 理解重启管理与可观测性设计的工程实践
- 掌握 Dockerfile 构建优化的关键技巧
上一章我们掌握了与 OpenClaw 交互的方式——CLI 和 TUI。但无论界面多么精美,如果系统跑不起来、跑不稳定,一切都是空谈。本章从开发环境走向生产环境,解决"最后一公里"的工程问题。
在第 2 章的消息旅程中,我们追踪了一条消息从通道进入到响应返回的完整路径。那条路径假设 Gateway 已经在稳定运行。本章关注的是"旅程之前的旅程"——Gateway 如何被部署、如何保持运行、如何在出错时恢复、如何在不停机的情况下更新。
15.1 从演示到生产
15.1.1 鸿沟有多大
你的 Agent 在笔记本上完美运行。现在挑战来了:把它部署到 24/7 运行的云服务器上,服务跨通道用户,经受重启考验,产生干净日志而不泄露 API 密钥。
从"能用"到"稳定运行",就像从搭一座积木桥到建一座真正的桥——材料可能一样,但工程标准完全不同。积木桥倒了重搭就好,真正的桥上面跑着几千辆车。
15.1.2 生产环境的六个隐性需求
开发环境不需要但生产环境必须有的:
- 进程管理:崩溃后自动重启,优雅关闭不丢数据。
- 日志系统:结构化日志、自动脱敏、大小限制、滚动归档。
- 安全加固:非 root 运行、文件权限收紧、供应链验证。
- 资源控制:内存限制、磁盘配额、网络安全组。
- 可观测性:健康检查端点、状态监控、告警集成。
- 可重现性:相同的代码 + 相同的配置 = 相同的行为。
每一个需求看起来都"微小",但忽略其中任何一个都可能招来凌晨三点的告警电话。
🔥 深度洞察:部署是免疫系统,不是包装纸
大多数开发者把部署当作"把代码包装好放到服务器上"——一个后期附加步骤。这是根本性的误解。正确的类比来自医学:部署不是包装纸,而是免疫系统。人体的免疫系统不是在皮肤之外运作的,它渗透在每一个器官、每一条血管中——白细胞在血液中巡逻,淋巴结分布在全身,皮肤本身就是第一道屏障。同样,OpenClaw 的"生产就绪"不是最后一步涂上的外壳,而是从第一行代码就开始渗透的品质:日志脱敏规则决定了配置文件的格式(SecretRef 而非明文),重启延迟策略影响了 LLM 调用的超时设计,Docker 的非 root 约束影响了文件路径的选择。生产环境不会原谅"先跑通再加固"的思维——因为加固不是附加层,而是骨骼结构本身。
15.1.3 部署架构的演进
OpenClaw 的部署模型经历了三个阶段的演进,每个阶段对应了用户群体和使用场景的扩大:
第一阶段:裸机直接运行。开发者在自己的机器上 npm install -g openclaw && openclaw gateway start。简单直接,但没有进程管理——终端关了,Agent 就停了。
第二阶段:系统服务化。引入 src/daemon/ 模块,支持 systemd/launchd/schtasks 三平台服务管理。Agent 变成了真正的守护进程——开机自启、崩溃重启、后台运行。这是大多数个人用户和小团队的最佳选择。
第三阶段:容器化部署。Docker 镜像 + Compose 编排。适合需要环境隔离、可复现部署或云平台部署的场景。代价是额外的容器管理开销。
为什么三个阶段共存而不是只保留最新的? 因为不同用户有不同的运维能力和部署需求:
- 个人开发者用裸机模式就够了——简单、直接、零开销
- 小团队用系统服务——可靠但不需要 Docker 知识
- 企业部署用容器——环境隔离、CI/CD 集成、基础设施即代码
OpenClaw 不强制用户接受不需要的复杂性。
关键概念:渐进式部署(Progressive Deployment) OpenClaw 的部署模型遵循渐进式复杂度原则——从最简单的裸机全局安装(零 Docker 知识),到系统服务化(Daemon 管理),再到容器化部署(Docker + Compose)。每一层都是可选的,用户只需承担其实际需求对应的复杂度。这与"全部容器化"的一刀切方案形成鲜明对比。
15.2 裸机部署:最简路径
15.2.1 全局安装
bash
npm install -g openclaw
# 或
bun install -g openclawOpenClaw 同时支持 Node.js(22+)和 Bun 运行时。选择 Bun 的理由通常是启动速度——Bun 的启动时间约 50ms,Node.js 约 200ms。对于 Daemon 来说这个差异可以忽略(只启动一次),但在 CLI 命令中(每次执行都启动一个新进程),150ms 的差异是可感知的。
15.2.2 首次配置
bash
openclaw # 首次运行自动启动配置向导
# 或
openclaw setup # 显式启动配置向导配置向导(第 14 章 14.8 节)引导用户完成:
- Provider 选择和认证配置
- 通道启用和 token 设置
- 安全策略确认
- 测试连接
配置文件生成在 ~/.openclaw/config.json5(或 $OPENCLAW_CONFIG 指定的路径)。
15.2.3 系统服务注册
bash
openclaw gateway start --install # 安装并启动系统服务这一条命令做了以下事情:
- 检测当前操作系统和可用的服务管理器
- 生成对应的服务配置(systemd unit / launchd plist / schtasks XML)
- 注册服务并启动
- 验证服务正常运行
Linux systemd 的具体实现:
src/daemon/systemd-unit.ts 生成的 unit 文件:
ini
[Unit]
Description=OpenClaw Gateway
After=network-online.target
Wants=network-online.target
[Service]
Type=exec
ExecStart=/usr/bin/node /usr/lib/node_modules/openclaw/openclaw.mjs gateway start --foreground
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
[Install]
WantedBy=default.target几个关键配置决策:
Type=exec(而非simple):确保 systemd 等待进程完全启动后才认为服务就绪After=network-online.target:Gateway 需要网络来连接通道和 LLM API,必须在网络就绪后才启动Restart=on-failure(而非always):正常退出(exit code 0)不重启,异常退出才重启——避免"用户手动停止后服务又自动启动"的困扰RestartSec=5:5 秒重启延迟,防止快速失败循环(crash loop)耗尽系统资源
systemd-linger.ts 的必要性。默认情况下,systemd 会在用户注销时停止其用户服务。loginctl enable-linger 让服务在用户注销后继续运行——这对无人值守的服务器至关重要。OpenClaw 会检测 linger 状态,如果未启用则提示用户。
15.2.4 裸机部署的局限
裸机部署的主要风险是环境依赖:
- Node.js 版本不匹配(OpenClaw 要求 22+,系统可能只有 18)
- 系统库缺失(Chromium 需要特定的 X11/Wayland 库)
- 权限问题(npm 全局安装可能需要 sudo)
这些问题正是容器化要解决的。
⚠️ 注意:裸机部署时,务必使用
loginctl enable-linger确保 systemd 用户服务在 SSH 断开后继续运行。否则关闭 SSH 会话后,Gateway 守护进程会被 systemd 自动终止——这是新部署中最常见的"Agent 突然失联"问题。
💡 最佳实践:生产环境部署推荐使用非 root 用户运行 OpenClaw。创建专用用户
useradd -m openclaw,将配置文件权限设为600(仅 owner 可读写),避免 API 密钥被其他用户读取。
15.3 Docker:多阶段构建工程
15.3.1 四阶段构建
OpenClaw 的 Dockerfile 使用四阶段构建——不是"从简单到复杂"的演进,而是对构建效率和镜像安全的精细工程:
图 15-1:四阶段 Docker 构建流程
下图展示了 OpenClaw Docker 镜像的四阶段构建流程:阶段 1 安装 npm 依赖(利用 Docker 层缓存加速),阶段 2 编译 TypeScript,阶段 3 剔除 devDependencies 和构建产物只保留生产文件,阶段 4 基于最小基础镜像生成最终运行镜像。这种分阶段设计使最终镜像体积减小约 60%。

阶段1:依赖安装。仅复制 package.json 和 package-lock.json,不复制源码。这利用了 Docker 的层缓存——源码变更不会使 npm install 缓存失效。典型的代码修改从 ~5 分钟降到 ~30 秒重建。
dockerfile
# 阶段 1:只复制依赖描述文件
COPY package*.json ./
RUN npm ci --frozen-lockfile为什么用 npm ci 而不是 npm install?ci 命令严格按照 package-lock.json 安装,不会更新 lockfile。这确保了构建的可重现性——无论何时何地构建,安装的依赖版本完全一致。
阶段2:构建。安装依赖和编译 TypeScript。包括 Bun 安装重试(5 次指数退避——Bun CDN 偶尔不稳定)和 BuildKit 缓存挂载。
dockerfile
# 阶段 2:复制源码并构建
COPY . .
RUN npm run build阶段3:生产剪裁。npm prune --production 移除开发依赖和构建工具。这一步可以将 node_modules 从 ~500MB 缩减到 ~200MB。
dockerfile
# 阶段 3:移除开发依赖
RUN npm prune --production这个步骤的效果非常显著——这一步移除 TypeScript 编译器(~60MB)、测试框架、lint 工具、类型声明等全部开发依赖。它们在生产环境中完全无用,但在不剪裁的情况下会显著增加镜像体积和攻击面。
阶段4:最终运行时镜像。非 root 执行(USER node)、内建健康检查。
dockerfile
# 阶段 4:最终镜像
FROM node:22-slim
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
USER node
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:18789/health || exit 115.3.2 两个镜像变体
| 变体 | 包含 | 镜像大小 | 用途 |
|---|---|---|---|
default | Node.js + Chromium + Playwright | ~1.2GB | 需要浏览器自动化(截图、网页操作) |
slim | 仅 Node.js | ~300MB | 不需要浏览器,纯文本 Agent |
为什么提供两个变体? Chromium 占据了镜像体积的 70%(~900MB)。如果 Agent 不需要浏览器自动化(很多纯对话型 Agent 不需要),强制包含 Chromium 是浪费——更慢的拉取、更大的存储、更大的攻击面。
选择建议:
- 你的 Agent 需要
browser工具(截图、网页浏览、表单填写)→ 用default - 你的 Agent 只需要文本对话、Shell 执行、Web 搜索 → 用
slim
15.3.3 供应链安全加固
GPG 指纹验证:Docker CLI 安装时验证 GPG 签名指纹,防止供应链攻击——即使攻击者入侵了安装源,签名不匹配也会导致安装失败。
dockerfile
# 验证 Docker GPG 签名
RUN gpg --verify docker.gpg && \
gpg --fingerprint | grep -q "EXPECTED_FINGERPRINT"--frozen-lockfile:确保 npm install 使用与开发环境完全相同的依赖版本。没有这个标志,npm install 可能静默升级依赖,导致"在我机器上好好的"问题。
非 root 执行:容器内进程以 node 用户运行,即使攻击者突破容器,也没有 root 权限。这是纵深防御的一部分(呼应第 13 章的安全哲学)——容器隔离是一道防线,非 root 是另一道,两者独立生效。
15.3.4 Docker Compose 双容器编排
yaml
services:
gateway:
# 网关持久运行
restart: unless-stopped
cap_drop: [NET_RAW, NET_ADMIN]
security_opt: ["no-new-privileges"]
volumes:
- openclaw-data:/home/node/.openclaw
ports:
- "18789:18789"
cli:
# CLI 按需启动
profiles: [cli] # 不随 docker compose up 启动
depends_on: [gateway]
volumes:
- openclaw-data:/home/node/.openclaw
volumes:
openclaw-data:为什么双容器而不是单容器? 延续了第 14 章的"守护进程 + 客户端"架构——Gateway 容器持久运行,CLI 容器按需启动(docker compose run --rm cli openclaw config validate)。两者共享同一个数据卷,保证状态一致。
安全配置详解:
| 配置 | 含义 | 防护目标 |
|---|---|---|
cap_drop: [NET_RAW] | 移除原始网络访问能力 | 防止端口扫描和网络嗅探 |
cap_drop: [NET_ADMIN] | 移除网络配置能力 | 防止修改路由表、防火墙规则 |
no-new-privileges | 禁止通过 setuid 提权 | 防止利用 suid 二进制提升权限 |
restart: unless-stopped | 崩溃自动重启,手动停止不重启 | 确保服务可用性 |
cap_drop 移除了不需要的 Linux 能力——网关不需要原始网络访问(NET_RAW)或网络配置(NET_ADMIN)。这遵循最小权限原则——进程只应该拥有完成工作所需的最小权限集。
15.3.5 容器化 vs 裸机的深度对比
| 维度 | 裸机部署 | 容器化部署 |
|---|---|---|
| 启动速度 | ~200ms(直接启动) | ~1-2s(容器开销) |
| 环境隔离 | 无——共享主机环境 | 完全隔离——独立文件系统和网络 |
| 资源占用 | 最小——无容器运行时开销 | 中等——Docker daemon 占用 ~100MB |
| 可重现性 | 依赖主机环境配置 | 完全可重现——镜像即环境 |
| 升级回滚 | 手动——需要备份和恢复 | 简单——切换镜像版本即可 |
| 调试便利性 | 高——直接 strace、gdb | 中——需要 docker exec 进入容器 |
| 多实例 | 需要手动管理端口和配置 | 简单——Compose 原生支持 |
| 日志管理 | 需要自行配置 logrotate | Docker 内建日志驱动 |
什么时候选裸机?
- 个人开发机——简单直接,调试方便
- 资源受限的设备(如树莓派)——Docker 开销不可忽视
- 需要直接访问 GPU——Docker GPU 透传配置复杂
什么时候选容器?
- 生产部署——环境一致性和可重现性是刚需
- CI/CD 集成——容器是标准构建产物
- 多实例部署——需要在同一主机运行多个 Gateway
- 团队协作——"在我机器上好好的"不应该是问题
15.4 云平台部署
15.4.1 Fly.io 部署
Fly.io 是 OpenClaw 社区推荐的云部署方案之一,因为它对长连接友好(WhatsApp/Discord 等通道需要维持 WebSocket 连接)。
关键配置:
toml
[http_service]
auto_stop_machines = false # ⚠️ 关键!
min_machines_running = 1auto_stop_machines = false 至关重要——网关维护与消息平台的持久 WebSocket 连接。Fly.io 的自动停止功能会切断所有通道连接,导致 Agent 变成"聋子"——看不到任何消息。
持久存储:
Agent 需要持久存储来保存会话状态、Cron 作业历史和安全审计日志。Fly.io 的 volumes 提供持久磁盘,但只在单机器部署时可用——这限制了水平扩展,但对于 OpenClaw 的单运营者假设是可接受的。
15.4.2 VPS 部署(通用方案)
对于不使用 PaaS 的场景(AWS EC2、DigitalOcean Droplet、Hetzner VPS),部署流程: