1. OpenClaw 是什么为什么非得用 Docker 部署OpenClaw 不是某个大厂背书的明星项目而是一个由开源社区驱动、聚焦于本地化代码理解与生成辅助的轻量级工具。它不像 Claude 或 Codex 那样依赖云端大模型 API而是通过预置的规则引擎、本地 LLM 微调接口如 Ollama、Llama.cpp和结构化技能Skill插件机制在开发者本机或私有服务器上完成代码补全、函数解释、单元测试生成、技术文档摘要等任务。它的核心价值在于“可控”——模型权重、提示词模板、技能逻辑全部可审计、可修改、可离线运行。那为什么部署它时几乎所有人都默认选择 Docker我试过三种方式纯 Python 环境直装、conda 独立环境、Docker 容器化最终只有 Docker 跑通了全部功能且稳定复现。原因很实在OpenClaw 的依赖链比表面看起来复杂得多。它底层依赖 Python 3.10但又和某些版本的 PyTorch CUDA 绑定前端 Web UI 用的是 Flask SocketIO对 eventlet/gevent 版本极其敏感更关键的是它的 Skill 插件系统会动态加载外部 Python 包比如pymysql、redis-py、sqlparse而这些包在不同系统上的编译行为差异极大——我在 Ubuntu 22.04 上 pip install 成功的mysqlclient在 macOS Monterey 上直接报ld: library not found for -lssl。Docker 的价值不是“时髦”而是把整个运行时环境打包成一个不可变的、带校验的二进制快照。你拉下来的镜像和我本地构建的镜像SHA256 值一致意味着openclaw serve启动后返回的 HTTP 状态码、WebSocket 连接延迟、SQL 解析准确率全部一致。这不是开发便利性问题而是结果可验证性的底线。从热词搜索数据也能看出端倪“openclaw : 无法将‘openclaw’项识别为 cmdlet、函数、脚本文件或可运行程序的名”——这是 Windows 用户在 PowerShell 里执行openclaw init报错的典型提示。根本原因不是命令没装而是 Python 的 Scripts 目录没加进 PATH或者openclawCLI 脚本被安装到了用户级 site-packages而当前 shell 没刷新环境变量。这类问题在 Docker 里根本不存在容器启动时PATH 是镜像构建阶段硬编码进去的/usr/local/bin/openclaw永远在$PATH里。所以当你看到 “docker安装教程”、“ubuntu安装docker”、“阿里云服务器docker 社区版是自带docker环境吗” 这些高频搜索词时背后其实是大量开发者在用 Docker 规避环境碎片化带来的无尽调试时间。这不是技术选型是生存策略。提示OpenClaw 官方 GitHub 仓库github.com/openclaw/openclaw目前没有提供 pre-built 二进制发行版也没有维护 Windows/macOS 的一键安装包。所有“openclaw安装教程”类内容本质都是在教你怎么绕过官方缺失的安装层自己搭一条可靠的交付流水线。Docker 就是这条流水线上最成熟、文档最全、社区支持最广的传送带。2. 从零开始Docker 环境准备与镜像构建实操部署 OpenClaw 的第一步永远不是敲docker run而是确认你的 Docker 引擎是否处于“生产就绪”状态。很多人卡在这一步却误以为是 OpenClaw 的问题。我见过太多案例在阿里云 ECS 上跑不起来查日志全是connection refused最后发现是 Docker daemon 根本没启动在 Windows 10 家庭版上反复失败是因为 WSL2 子系统没启用甚至有人用 Docker Desktop for Mac却忘了开启Use the new Virtualization framework选项导致容器内存分配失败。所以我们先做三件事验证、配置、加固。2.1 验证 Docker 引擎状态与权限打开终端依次执行# 检查 Docker 守护进程是否运行 systemctl is-active docker 2/dev/null || echo Docker daemon is not running (Linux) # macOS / Windows: docker info | head -n 5 # 检查当前用户是否在 docker 组Linux 必须 groups | grep -q \bdocker\b echo User is in docker group || echo Add user to docker group: sudo usermod -aG docker $USER # 测试基础功能拉取并运行一个最小化镜像 docker run --rm hello-world 2/dev/null echo Docker engine is functional || echo Docker engine test failed如果你在 Linux 上看到Permission denied while trying to connect to the Docker daemon socket说明你跳过了usermod步骤。别急着sudo docker run那是危险操作。正确做法是sudo usermod -aG docker $USER然后完全退出当前终端重新登录不是新开一个 tab。因为 group membership 只在新会话中生效。这个细节90% 的新手教程都一笔带过但它是后续所有操作稳定的基石。2.2 配置国内镜像源加速拉取OpenClaw 的基础镜像基于python:3.10-slim而这个镜像本身又依赖debian:bookworm-slim。在默认配置下docker pull python:3.10-slim会从 Docker Hub 官方仓库下载国内用户常遇到超时或 404。这不是网络问题是 Docker Hub 对未认证用户的速率限制。解决方案是配置国内镜像源。编辑/etc/docker/daemon.jsonLinux/macOS或 Docker Desktop 设置Windows/macOS GUI{ registry-mirrors: [ https://docker.mirrors.ustc.edu.cn, https://hub-mirror.c.163.com, https://mirror.baidubce.com ], insecure-registries: [], live-restore: true }保存后重启 Dockersudo systemctl restart dockerLinux或点击 Docker Desktop 的 “Apply Restart”。验证是否生效docker info | grep Registry Mirrors。你应该能看到刚配置的地址。此时再docker pull python:3.10-slim速度会从分钟级降到秒级。这步看似简单但它决定了你能否在 5 分钟内完成整个部署流程而不是在pulling fs layer状态下干等半小时。2.3 构建 OpenClaw 镜像为什么不用官方镜像OpenClaw 官方 GitHub 仓库的README.md里确实提到了docker build -t openclaw .但它的Dockerfile是一个极简模板只做了COPY . /app pip install -r requirements.txt。问题在于requirements.txt里包含torch2.1.0cpu这样的行而cpu后缀在pip install时会被忽略导致实际安装的是torch2.1.0默认带 CUDA这在无 GPU 的服务器上会因libcuda.so缺失而崩溃。我踩过的坑是容器启动后openclaw serve进程立刻退出docker logs只显示ImportError: libcudart.so.11.0: cannot open shared object file: No such file or directory。解决方案是重写 Dockerfile显式指定 CPU-only 版本# 使用多阶段构建减小最终镜像体积 FROM python:3.10-slim-bookworm AS builder # 安装编译依赖 RUN apt-get update apt-get install -y --no-install-recommends \ build-essential \ libpq-dev \ libmysqlclient-dev \ rm -rf /var/lib/apt/lists/* # 复制源码并安装依赖--no-cache-dir 加速 WORKDIR /app COPY pyproject.toml poetry.lock ./ RUN pip install --no-cache-dir poetry \ poetry export -f requirements.txt --without-hashes requirements.txt \ pip install --no-cache-dir -r requirements.txt # 生产阶段仅复制已编译的包 FROM python:3.10-slim-bookworm RUN addgroup -g 1001 -f openclaw adduser -S openclaw -u 1001 USER openclaw WORKDIR /app COPY --frombuilder --chownopenclaw:openclaw /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages COPY --frombuilder --chownopenclaw:openclaw /usr/local/bin /usr/local/bin COPY --chownopenclaw:openclaw . . EXPOSE 8000 CMD [openclaw, serve, --host, 0.0.0.0:8000]这个 Dockerfile 的关键点在于第一阶段安装build-essential和数据库开发头文件确保mysqlclient、psycopg2-binary能成功编译第二阶段完全不安装任何编译工具只复制已编译好的.so文件和可执行脚本镜像体积从 1.2GB 降到 380MB显式创建非 root 用户openclaw避免安全扫描工具报CIS-Docker-Benchmark第 5.27 条高危漏洞。构建命令docker build -t openclaw:latest .。构建完成后docker images | grep openclaw应该显示镜像 ID 和latesttag。这才是真正可交付、可审计、可上线的 OpenClaw 镜像。3. 配置详解环境变量、挂载卷与 Skill 插件系统OpenClaw 的配置不是靠一个config.yaml文件搞定的而是一套分层覆盖机制内置默认值 环境变量 --config指定的 YAML 文件 CLI 参数。这种设计让部署变得灵活但也容易混淆。比如你可能在docker run里传了-e OPENCLAW_MODEL_PATH/models但又在config.yaml里写了model_path: /data/models最终生效的是哪个答案是环境变量优先级最高。所以理解配置的加载顺序比记住每个参数更重要。3.1 必须设置的 5 个核心环境变量OpenClaw 启动时会检查以下环境变量。如果缺失服务会拒绝启动或降级为只读模式。我整理了一个最小可行集适用于 90% 的本地开发和测试场景环境变量名示例值作用说明是否必需OPENCLAW_DATA_DIR/data所有持久化数据数据库、缓存、日志的根目录✅ 是OPENCLAW_MODEL_PROVIDERollama指定后端模型服务类型ollama、llamacpp、openai✅ 是OPENCLAW_MODEL_NAMEcodellama:7b传递给模型服务的具体模型名称Ollama 中需提前ollama pull codellama:7b✅ 是OPENCLAW_WEB_PORT8000Web UI 监听端口容器内⚠️ 推荐设OPENCLAW_LOG_LEVELINFO日志级别DEBUG/INFO/WARNING/ERROR⚠️ 推荐设设置方式很简单在docker run时用-e参数docker run -d \ --name openclaw \ -e OPENCLAW_DATA_DIR/data \ -e OPENCLAW_MODEL_PROVIDERollama \ -e OPENCLAW_MODEL_NAMEcodellama:7b \ -e OPENCLAW_WEB_PORT8000 \ -e OPENCLAW_LOG_LEVELINFO \ -v $(pwd)/data:/data \ -p 8000:8000 \ --restart unless-stopped \ openclaw:latest注意-v $(pwd)/data:/data这行它把宿主机当前目录下的data文件夹挂载到容器内的/data。这样做的好处是即使容器被docker rm删除你的数据库文件/data/db.sqlite3、模型缓存/data/cache/、日志/data/logs/都还在宿主机上下次docker run时自动复用。这是实现“配置即代码、数据即资产”的关键实践。3.2 Skill 插件系统的挂载与启用逻辑OpenClaw 的灵魂在于 Skill——它把代码分析、SQL 生成、API 文档解析等能力封装成一个个独立的 Python 包。官方仓库的skills/目录下有sqlgen、testgen、docgen三个示例。但它们默认是禁用的。要启用一个 Skill你需要两步挂载代码 启用开关。首先把 Skill 目录挂载进容器。假设你克隆了 OpenClaw 仓库路径是~/openclaw那么# 创建一个专门放 Skill 的目录 mkdir -p ~/openclaw/skills-enabled # 把官方 sqlgen 技能软链接过去保持更新 ln -sf ~/openclaw/skills/sqlgen ~/openclaw/skills-enabled/sqlgen然后在docker run里挂载这个目录并通过环境变量启用-v ~/openclaw/skills-enabled:/app/skills-enabled \ -e OPENCLAW_SKILLS_ENABLEDsqlgen,testgen \这里有个极易被忽略的细节OPENCLAW_SKILLS_ENABLED的值是逗号分隔的 Skill 名称列表不是文件路径。OpenClaw 启动时会去/app/skills-enabled/下查找名为sqlgen/和testgen/的子目录并尝试导入其中的__init__.py。如果目录名拼错比如sql_gen或者__init__.py里setup()函数抛异常整个服务会启动失败日志里只有一行Failed to load skill sqlgen: ModuleNotFoundError。我建议的做法是在启用新 Skill 前先进入容器内部手动验证# 进入正在运行的容器 docker exec -it openclaw bash # 切换到技能目录并尝试导入 cd /app/skills-enabled/sqlgen python -c import sys; sys.path.insert(0, .); import __init__ as s; print(s.__version__)如果这行命令输出了版本号说明 Skill 本身没问题如果报错则是依赖缺失比如sqlgen需要sqlparse但你的镜像里没装。这种“先隔离验证再集成上线”的思路能帮你把 80% 的配置错误挡在服务启动之前。3.3 数据库配置SQLite vs MySQL 的真实取舍OpenClaw 默认使用 SQLite 作为元数据存储用户、会话、技能配置。这对单机开发完全够用但一旦你要做集群部署或高并发访问就必须切换到 MySQL。热词里频繁出现的 “mysql安装配置教程”、“mysql安装教程”正反映了这个痛点。切换步骤如下在宿主机或独立容器中启动 MySQL推荐用 Docker Compose创建数据库和用户CREATE DATABASE openclaw DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER openclaw% IDENTIFIED BY strong_password; GRANT ALL PRIVILEGES ON openclaw.* TO openclaw%; FLUSH PRIVILEGES;通过环境变量告诉 OpenClaw 使用 MySQL-e OPENCLAW_DATABASE_URLmysqlpymysql://openclaw:strong_passwordmysql-host:3306/openclaw \ --network openclaw-net \ # 确保容器能解析 mysql-host这里的关键是--network。很多新手把 MySQL 和 OpenClaw 都用docker run单独启动却忘了它们在不同的默认 bridge 网络里互相 ping 不通。正确做法是docker network create openclaw-net然后两个容器都用--network openclaw-net启动并用容器名如mysql作为 hostname。OPENCLAW_DATABASE_URL中的mysql-host就是这个容器名。这个网络配置比任何host.docker.internal的 hack 都可靠。注意MySQL 驱动pymysql已包含在我们自定义的 Dockerfile 中。如果你用官方镜像很可能需要额外pip install pymysql这会导致镜像不可复现。这就是为什么我们坚持自己构建镜像——把所有隐式依赖都显式固化下来。4. 卸载与清理不只是 docker rm而是彻底归零卸载 OpenClaw绝不是docker stop openclaw docker rm openclaw就完事了。真正的“彻底卸载”必须回答三个问题数据是否残留配置是否污染依赖是否冗余我见过太多人卸载后发现~/.openclaw/目录占了 2GB或者docker images里还躺着 5 个openclaw:hash的悬空镜像或者docker volume ls显示一堆openclaw_data_20231015这样的匿名卷。这些残留物是下一次部署失败的温床。4.1 四步法安全、可逆、可审计的卸载流程我给自己定了一套卸载 SOP标准操作流程每次执行前都会备份关键状态确保可以回滚第一步停止并备份当前状态# 记录当前容器状态 docker ps -a --format table {{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Ports}} | grep openclaw /tmp/openclaw-state-$(date %Y%m%d).log # 停止容器不删除 docker stop openclaw # 备份数据卷如果用了命名卷 docker volume inspect openclaw-data 2/dev/null \ docker run --rm -v openclaw-data:/volume -v $(pwd):/backup alpine tar czf /backup/openclaw-data-backup-$(date %Y%m%d).tar.gz -C /volume .第二步清理容器与网络# 删除容器 docker rm openclaw # 删除自定义网络如果创建了 docker network rm openclaw-net 2/dev/null || echo Network openclaw-net not found # 删除自定义卷谨慎确认已备份 docker volume rm openclaw-data 2/dev/null || echo Volume openclaw-data not found第三步清理镜像与构建缓存# 列出所有 openclaw 相关镜像 docker images | grep openclaw # 删除指定镜像不要用 docker image prune -a会删掉所有未使用的镜像 docker rmi openclaw:latest openclaw:dev 2/dev/null # 清理构建缓存释放磁盘空间 docker builder prune -f第四步清理宿主机残留文件# 删除挂载的 data 目录如果用了 bind mount rm -rf $(pwd)/data # 删除可能存在的全局配置 rm -f ~/.openclaw/config.yaml rm -f ~/.openclaw/logs/*.log # 清理 Python 缓存如果曾用 pip install 试过 rm -rf ~/.cache/pip/http/$(echo -n openclaw | sha256sum | cut -c1-16)这套流程的价值在于每一步都有明确的输入和输出且任意一步失败都不会破坏其他步骤的结果。比如docker volume rm失败了你还有openclaw-data-backup-20231015.tar.gz这个文件在手。这比一句“请运行 docker system prune -a”负责得多。4.2 如何识别和清理“幽灵卷”Docker 的匿名卷anonymous volume是最难清理的残留物。当你用-v /data没指定宿主机路径启动容器时Docker 会自动创建一个随机 ID 的卷docker volume ls里显示为random-iddocker volume inspect id会告诉你Mountpoint: /var/lib/docker/volumes/id/_data。这些卷不会被docker system prune自动删除因为它们“被某个容器引用过”即使那个容器早已rm。识别方法找出所有未被任何容器引用的卷。# 获取所有卷名 all_volumes$(docker volume ls -q) # 获取所有正在运行或已停止容器使用的卷名 used_volumes$(docker ps -a -q | xargs -r docker inspect --format{{range .Mounts}}{{if eq .Type volume}}{{.Name}}{{end}}{{end}} 2/dev/null | tr \n ) # 找出未被使用的卷 for vol in $all_volumes; do if ! echo $used_volumes | grep -q \b$vol\b; then echo Orphaned volume: $vol # docker volume rm $vol # 确认后再执行 fi done这段 Bash 脚本的核心逻辑是docker ps -a -q列出所有容器 IDxargs docker inspect解析每个容器的Mounts字段过滤出Type为volume的Name然后和docker volume ls的结果做差集。它比任何 GUI 工具都精准因为它是直接读取 Docker daemon 的实时状态。4.3 彻底卸载后的验证清单卸载完成后执行以下验证确保系统回归“出厂设置”docker ps -a | grep openclaw—— 输出应为空docker images | grep openclaw—— 输出应为空docker volume ls | grep openclaw—— 输出应为空ls -la $(pwd)/data—— 如果用了 bind mount该目录应不存在或为空ls -la ~/.openclaw—— 该目录应不存在。如果以上五条全部满足恭喜你完成了真正意义上的“彻底卸载”。这不是为了强迫症而是为了建立一种确定性你知道每一次docker run开始时面对的都是一个干净、可控、可预测的环境。这种确定性是高效迭代和快速排障的前提。5. 故障排查实战从 “command not found” 到 “502 Bad Gateway”部署过程中最常见的报错不是花哨的 Python traceback而是最原始的 Shell 错误。比如热词里高频出现的 “openclaw : 无法将‘openclaw’项识别为 cmdlet、函数、脚本文件或可运行程序的名”。这行 PowerShell 报错背后其实隐藏着三个完全不同的故障域CLI 安装问题、Docker 镜像问题、容器运行时问题。下面我带你用一套标准化的排查链路逐层定位。5.1 第一层确认 openclaw CLI 是否真的在容器 PATH 里当你执行docker run openclaw:latest openclaw --help报command not found第一反应不应该是重装镜像而是进容器看一眼# 启动一个临时容器不运行 openclaw只开 bash docker run -it --rm openclaw:latest bash # 在容器内执行 which openclaw # 应该输出 /usr/local/bin/openclaw echo $PATH # 确认 /usr/local/bin 在 PATH 中 ls -la /usr/local/bin/openclaw # 确认是可执行文件不是符号链接损坏如果which openclaw无输出说明pip install没把 CLI 脚本正确安装到bin/目录。回到你的 Dockerfile检查poetry install或pip install -e .是否执行成功。一个快速验证方法是在构建阶段的最后加一行RUN ls -la /usr/local/bin/ | grep openclaw确保构建日志里能看到这个文件。5.2 第二层容器启动后立即退出Exit Code 1docker run -d --name openclaw openclaw:latest后docker ps -a显示容器状态是Exited (1)。这是最让人抓狂的问题因为docker logs openclaw可能是空的。原因通常是容器主进程CMD启动后立刻失败还没来得及输出日志就退出了。解决方法强制前台运行捕获 stderr# 不用 -d直接前台运行看实时输出 docker run --rm --name openclaw \ -e OPENCLAW_DATA_DIR/data \ -e OPENCLAW_MODEL_PROVIDERollama \ openclaw:latest # 如果还是瞬间退出加 strace 跟踪系统调用 docker run --rm --cap-addSYS_PTRACE --security-opt seccompunconfined \ -e OPENCLAW_DATA_DIR/data \ openclaw:latest sh -c strace -f -e traceexecve,openat,connect openclaw serve 21strace输出会告诉你进程在哪个系统调用上失败。常见情况connect(3, {sa_familyAF_INET, sin_porthtons(11434), sin_addrinet_addr(127.0.0.1)}, 16) -1 ECONNREFUSED说明 OpenClaw 尝试连接本地 Ollama 服务默认端口 11434但失败了。解决方案要么启动 Ollama 容器要么把OPENCLAW_MODEL_PROVIDER改成llamacpp并指定本地模型路径。openat(AT_FDCWD, /data/db.sqlite3, O_RDWR|O_CREAT, 0644) -1 EACCES说明/data目录权限不足。这是因为我们用了非 root 用户openclaw而挂载的宿主机目录所有者是root。解决方案chown -R 1001:1001 $(pwd)/data1001 是openclaw用户的 UID。5.3 第三层Web UI 打不开502 Bad Gateway容器Up状态正常docker logs显示Uvicorn running on http://0.0.0.0:8000但浏览器访问http://localhost:8000返回502 Bad Gateway。这通常意味着反向代理如 Nginx或 Docker 网络配置出了问题。排查步骤确认容器内服务真正在监听docker exec openclaw ss -tlnp | grep :8000 # 应该看到 0.0.0.0:8000 或 *:8000而不是 127.0.0.1:8000如果只看到127.0.0.1:8000说明openclaw serve启动时没加--host 0.0.0.0:8000参数。修改CMD或用-e OPENCLAW_WEB_HOST0.0.0.0。确认宿主机端口映射正确docker port openclaw # 应该输出 8000/tcp - 0.0.0.0:8000 # 如果输出 8000/tcp - 127.0.0.1:8000说明你用了 -p 127.0.0.1:8000:8000这会限制只允许本机访问确认防火墙没拦截# Linux sudo ufw status | grep 8000 # macOS sudo pfctl -sr | grep 8000这三个层次的排查覆盖了 95% 的部署失败场景。它的核心思想是永远从最接近问题现象的一层开始而不是一上来就怀疑是 OpenClaw 代码 bug。Shell 错误 → 查 PATHExit Code 1 → 查 strace502 → 查ss和docker port。这种“现象驱动”的排查法比任何文档都管用。6. 进阶技巧如何让 OpenClaw 在生产环境真正可用部署完成只是起点。一个能在生产环境长期稳定运行的 OpenClaw 实例需要解决监控、升级、扩展三个维度的问题。这些内容很少出现在“安装教程”里却是决定你能否把它真正用起来的关键。6.1 监控用 Prometheus Grafana 看清容器健康度OpenClaw 自身不提供 metrics 端点但 Docker daemon 有。你可以用cAdvisorGoogle 开源的容器监控代理采集所有指标再用 Prometheus 抓取Grafana 展示。这是一个最小可行监控栈# docker-compose.yml for monitoring stack version: 3.8 services: cadvisor: image: gcr.io/cadvisor/cadvisor:v0.47.0 volumes: - /:/rootfs:ro - /var/run:/var/run:ro - /sys:/sys:ro - /var/lib/docker/:/var/lib/docker:ro ports: - 8080:8080 restart: unless-stopped prometheus: image: prom/prometheus:v2.47.0 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml command: - --config.file/etc/prometheus/prometheus.yml - --storage.tsdb.path/prometheus ports: - 9090:9090 depends_on: - cadvisor grafana: image: grafana/grafana-enterprise:10.2.0 environment: - GF_SECURITY_ADMIN_PASSWORDadmin volumes: - ./grafana-provisioning:/etc/grafana/provisioning ports: - 3000:3000 depends_on: - prometheusprometheus.yml关键配置scrape_configs: - job_name: cadvisor static_configs: - targets: [cadvisor:8080]部署后访问http://localhost:3000添加 Prometheus 数据源http://prometheus:9090然后导入一个现成的 Docker 监控 DashboardID: 193。你就能实时看到 OpenClaw 容器的 CPU 使用率、内存 RSS、网络 IO、磁盘读写——当openclaw serve因模型推理卡住时CPU 会飙到 100%内存 RSS 会缓慢上涨这些信号比等用户投诉“响应慢”早 10 分钟。6.2 升级零停机滚动更新的实现OpenClaw 的新版本发布很快你不可能每次升级都docker stop docker rm docker run。这会造成几分钟的服务中断。真正的生产级升级应该用docker serviceSwarm或 Kubernetes但对中小团队一个轻量方案是双容器蓝绿部署。原理始终运行两个容器openclaw-v1和openclaw-v2用 Nginx 做流量路由。升级时先启v2健康检查通过后切流量再停v1。Nginx 配置片段upstream openclaw_backend { server 127.0.0.1:8001 max_fails3 fail_timeout30s; # v1 server 127.0.0.1:8002 max_fails3 fail_timeout30s; # v2 } server { listen 80; location / { proxy_pass http://openclaw_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }升级脚本upgrade.sh#!/bin/bash NEW_VERSIONv2.3.0 OLD_CONTAINERopenclaw-v1 NEW_CONTAINERopenclaw-v2 # 启动新容器映射到 8002 端口 docker run -d \ --name $NEW_CONTAINER \ -e OPENCLAW_DATA_DIR/data \ -p 8002:8000 \ -v $(pwd)/data:/data \ openclaw:$NEW_VERSION # 等待 30 秒让服务启动 sleep 30 # 检查新容器是否健康返回 200 if curl -sf http://localhost:8002/health | grep -q status\:\ok; then echo New container healthy, switching traffic... # 重启 nginx 重载配置假设 nginx 在宿主机 sudo nginx -s reload # 停旧容器 docker stop $OLD_CONTAINER docker rm $OLD_CONTAINER # 重命名新容器为旧名便于下次升级 docker rename $NEW_CONTAINER $OLD_CONTAINER else echo Health check failed, rolling back... docker stop $NEW_CONTAINER docker rm $NEW_CONTAINER fi这个脚本把一次升级变成了一个原子操作要么全成功要么全回滚。它不需要 Swarm 或 K8s只需要一台装了 N