一、这篇文章最终要达到什么结果
按本文操作完成后,你应当能达到下面这组结果:
1. Docker 项目根目录固定在:
`/opt/clawpanel-all-in-one`
2. OpenClaw 持久化数据根目录固定在:
`/opt/clawpanel-all-in-one/data/openclaw`
3. `docker compose ps` 应看到:
- `clawpanel` -> `Up`
- `openclaw-gateway` -> `Up (healthy)`
4. 执行:
`curl http://127.0.0.1:18789/health`
应返回:
`{"ok":true,"status":"live"}`
5. 宿主机可以直接看到 OpenClaw 关键数据,例如:
- `workspace/`
- `workspace/MEMORY.md`
- `workspace/memory/`
- `extensions/`
- `agents/`
- `openclaw.json`
6. Web 端可以继续对话
7. 微信插件启动链正常,不再依赖“数据全藏在 Docker volume 黑盒里”的旧状态
---
二、先讲清楚:这套 V1 方案到底解决了什么
这次工程里,真正被解决掉的,不是“某次侥幸启动成功”,而是下面这些关键问题:
1. 数据从 Docker volume 黑盒,切到宿主机 bind mount 可见目录
以前最麻烦的问题之一,是 OpenClaw 核心数据主要躺在 Docker volume 里。只要一涉及:
- 备份
- 排障
- 迁移
- 查会话
- 查插件
- 查记忆文件
就很容易变成“先猜 volume 在哪里”。
这次成功路径里,已经把核心数据切到了宿主机可直接查看的目录:
`/opt/clawpanel-all-in-one/data/openclaw`
这意味着:
- 记忆文件能直接看
- 工作区能直接看
- 插件目录能直接看
- agent 会话能直接看
- `openclaw.json` 能直接看
- 做备份、迁移、排障时,不再先从 volume 黑盒猜起
2. gateway 启动命令已经验证了正确姿势
本次实战已经明确验证:
**正确命令:**
openclaw gateway
**不要再用:**
openclaw gateway start
openclaw gateway start --foreground
之前出过一次大坑,就是 compose 里把 gateway 启动命令写成了 `openclaw gateway start --foreground`,结果当前版本根本不认,直接把链路搞废。
所以本文后面所有正式写法,都以:
openclaw gateway
为唯一正确前台运行方式。
3. healthcheck 已切到 Node 脚本,不再依赖 curl 假告警
之前还有一个很烦的坑:
- 容器明明其实已经起来了
- 但 `docker compose ps` 里却显示 `unhealthy`
- 根因不是 gateway 真挂了,而是 healthcheck 用了 `curl`
- `node:22-slim` 镜像里不一定有 `curl`
所以这次成功方案里,healthcheck 改成了:
test: ["CMD", "node", "/healthcheck/gateway-healthcheck.js"]
对应脚本是:
`/opt/clawpanel-all-in-one/runtime/gateway-healthcheck.js`
这样就不会再被“curl 不存在”这种低级问题污染健康状态判断。
4. 这篇文章是 V1,不是假装 V2 已完成
这一点必须说清楚,免得误导别人。
本篇写的是:
- **已经跑通的真实方案**
- **当前可长期使用的 V1 稳定方案**
但它还不是:
- 独立 `Dockerfile.gateway` 固化镜像终版
- 完全不依赖运行时在线安装 CLI 的终极版本
当前 V1 的 gateway 仍然是用下面这种启动逻辑:
npm install -g @qingchencloud/openclaw-zh --registry https://registry.npmmirror.com && \
openclaw init 2>/dev/null || true && \
openclaw gateway
这套逻辑已经被这次实际部署验证为可用,因此 V1 可以发。
未来如果把 gateway 再进一步固化成更干净的镜像方案,那是后续 V2 的内容,不在本文里冒充已经完成。
---
三、部署前准备:无论新装还是重装,都先把这些前提确认清楚
这一节不要跳。尤其是第一次部署的人,最容易因为“看起来简单”直接跳过去,最后自己把自己坑了。
3.1 机器与系统前提
本文默认你使用的是一台 Linux 服务器,并且:
1. 你有 root 权限,或者有等价的 sudo 权限
2. 系统已经安装好 Docker
3. 系统已经安装好 Docker Compose 插件(即 `docker compose` 可用)
4. 你能正常访问公网 npm 源,或者能访问文中使用的镜像源
5. 你允许把项目根目录放在:
`/opt/clawpanel-all-in-one`
3.2 先检查 Docker 是否可用
执行下面两条命令:
docker --version
docker compose version
预期结果
两条命令都应该正常输出版本号,例如:
Docker version 27.x.x, build xxxxx
Docker Compose version v2.x.x
如果不对怎么办
- 如果 `docker --version` 报错:先安装 Docker
- 如果 `docker compose version` 报错:先安装 Docker Compose 插件
- 这两项没准备好,后面的内容不要继续
3.3 本文统一使用的目录结构
本文统一使用:
/opt/clawpanel-all-in-one/
├── Dockerfile.clawpanel
├── docker-compose.yml
├── runtime/
│ └── gateway-healthcheck.js
├── scripts/
│ ├── backup-pre-reinstall.sh
│ ├── migrate-openclaw-data.sh
│ └── verify-openclaw.sh
└── data/
└── openclaw/
各目录的作用
- `/opt/clawpanel-all-in-one/`
- 项目总根目录
- `/opt/clawpanel-all-in-one/data/openclaw/`
- OpenClaw 的持久化数据目录
- `/opt/clawpanel-all-in-one/runtime/`
- 放运行时辅助脚本,例如 healthcheck
- `/opt/clawpanel-all-in-one/scripts/`
- 放备份、迁移、验收等运维脚本
3.4 当前 V1 的核心思路
本文不是“两个容器都自己维护自己的数据”。
当前 V1 的核心设计是:
- `clawpanel` 使用项目内构建出来的镜像
- `openclaw-gateway` 使用 `node:22-slim`
- gateway 启动时在线安装 `@qingchencloud/openclaw-zh`
- 两个容器都把 `/root/.openclaw` 挂到宿主机同一个目录:
`/opt/clawpanel-all-in-one/data/openclaw`
这样做的直接结果是:
- 数据统一
- 宿主机可见
- backup / migrate / verify 的逻辑统一
- 当前这轮真实成功结果可复现
---
四、方案 A:全新安装(从空目录开始)
这一节适合:
- 新服务器
- 新目录
- 之前没有现成旧环境
- 或者你明确接受从零开始搭一套新的结构
A-1. 创建项目目录
这一步的目的
在宿主机上创建统一的部署根目录和子目录,后面所有文件都放这里,不要边做边换地方。
要执行的命令
mkdir -p /opt/clawpanel-all-in-one
mkdir -p /opt/clawpanel-all-in-one/runtime
mkdir -p /opt/clawpanel-all-in-one/scripts
mkdir -p /opt/clawpanel-all-in-one/data/openclaw
cd /opt/clawpanel-all-in-one
pwd
预期结果
最后一条 `pwd` 应输出:
/opt/clawpanel-all-in-one
如果不对怎么办
- 如果提示权限不足,请使用 root 或 sudo
- 如果你不打算用本文的固定路径,就不要继续照抄本文,后面所有路径都得跟着改
---
A-2. 创建 `Dockerfile.clawpanel`
这一步的目的
构建 `clawpanel` 容器镜像。当前这次成功方案里,`clawpanel` 侧使用的是单独 Dockerfile。
要执行的命令
cat > /opt/clawpanel-all-in-one/Dockerfile.clawpanel <<'EOF'
FROM node:22-slim
RUN apt-get update && apt-get install -y python3 git ca-certificates && rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN git clone https://github.com/qingchencloud/clawpanel.git . && npm install
RUN npm run build
EXPOSE 1420
CMD ["npm", "run", "serve"]
EOF
写完后立即检查文件内容
执行:
sed -n '1,200p' /opt/clawpanel-all-in-one/Dockerfile.clawpanel
你应该看到的完整内容
FROM node:22-slim
RUN apt-get update && apt-get install -y python3 git ca-certificates && rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN git clone https://github.com/qingchencloud/clawpanel.git . && npm install
RUN npm run build
EXPOSE 1420
CMD ["npm", "run", "serve"]
如果不对怎么办
- 如果文件里混进了 shell 提示符、额外空行、命令本身文字,说明你粘贴脏了,删掉重写
- 如果 `sed` 看不到完整内容,先别继续 build
---
A-3. 创建 gateway Node healthcheck 脚本
这一步的目的
给 `openclaw-gateway` 提供一个不依赖 `curl` 的健康检查脚本,避免 `node:22-slim` 环境里因为缺少 `curl` 导致误报 `unhealthy`。
要执行的命令
cat > /opt/clawpanel-all-in-one/runtime/gateway-healthcheck.js <<'EOF'
const url = 'http://127.0.0.1:18789/health';
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 4000);
fetch(url, { signal: controller.signal })
.then(async (res) => {
clearTimeout(timeout);
const text = await res.text();
if (!res.ok) {
console.error(`healthcheck failed: status=${res.status} body=${text}`);
process.exit(1);
}
process.stdout.write(text);
process.exit(0);
})
.catch((err) => {
clearTimeout(timeout);
console.error(`healthcheck error: ${err.message}`);
process.exit(1);
});
EOF
写完后立即检查
sed -n '1,200p' /opt/clawpanel-all-in-one/runtime/gateway-healthcheck.js
你应该看到的完整内容
const url = 'http://127.0.0.1:18789/health';
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 4000);
fetch(url, { signal: controller.signal })
.then(async (res) => {
clearTimeout(timeout);
const text = await res.text();
if (!res.ok) {
console.error(`healthcheck failed: status=${res.status} body=${text}`);
process.exit(1);
}
process.stdout.write(text);
process.exit(0);
})
.catch((err) => {
clearTimeout(timeout);
console.error(`healthcheck error: ${err.message}`);
process.exit(1);
});
如果不对怎么办
- 少一行都别往下走
- `fetch`、`AbortController`、`process.exit` 这些关键逻辑不要擅自改
---
A-4. 创建验收脚本 `scripts/verify-openclaw.sh`
这一步的目的
把部署后的检查固定成一个脚本,不要每次靠肉眼想“我该查什么”。
要执行的命令
cat > /opt/clawpanel-all-in-one/scripts/verify-openclaw.sh <<'EOF'
#!/usr/bin/env bash
set -eu
cd /opt/clawpanel-all-in-one
echo "==> [1/4] docker compose ps"
docker compose ps
echo
echo "==> [2/4] gateway /health"
HEALTH_OUTPUT="$(curl -fsS http://127.0.0.1:18789/health)"
echo "$HEALTH_OUTPUT"
if [ "$HEALTH_OUTPUT" != '{"ok":true,"status":"live"}' ]; then
echo "❌ gateway /health 返回异常"
exit 1
fi
echo
echo "==> [3/4] data/openclaw top level"
find /opt/clawpanel-all-in-one/data/openclaw -maxdepth 2 -mindepth 1 | sort | sed -n '1,120p'
echo
echo "==> [4/4] done"
echo "✅ 基础巡检通过:compose / health / data 目录均已检查"
EOF
赋予执行权限
chmod +x /opt/clawpanel-all-in-one/scripts/verify-openclaw.sh
检查文件内容
sed -n '1,200p' /opt/clawpanel-all-in-one/scripts/verify-openclaw.sh
你应该看到的完整内容
#!/usr/bin/env bash
set -eu
cd /opt/clawpanel-all-in-one
echo "==> [1/4] docker compose ps"
docker compose ps
echo
echo "==> [2/4] gateway /health"
HEALTH_OUTPUT="$(curl -fsS http://127.0.0.1:18789/health)"
echo "$HEALTH_OUTPUT"
if [ "$HEALTH_OUTPUT" != '{"ok":true,"status":"live"}' ]; then
echo "❌ gateway /health 返回异常"
exit 1
fi
echo
echo "==> [3/4] data/openclaw top level"
find /opt/clawpanel-all-in-one/data/openclaw -maxdepth 2 -mindepth 1 | sort | sed -n '1,120p'
echo
echo "==> [4/4] done"
echo "✅ 基础巡检通过:compose / health / data 目录均已检查"
说明
这个 V1 验收脚本做的是“基础验收”,重点检查:
- compose 状态
- gateway `/health`
- bind mount 数据目录
如果你后面还要加更复杂的插件状态检查,可以在这个脚本基础上继续增强。但本文先保证当前真实成功基线能复现。
---
A-5. 创建迁移脚本 `scripts/migrate-openclaw-data.sh`
这一步的目的
即使你现在是全新安装,也建议把迁移脚本先放好。这样后面如果要从旧 volume 导数据,脚本已经在位。
要执行的命令
cat > /opt/clawpanel-all-in-one/scripts/migrate-openclaw-data.sh <<'EOF'
#!/usr/bin/env bash
set -eu
OLD_VOLUME="${1:-clawpanel-all-in-one_openclaw-data}"
TARGET_DIR="/opt/clawpanel-all-in-one/data/openclaw"
mkdir -p "$TARGET_DIR"
echo "==> source volume: $OLD_VOLUME"
echo "==> target dir : $TARGET_DIR"
docker run --rm \
-v "$OLD_VOLUME":/from \
-v "$TARGET_DIR":/to \
alpine:3.20 \
sh -c 'cd /from && cp -a . /to/'
echo "✅ migration copy finished"
EOF
赋予执行权限
chmod +x /opt/clawpanel-all-in-one/scripts/migrate-openclaw-data.sh
立即检查
sed -n '1,200p' /opt/clawpanel-all-in-one/scripts/migrate-openclaw-data.sh
---
A-6. 创建重装前备份脚本 `scripts/backup-pre-reinstall.sh`
这一步的目的
把重装前备份动作固定下来,避免真正到重装那一步时才手忙脚乱。
要执行的命令
cat > /opt/clawpanel-all-in-one/scripts/backup-pre-reinstall.sh <<'EOF'
#!/usr/bin/env bash
set -eu
TS="$(date -u +%Y%m%dT%H%M%SZ)"
BACKUP_ROOT="/opt/clawpanel-all-in-one/backup/pre-reinstall-$TS"
mkdir -p "$BACKUP_ROOT"
if [ -f /opt/clawpanel-all-in-one/docker-compose.yml ]; then
cp -a /opt/clawpanel-all-in-one/docker-compose.yml "$BACKUP_ROOT/"
fi
if [ -f /opt/clawpanel-all-in-one/Dockerfile.clawpanel ]; then
cp -a /opt/clawpanel-all-in-one/Dockerfile.clawpanel "$BACKUP_ROOT/"
fi
if [ -d /opt/clawpanel-all-in-one/runtime ]; then
cp -a /opt/clawpanel-all-in-one/runtime "$BACKUP_ROOT/"
fi
if [ -d /opt/clawpanel-all-in-one/scripts ]; then
cp -a /opt/clawpanel-all-in-one/scripts "$BACKUP_ROOT/"
fi
if [ -d /opt/clawpanel-all-in-one/data/openclaw ]; then
tar -C /opt/clawpanel-all-in-one/data -czf "$BACKUP_ROOT/openclaw-data.tar.gz" openclaw
fi
echo "✅ backup done: $BACKUP_ROOT"
EOF
赋予执行权限
chmod +x /opt/clawpanel-all-in-one/scripts/backup-pre-reinstall.sh
立即检查
sed -n '1,220p' /opt/clawpanel-all-in-one/scripts/backup-pre-reinstall.sh
---
A-7. 创建 `docker-compose.yml`
这一步的目的
把整个部署结构写死下来:
- `clawpanel` 单独 build
- `openclaw-gateway` 使用 `node:22-slim`
- `/root/.openclaw` 统一挂到宿主机 bind mount
- healthcheck 使用 Node 脚本
- gateway 使用已验证正确的前台命令 `openclaw gateway`
要执行的命令
cat > /opt/clawpanel-all-in-one/docker-compose.yml <<'EOF'
services:
gateway:
image: node:22-slim
container_name: openclaw-gateway
restart: unless-stopped
working_dir: /root
command: >
sh -c "npm install -g @qingchencloud/openclaw-zh --registry https://registry.npmmirror.com &&
openclaw init 2>/dev/null || true &&
openclaw gateway"
ports:
- "18789:18789"
volumes:
- ./data/openclaw:/root/.openclaw
- ./runtime/gateway-healthcheck.js:/healthcheck/gateway-healthcheck.js:ro
healthcheck:
test: ["CMD", "node", "/healthcheck/gateway-healthcheck.js"]
interval: 30s
timeout: 5s
retries: 5
start_period: 40s
clawpanel:
build:
context: .
dockerfile: Dockerfile.clawpanel
container_name: clawpanel
restart: unless-stopped
working_dir: /app
ports:
- "1420:1420"
volumes:
- ./data/openclaw:/root/.openclaw
environment:
NODE_ENV: production
depends_on:
gateway:
condition: service_healthy
EOF
写完后立即检查
sed -n '1,240p' /opt/clawpanel-all-in-one/docker-compose.yml
你应该看到的完整内容
services:
gateway:
image: node:22-slim
container_name: openclaw-gateway
restart: unless-stopped
working_dir: /root
command: >
sh -c "npm install -g @qingchencloud/openclaw-zh --registry https://registry.npmmirror.com &&
openclaw init 2>/dev/null || true &&
openclaw gateway"
ports:
- "18789:18789"
volumes:
- ./data/openclaw:/root/.openclaw
- ./runtime/gateway-healthcheck.js:/healthcheck/gateway-healthcheck.js:ro
healthcheck:
test: ["CMD", "node", "/healthcheck/gateway-healthcheck.js"]
interval: 30s
timeout: 5s
retries: 5
start_period: 40s
clawpanel:
build:
context: .
dockerfile: Dockerfile.clawpanel
container_name: clawpanel
restart: unless-stopped
working_dir: /app
ports:
- "1420:1420"
volumes:
- ./data/openclaw:/root/.openclaw
environment:
NODE_ENV: production
depends_on:
gateway:
condition: service_healthy
这一步特别注意
不要把 gateway command 写成:
openclaw gateway start
也不要写成:
openclaw gateway start --foreground
这两个都不是本次成功路径。
---
A-8. 构建并启动服务
这一步的目的
正式把 `clawpanel` 和 `openclaw-gateway` 拉起来。
要执行的命令
cd /opt/clawpanel-all-in-one
docker compose up -d --build
第一次启动时要有心理准备
第一次启动通常会比普通容器慢,原因包括:
- `clawpanel` 要 build
- gateway 首次启动时会在线安装 `@qingchencloud/openclaw-zh`
- 健康检查要等服务真正起来后才会转绿
查看启动过程
建议马上执行:
docker compose ps
docker compose logs --tail=100 gateway
docker compose logs --tail=100 clawpanel
预期结果
你最终应看到:
- `gateway` 容器存在
- `clawpanel` 容器存在
- gateway 日志里出现类似:
- `heartbeat started`
- `health-monitor started`
- `listening on ws://127.0.0.1:18789`
- clawpanel 日志里出现类似:
- `ClawPanel Web Server (Headless)`
- `http://0.0.0.0:1420/`
如果不对怎么办
1. 如果 gateway 容器反复退出:
先查 `docker compose logs gateway`
2. 如果看到和 `openclaw gateway start --foreground` 有关的报错:
说明你命令写错了
3. 如果 `clawpanel` 没起来:
查 `docker compose logs clawpanel`
4. 如果 `gateway` 长时间 `starting`:
多半是 CLI 在线安装还没跑完,先别急着判死刑
---
A-9. 基础验收
这一步的目的
确认整个结构已经真正跑起来,而不是“看起来大概差不多”。
先跑验收脚本
cd /opt/clawpanel-all-in-one
./scripts/verify-openclaw.sh
再手工补两条关键检查
docker compose ps
curl -fsS http://127.0.0.1:18789/health
预期结果
1. `docker compose ps` 里看到:
- `clawpanel` -> `Up`
- `openclaw-gateway` -> `Up (healthy)`
2. `curl -fsS http://127.0.0.1:18789/health` 返回:
{"ok":true,"status":"live"}
再检查宿主机数据目录
find /opt/clawpanel-all-in-one/data/openclaw -maxdepth 2 -mindepth 1 | sort | sed -n '1,120p'
预期结果
你应该能在宿主机上直接看到 `/root/.openclaw` 的映射内容,而不是一个空目录。
在全新安装场景下,一开始内容可能还不多,但目录至少不该完全没有 OpenClaw 运行痕迹。
---
五、方案 B:已有旧环境,重装 / 迁移到 bind mount
这一节是本文最重要的部分之一。
它适合下面这些场景:
- 你之前已经跑过 ClawPanel / OpenClaw
- 数据还在旧 Docker volume 里
- 你不想丢失记忆、插件、会话、配置
- 你想把旧结构迁到宿主机 bind mount
- 同时保留一个可回滚的旧保险
B-1. 先确认你现在是不是旧环境迁移场景
如果你满足下面任意一条,就优先走本节,而不是“当自己是新装”:
- 旧环境里已经有记忆文件
- 旧环境里已经登录过微信插件
- 旧环境里已有 agent 会话记录
- 旧环境里已经存在 `openclaw.json`
- 你担心重装后数据丢失
只要你属于这些情况之一,就不要偷懒,不要跳过备份和卷名确认步骤。
---
B-2. 停掉当前服务
这一步的目的
在真正做迁移前,先停止旧容器,避免迁移时数据仍在被写入。
要执行的命令
cd /opt/clawpanel-all-in-one
docker compose down
预期结果
容器被停止并移除,但 volume 不会因为这一步自动删除。
注意
不要执行这种带卷删除的命令:
docker compose down -v
这条在没确认备份和迁移前,风险太高。
---
B-3. 确认真实旧 volume 名,不要想当然
这一步的目的
本次实战已经踩过坑:
**真实旧卷名是:**
clawpanel-all-in-one_openclaw-data
**不是:**
openclaw-data
但你在自己机器上仍然应该先确认,不要因为文章里写了一个名字,就直接拿去赌。
先列出本机 volume
docker volume ls
再定位当前项目实际使用的卷
docker inspect clawpanel --format '{{json .Mounts}}'
docker inspect openclaw-gateway --format '{{json .Mounts}}'
你应该重点看什么
看输出里是否出现:
- `Type: volume`
- 卷名接近 `clawpanel-all-in-one_openclaw-data`
- 目标挂载路径是 `/root/.openclaw`
本次实战确认的结论
这次真实成功迁移里,旧卷名已实锤为:
clawpanel-all-in-one_openclaw-data
后面示例如果没特别说明,都按这个真实卷名演示。
---
B-4. 先做重装前备份
这一步的目的
在真正迁移前,先把当前 compose、脚本、运行目录和 bind mount 数据(如果已有)打一个时间戳备份。
要执行的命令
cd /opt/clawpanel-all-in-one
./scripts/backup-pre-reinstall.sh
预期结果
脚本应输出类似:
✅ backup done: /opt/clawpanel-all-in-one/backup/pre-reinstall-20260328T090000Z
备份完成后立即检查
find /opt/clawpanel-all-in-one/backup -maxdepth 2 -mindepth 1 | sort | tail -n 20
说明
即使你觉得“反正旧 volume 还在”,这一步也不要省。多一层保险总比真出事后哭强。
---
B-5. 创建 bind mount 目标目录
这一步的目的
为迁移后的宿主机数据目录做好承接位置。
要执行的命令
mkdir -p /opt/clawpanel-all-in-one/data/openclaw
ls -ld /opt/clawpanel-all-in-one/data/openclaw
预期结果
应能看到目录存在。
注意
- 如果目录已经存在,不是问题
- 真正危险的是:你以为存在,实际路径写错了
---
B-6. 把旧 volume 数据复制到宿主机 bind mount 目录
这一步的目的
把旧的 `/root/.openclaw` 数据从 Docker volume 拷出来,复制到宿主机目录 `/opt/clawpanel-all-in-one/data/openclaw`。
要执行的命令
cd /opt/clawpanel-all-in-one
./scripts/migrate-openclaw-data.sh clawpanel-all-in-one_openclaw-data
如果你已经核对出自己机器的旧卷名不同
那就把最后一个参数替换成你实际查出来的卷名,不要死抄:
./scripts/migrate-openclaw-data.sh 你的真实旧卷名
预期结果
脚本应输出类似:
==> source volume: clawpanel-all-in-one_openclaw-data
==> target dir : /opt/clawpanel-all-in-one/data/openclaw
✅ migration copy finished
迁移完成后立即检查目标目录
find /opt/clawpanel-all-in-one/data/openclaw -maxdepth 2 -mindepth 1 | sort | sed -n '1,120p'
你应该看到什么
至少应看到大量原本属于 `/root/.openclaw` 的数据,例如:
- `workspace/`
- `extensions/`
- `agents/`
- `openclaw.json`
如果目标目录还是空的怎么办
优先排查:
1. 你传入的旧卷名是不是错了
2. 旧容器真实挂载的卷名是不是另一个名字
3. `docker run -v 旧卷:/from ...` 是否能实际读到内容
不要在这一步目录还是空的时候直接继续 `up -d`。那样很容易把“迁移失败”伪装成“新环境覆盖旧环境”。
---
B-7. 写入正式的 V1 `docker-compose.yml`
如果你前面已经按方案 A 写好了 `docker-compose.yml`,这里仍然建议再核对一遍,确保你真正用的是 bind mount 版本,而不是旧的 volume 版本。
再次确认 compose 文件中的关键行
执行:
grep -n "./data/openclaw:/root/.openclaw" /opt/clawpanel-all-in-one/docker-compose.yml
grep -n "gateway-healthcheck.js" /opt/clawpanel-all-in-one/docker-compose.yml
grep -n "openclaw gateway" /opt/clawpanel-all-in-one/docker-compose.yml
预期结果
应至少命中下面这些核心特征:
- `./data/openclaw:/root/.openclaw`
- `./runtime/gateway-healthcheck.js:/healthcheck/gateway-healthcheck.js:ro`
- `openclaw gateway`
如果 grep 没命中怎么办
不要继续启动,先回去把 compose 改对。
---
B-8. 启动新结构
要执行的命令
cd /opt/clawpanel-all-in-one
docker compose up -d --build
启动后立刻看状态
docker compose ps
docker compose logs --tail=100 gateway
docker compose logs --tail=100 clawpanel
预期结果
1. `gateway` 起得来
2. `clawpanel` 起得来
3. gateway 后续应转为 `healthy`
4. 旧数据应被新结构正确使用,而不是变成一套空白的新环境
---
B-9. 验证旧数据确实被新结构接住了
要执行的命令
./scripts/verify-openclaw.sh
再补充执行:
find /opt/clawpanel-all-in-one/data/openclaw/workspace -maxdepth 2 -mindepth 1 | sort | sed -n '1,120p'
find /opt/clawpanel-all-in-one/data/openclaw/extensions -maxdepth 2 -mindepth 1 | sort | sed -n '1,120p'
find /opt/clawpanel-all-in-one/data/openclaw/agents -maxdepth 2 -mindepth 1 | sort | sed -n '1,120p'
ls -l /opt/clawpanel-all-in-one/data/openclaw/openclaw.json
预期结果
你应该在宿主机上直接看到:
- 原来的工作区文件
- 记忆文件
- 扩展插件目录
- agent 会话目录
- `openclaw.json`
这一步如果成立,才说明本次迁移真正成功了。
---
B-10. 保留旧 volume,不要急着删
这一步的目的
给自己留回滚锚点。
本次实战的明确结论
旧 volume:
clawpanel-all-in-one_openclaw-data
目前**建议保留**,不要删。
为什么不要删
因为新 bind mount 结构虽然已经实测跑通,但观察期内保留旧卷,能给你这些保障:
- 万一发现遗漏数据,还能回查
- 万一新结构后面又出问题,还有额外保险
- 可以做二次离线导出
当前建议
至少等新结构稳定运行一段时间后,再考虑是否清理旧卷。
---
六、部署完成后的最终验收清单
这一节无论是新装还是重装,都必须做。
6.1 容器状态验收
执行:
cd /opt/clawpanel-all-in-one
docker compose ps
通过标准
应该看到:
- `clawpanel` -> `Up`
- `openclaw-gateway` -> `Up (healthy)`
如果 `gateway` 不是 `healthy`,先别自我安慰说“应该没事”,先往下查。
6.2 gateway 健康检查验收
执行:
curl -fsS http://127.0.0.1:18789/health
通过标准
必须返回:
{"ok":true,"status":"live"}
6.3 gateway 日志特征验收
执行:
docker compose logs --tail=200 gateway
通过标准
应能看到类似信号:
- `heartbeat started`
- `health-monitor started`
- `agent model:`
- `listening on ws://127.0.0.1:18789`
- `openclaw-weixin` 相关启动日志
- `weixin monitor started`
6.4 clawpanel 日志特征验收
执行:
docker compose logs --tail=200 clawpanel
通过标准
应能看到类似:
- `ClawPanel Web Server (Headless)`
- `http://0.0.0.0:1420/`
- `配置目录: /root/.openclaw`
6.5 数据目录验收
执行:
find /opt/clawpanel-all-in-one/data/openclaw -maxdepth 2 -mindepth 1 | sort | sed -n '1,120p'
通过标准
宿主机上应能直接看到 OpenClaw 关键数据,而不是空目录。
重装场景里尤其要确认:
- `workspace/`
- `workspace/MEMORY.md`
- `workspace/memory/`
- `extensions/`
- `agents/`
- `openclaw.json`
6.6 业务侧验收
最后再做人类视角的业务验收:
1. 打开 webchat,看是否能正常对话
2. 检查微信插件是否正常工作
3. 检查原有记忆 / 配置 / 会话是否仍可继续使用
这一步不能省,因为“容器起来了”不等于“业务真的可用了”。
---
七、常见故障与优先排查顺序
7.1 `gateway` 显示 `unhealthy`
先执行:
docker compose ps
docker compose logs --tail=200 gateway
curl -fsS http://127.0.0.1:18789/health
怎么判断
- 如果 `/health` 已经返回 `{"ok":true,"status":"live"}`
但 compose 仍显示 `unhealthy`
才去怀疑 healthcheck 本身
- 但本文这套 V1 已经把 healthcheck 切到 Node 脚本,正常情况下不该再因为 `curl` 缺失误报
7.2 gateway 启动失败
先看 compose 里是不是把命令写错了。
**正确:**
openclaw gateway
**错误:**
openclaw gateway start
openclaw gateway start --foreground
这两个错误命令不要再碰。
7.3 迁移后 `data/openclaw` 还是空的
优先怀疑:
- 旧卷名写错
- 没有先 inspect 真实挂载
- 复制时其实没从正确的卷里读到数据
本次实战的关键坑就是:
- 真正旧卷是 `clawpanel-all-in-one_openclaw-data`
- 不是很多人会想当然写的 `openclaw-data`
7.4 重装后看起来像全新环境
这通常意味着:
- bind mount 目录没接到原数据
- 或者你启动时绑定的是一个空目录
不要继续盲目登录、盲目初始化,先查宿主机 `data/openclaw` 里到底有没有旧数据。
---
八、这套 V1 方案当前的边界
本文是当前真实成功路径的完整沉淀,但也要诚实说清边界:
1. 当前 gateway 仍是运行时在线安装 CLI,不是最终镜像固化版
2. 本文解决的是“本次真实可复现成功路径”,不是宣称未来所有版本、所有插件行为永远不变
3. 首次接入外部平台(例如某些插件登录、扫码、授权)仍可能需要人工参与
4. 如果你未来要继续追求更强固化,下一步方向应是:
- 评估单独 `Dockerfile.gateway`
- 在镜像构建阶段预装 OpenClaw CLI
- 进一步减少运行时在线安装依赖
但这些属于 V2,不是本文要硬塞进去假装已经做完的内容。
---
九、最终总结
如果你要的是一篇:
- 不省关键步骤
- 区分新装和重装
- 命令能直接抄
- 文件内容完整给出
- 能按步骤复现本次真实成功结果
- 并且能把数据切到宿主机 bind mount、让 webchat / gateway / 微信插件链路继续正常工作的教程
那么本文这版 V1,就是当前应该使用的版本。
最后把最关键的事实再压一遍:
当前 V1 成功基线
- 项目根目录:
`/opt/clawpanel-all-in-one`
- 数据根目录:
`/opt/clawpanel-all-in-one/data/openclaw`
- gateway 正确命令:
`openclaw gateway`
- healthcheck:
Node 脚本,不用 curl 探测
- 旧卷真实名字:
`clawpanel-all-in-one_openclaw-data`
- 旧卷当前建议:
先保留,不要急着删
当前推荐的日常检查顺序
cd /opt/clawpanel-all-in-one
./scripts/verify-openclaw.sh
如果还要再补:
docker compose ps
curl -fsS http://127.0.0.1:18789/health
做到这里,你就不是“这次凑巧跑通”,而是已经把一套**可复现、可排查、可回滚、可继续维护**的 V1 路线真正搭起来了。
---
标签建议
- OpenClaw
- ClawPanel
- Docker
- Docker Compose
- 微信插件
- 部署教程
- 运维实战
- 数据迁移
SEO 描述
这是一篇基于真实成功路径整理的 ClawPanel + OpenClaw Gateway Docker 一体化部署实战教程,完整覆盖全新安装、旧环境重装迁移、bind mount 数据切换、gateway 健康检查、验收脚本与常见故障排查,适合希望按步骤复现可用状态的新手用户。
评论区