
ZeroClaw 这个开源自托管的轻量级任务调度与自动化流程编排工具最近在开发者圈子里讨论的人不少。它解决的问题其实很直接你要在服务器上跑定时任务、接收 Webhook 触发、跨节点调度一批脚本或 API 调用又不想被重量级的商业调度平台绑住那 ZeroClaw 这类自托管方案就很对胃口。它的安装部署本身不算难但真正的坑往往集中在下半场数据库怎么接、执行器怎么注册、HTTPS 怎么配、升级时怎么保证任务数据不丢。这篇文章我按生产环境标准走一遍完整流程从服务器初始化、Docker 环境准备到编排文件编写、首次启动初始化再到故障排查和日常维护尽量把所有选择背后的原因也讲清楚。适合刚接触自托管、习惯用 Docker Compose 部署服务的开发者也适合那些已经装过一次、但被各种细节折磨过的人。文中所有命令我都在 Debian 系发行版上验证过CentOS / RHEL 系需要把包管理命令对应替换一下。1. 部署前的需求分析与方案选型1.1 ZeroClaw 到底是个什么组件简单说ZeroClaw 是一套可以自己架设的“任务中枢”。它把三类常见需求合并到了一个面板里定时任务、Webhook 触发、跨机器执行脚本。定时任务类似服务器自带的 cron但多了可视化管理、历史执行记录、失败重试和告警通知。Webhook 触发外部系统可以往指定地址发一个 HTTP 请求ZeroClaw 收到后立刻拉起对应任务流适合做自动化联动。跨节点执行一个控制端可以管理多台执行器机器任务可以指定在哪个执行器上运行而不是把所有脚本堆在一台机器上。如果说 cron 是一个只能定闹钟的微型计算器那 ZeroClaw 更像是把闹钟、接线板和报表合到一起的中控台。对于个人服务器、小团队内部系统、实验室设备调度这些场景用它来管脚本任务比手动维护一堆 crontab 文件要省心得多。但问题也在这里它不是一个开箱即用的单文件工具默认安装依赖 PostgreSQL 做数据存储、Redis 做消息队列还需要执行器节点配合所以部署时要操心的事情确实不少。1.2 三种部署方式怎么选我见过有人直接在宿主机上裸奔安装也有人一上来就想上 Kubernetes其实都不太适合。先看一张表部署方式优点缺点适合场景裸机直接部署少一层容器抽象心理上觉得“简单”环境隔离差依赖冲突时很难收拾迁移和回滚成本高一台机器只跑这一个工具且完全不想学 DockerDocker Compose 单机编排环境隔离、升级回滚方便、配置集中、数据目录清晰需要理解一点点容器网络和卷的概念绝大多数个人和小团队场景也是本文采用的方式Kubernetes 集群部署高可用、弹性伸缩运维成本高资源占用大对一个单机工具来说是杀鸡用牛刀已经有一套 K8s 集群且需要管理大量执行器节点我推荐 Docker Compose 的核心原因有两个。一是 ZeroClaw 的官方镜像更新节奏比较快用容器跑可以做到“拉新镜像、重启服务、不行就回滚”这套操作在裸机环境下要折腾很多。二是它的数据目录、日志目录和配置可以完全收拢到一个项目文件夹里备份时拷贝几个目录就走出了问题也能快速在新机器上重建。1.3 资源预估与端口规划先说配置底线。如果只是管理十几条定时任务、触发频率不高1 核 2G 内存的机器也能跑起来但 Web 界面操作时会有明显迟滞任务并发一多还可能 OOM。个人建议至少 2 核 4G磁盘 20G 起步、建议 SSD。PostgreSQL 和 Redis 本身吃不了多少资源真正占内存的是任务并发执行时的子进程所以内存宽松点没有坏处。端口规划这块容易踩坑很多第一次部署的人把所有端口都暴露到公网结果被扫描器盯上。我的建议是只把 Web 面板端口对外开放其他全部绑到容器内网或者 127.0.0.1服务默认端口暴露范围说明ZeroClaw Web 面板8080对外反代后仅走 443浏览器访问和管理入口ZeroClaw 执行器9090控制端内网或可信局域网执行器和控制端通信不要直接暴露公网PostgreSQL5432仅容器内网数据存储不需要对宿主机开放Redis6379仅容器内网消息队列和缓存绝对不要公网裸奔这里还有一个小细节如果你打算在另一台机器上跑执行器节点那 9090 端口至少要能被那台机器的 IP 访问需要在防火墙安全组里精确放行来源 IP而不是 0.0.0.0/0。2. 服务器初始化和 Docker 环境准备2.1 系统安全初始化拿到一台新的 Linux 服务器我建议先花十分钟做完初始化再碰业务。很多人跳过这步直接装 Docker后面 SSH 爆破、权限混乱的问题全都来了。以下操作默认你是 root 用户系统是 Debian 系的。apt update apt upgrade -y apt install -y curl wget vim ufw升级系统不是例行公事ZeroClaw 依赖的 PostgreSQL 和 Redis 镜像对内核和 libc 版本有隐性要求旧系统镜像启动后可能直接 segment fault到时候排查起来会很痛苦。然后创建一个日常使用的普通用户adduser deploy usermod -aG sudo deploy为什么要专门建一个 deploy 用户而不是一直用 root因为 Web 面板管理的任务是要在你的服务器上执行脚本的如果任务以 root 身份跑一旦任务内容被恶意篡改等于把整台机器的控制权交给了对方。用普通用户跑任务即使出问题损失也限定在这个用户权限范围内。ZeroClaw 的执行器在容器里默认也会降权运行宿主机侧再拦一道整体安全面就小很多。接着配置防火墙。我个人习惯用 ufw规则简单明了ufw default deny incoming ufw default allow outgoing ufw allow 22/tcp ufw allow 80/tcp ufw allow 443/tcp ufw enable这里只放行了 SSH、HTTP、HTTPS。8080 和 9090 端口不需要从公网访问后面用 Nginx 反代的时候Docker 容器端口只需要绑定在 127.0.0.1 上即可。2.2 安装 Docker 与 Docker Compose 插件安装 Docker 我建议走官方 apt 源而不是直接执行网上流传的 curl 一键脚本。一键脚本虽然快但你不知道它到底往系统里写了什么一旦出问题很难追溯。先把官方 GPG key 和仓库加进来install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg chmod ar /etc/apt/keyrings/docker.gpg然后写入 apt 源配置。注意发行版代号要从官方文档里确认别照抄别人的。写好后更新索引并安装apt update apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin安装完验证三个命令docker --version docker compose version systemctl enable --now docker如果docker compose version能正常输出说明 Compose 插件装好了。这一步很关键很多老教程还在教docker-compose独立命令的用法和现在的 Compose 插件是两回事。整个 ZeroClaw 部署我全程使用docker compose带空格这种新式写法。2.3 项目目录规划把服务都跑在容器里不代表文件可以乱丢。我习惯创建一个独立项目目录把所有东西收拢在一处mkdir -p /opt/zeroclaw/{data/{pgdata,redisdata,logs},backup} cd /opt/zeroclaw touch docker-compose.yml .env目录结构作用如下data/pgdataPostgreSQL 数据文件这是整个工具最重要的数据千万不能丢。data/redisdataRedis 持久化文件任务排队信息在这里。data/logs各容器日志挂载目录方便统一查看。backup后续定时备份脚本的输出目录。有人会用 Docker 命名卷来管理数据但我更倾向于这种 bind mount 目录方式。原因只有一个备份时直接压缩目录就够了不用再执行docker run --rm -v ...之类的导出操作灾难恢复时心智负担小很多。3. 编排文件与核心配置解析3.1 docker-compose.yml 分块详解下面这份 Compose 文件是我在实际部署中打磨过的版本不是随手抄的模板。省略了不必要的高大上配置保留了生产环境最需要关注的部分。先看完整内容services: postgres: image: postgres:16-alpine container_name: zeroclaw-postgres restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} volumes: - ./data/pgdata:/var/lib/postgresql/data healthcheck: test: [CMD-SHELL, pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}] interval: 10s timeout: 5s retries: 5 networks: - zeroclaw-net redis: image: redis:7-alpine container_name: zeroclaw-redis restart: unless-stopped command: [redis-server, --appendonly, yes, --requirepass, ${REDIS_PASSWORD}] volumes: - ./data/redisdata:/data healthcheck: test: [CMD, redis-cli, -a, ${REDIS_PASSWORD}, ping] interval: 10s timeout: 5s retries: 5 networks: - zeroclaw-net app: image: your-registry/zeroclaw-core:${ZEROCLAW_VERSION} container_name: zeroclaw-app restart: unless-stopped depends_on: postgres: condition: service_healthy redis: condition: service_healthy environment: DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}postgres:5432/${POSTGRES_DB} REDIS_URL: redis://:${REDIS_PASSWORD}redis:6379/0 SECRET_KEY: ${ZEROCLAW_SECRET_KEY} TZ: ${TZ} ports: - 127.0.0.1:8080:8080 volumes: - ./data/logs:/app/logs networks: - zeroclaw-net executor: image: your-registry/zeroclaw-executor:${ZEROCLAW_VERSION} container_name: zeroclaw-executor restart: unless-stopped depends_on: - app environment: EXECUTOR_TOKEN: ${EXECUTOR_TOKEN} CONTROL_SERVER: http://app:8080 TZ: ${TZ} ports: - 127.0.0.1:9090:9090 volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./data/logs:/app/logs networks: - zeroclaw-net networks: zeroclaw-net: driver: bridge几个关键点展开说明postgres和redis都配置了 healthcheck。这是整个编排中最容易被忽略但最重要的部分。如果没有健康检查app 容器启动时可能数据库还没就绪然后连接失败退出重启后数据库刚好好了看起来没问题但一旦机器负载高、数据库启动变慢服务就会陷入“启动-退出-再启动”的循环。depends_on配合condition: service_healthy能保证 app 只在数据库真正可用后才启动。app的端口绑定写的是127.0.0.1:8080:8080这意味着外部无法直接访问 8080只能通过本机的 Nginx 转发。这比直接8080:8080要安全得多。你机器被扫描后8080 端口是关闭状态攻击面直接少了一个。executor挂载了 Docker socket这是为了让执行器能动态拉起任务容器。这里要提醒一句挂载/var/run/docker.sock等同于把 Docker 控制权交给容器所以执行器容器本身的镜像可信度要非常重视部署时务必锁定版本号而不是用 latest 标签。3.2 环境变量、密钥与生产配置.env文件是 Compose 的灵魂所有敏感信息都从这里注入。下面是一份可直接改用的模板# 基础配置 ZEROCLAW_VERSION1.4.2 TZAsia/Shanghai # 数据库配置 POSTGRES_USERzeroclaw POSTGRES_PASSWORDChangeMe_StrongPassword POSTGRES_DBzeroclaw # 缓存配置 REDIS_PASSWORDChangeMe_RedisPassword # 应用安全 ZEROCLAW_SECRET_KEYChangeMe_RandomHex EXECUTOR_TOKENChangeMe_ExecutorToken密码和密钥怎么生成手工敲一个“看起来随机”的字符串是不够的。用下面两条命令生成openssl rand -hex 32 openssl rand -base64 24第一条生成 64 位十六进制密钥适合当 SECRET_KEY第二条生成带有特殊符号的密码适合当数据库密码。但我个人建议密码尽量只用大小写字母和数字不要包含#$%这些符号。为什么要避开因为连接串DATABASE_URL里密码是拼在 URL 里的特殊字符需要转义一旦转义没处理好就会出现“密码明明对了但连不上”的诡异问题。后文故障排查里我会细说。ZEROCLAW_VERSION这个变量值得单独强调。很多人喜欢写latest图省事。latest标签在生产环境是灾难因为每次docker compose pull都可能拉到一个行为和配置完全不兼容的新版本。我建议部署前先到项目发布页面确认当前稳定版本号写到.env里之后手动升级时再显式修改版本号。3.3 数据持久化与备份思路Compose 文件里已经把 PostgreSQL 数据挂载到了宿主机目录但挂载不等于备份。数据库文件是持续写入的直接拷贝目录可能得到一份损坏的数据。正确姿势是使用 PostgreSQL 的逻辑备份工具pg_dump。docker exec zeroclaw-postgres pg_dump -U ${POSTGRES_USER} -d ${POSTGRES_DB} -F c -f /backup/zeroclaw_$(date %F).dump备份完成后配合 cron 异机拷贝或者云存储同步才能算完成闭环。我个人会在备份目录里只保留最近 7 天的文件避免磁盘被每日备份占满。恢复的时候用pg_restore导入即可后面升级章节我会给完整流程。4. 首次启动与初始化配置4.1 拉取镜像、启动服务与日志观察配置文件和目录都准备好之后第一次启动其实只需要两条命令cd /opt/zeroclaw docker compose pull docker compose up -dpull先把所有镜像拉下来避免up -d边拉边启动时半路卡住。等命令执行完不要急着打开浏览器先看服务状态docker compose ps正常情况下四个容器应该都是 Up 状态并且 postgres 和 redis 显示 healthy。如果某个容器显示 restarting先看它退出前的日志docker compose logs -f app docker compose logs -f executor首次启动时有一点要特别注意app 容器第一次运行会自动执行数据库迁移这个过程可能在日志里表现为较长时间的“静默”。如果你看到 app 日志停在某条初始化信息上超过一分钟别急着重启容器多等一会儿。我遇到过很多次就是在数据库迁移执行到一半时手动重启容器导致表结构不完整后面怎么修都别扭。4.2 Web 面板初始化与管理员创建容器都稳定运行后在浏览器里访问http://服务器IP:8080。页面正常打开后会进入初始化向导需要做三件事第一确认数据库连接信息。向导默认会读取容器环境变量里的DATABASE_URL正常情况下不需要手动修改。如果你安装时动了配置这里会直接报错回头检查.env再重启。第二创建管理员账号。邮箱、用户名、密码三件套密码强度要求比较高建议直接用密码管理器生成一个随机强密码不要图方便用弱密码。第三注册默认执行器。这里有个大坑页面会让你填执行器地址容器内通信应该填http://executor:9090但如果你需要在浏览器里查看执行器状态或者手动测试连通性这个地址访问不到。我的做法是在面板配置中填容器内网地址http://executor:9090同时确保执行器容器的 9090 端口绑定了127.0.0.1:9090这样控制端和执行器走 Docker 内部网络保持通信稳定。初始化完成后登录管理面板建议先创建一个简单测试任务比如每分钟执行一次echo hello确认整个链路是通的再慢慢添加真实任务。4.3 HTTPS 与反向代理接入光用 IP 加端口访问面板不是长久之计Cookie 安全和任务回调的 URL 都会出问题。生产环境必须套一层 HTTPS。前面 Compose 文件里 app 端口已经绑定了127.0.0.1:8080现在只需要在宿主机上配置 Nginx 反代即可。server { listen 80; server_name your.domain.com; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name your.domain.com; ssl_certificate /etc/letsencrypt/live/your.domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/your.domain.com/privkey.pem; client_max_body_size 20m; location / { proxy_pass http://127.0.0.1:8080; 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_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; } }proxy_set_header X-Forwarded-Proto别漏掉否则 ZeroClaw 会认为当前请求还是 HTTP生成的任务回调 URL 就会全是 http://外部系统回调时会漏掉证书校验或者被防火墙拦掉。证书申请用 ACME 客户端签发即可推荐先用--dry-run测试通过再正式签发这个习惯能帮你避免申请次数被限流。配置完成后执行nginx -t systemctl reload nginx然后强制刷新浏览器访问https://your.domain.com面板地址栏应该显示小锁图标。5. 常见问题与故障排查实录5.1 容器一直重启exit code 怎么读故障排查最关键的一步是先看退出码。docker ps -a里每个容器都有一个 STATUS 列后面括号里的数字就是退出码。不同退出码含义差别很大退出码常见原因处理思路0容器正常退出比如任务容器执行完检查是否配置错了启动命令1应用内部错误、配置缺失看docker compose logs尾部日志137被系统 OOM killer 杀掉用 dmesg143收到 SIGTERM 被停止通常是docker compose down或重启造成的130收到 CtrlC / SIGINT手动中断导致其中 137 是我见过最多的。低配机器上跑 ZeroClaw 全家桶再加几个并发任务内存很容易打满。确认是 OOM 后优先优化任务并发数减少同时执行的 worker 数量实在不行再考虑给 Docker 加内存限制比如在 compose 里加mem_limit: 1g让单个容器不会把整台机器拖死。5.2 数据库连接不上的两种典型情况数据库连不上是新手翻车率最高的问题仔细看其实就两类第一类密码特殊字符导致连接失败。比如你生成的密码里有在DATABASE_URL里就成了postgresql://user:psspostgres:5432/dbURL 解析器会把pss前面的当成用户名和密码的分隔符整个连接串就乱套了。即使你在一开始避免了特殊字符从别处抄来的配置里也可能中毒。排查时可以直接在 app 容器里打印环境变量检查docker exec zeroclaw-app env | grep DATABASE_URL看到变量值带上了奇怪的分隔符基本就是这里的问题。第二类容器间网络不通。app 容器里访问数据库应该用服务名postgres而不是localhost或宿主机 IP。如果你在DATABASE_URL里写的是localhost:5432那 app 容器会去连它自己的回环地址当然连不上数据库。检查一下数据库 URL 里的主机名是不是postgres这是 Compose 网络别名别的都不能用。5.3 执行器离线任务一直卡在队列里面板里执行器状态显示离线但容器明明在运行这是另一个高频问题。按照下面顺序排查第一步在 app 容器里手动请求执行器地址确认网络通docker exec zeroclaw-app curl http://executor:9090/health如果这里都不通检查两个容器是不是在同一个 Docker 网络里。有时候你手动docker network create过自定义网络而 compose 文件用的网络名不一致两个容器就在不同网络了。第二步如果网络通但执行器还是离线大概率是 Token 不匹配。执行器容器里配置的EXECUTOR_TOKEN和控制端环境变量里的必须一模一样。麻烦的是面板里显示的执行器 Token 是注册时生成的如果你后来改过.env里的 Token执行器会拿着新 Token 去找 appapp 还是按旧 Token 校验自然认不出来。解决方法很简单在面板里删除旧执行器然后重新注册使用新的 Token。第三步检查执行器所在机器和控制端机器的系统时间。执行器通信时会带时间戳校验如果两台机器时间偏差超过 5 分钟控制端会直接丢弃心跳包表现就是“执行器在线几分钟后离线然后又在线”。这个问题我在很多服务器上遇到过尤其是一些没配 NTP 的云主机时间漂移很严重。解决方案是统一配置系统时间同步服务别只靠手动物理改时间。5.4 版本升级时保证数据不丢升级 ZeroClaw 本身不复杂但顺序错了就可能丢数据或者配置文件被覆盖。我总结了一套安全升级流程第一步备份数据库。这是任何操作之前都必须做的前置动作cd /opt/zeroclaw docker compose exec postgres pg_dump -U ${POSTGRES_USER} -d ${POSTGRES_DB} -F c -f /backup/zeroclaw_pre_upgrade.dump第二步备份整个项目目录tar czf backup/zeroclaw_$(date %F).tar.gz docker-compose.yml .env data/第三步停掉旧版本服务docker compose down第四步修改.env里的ZEROCLAW_VERSION然后重新拉取并启动docker compose pull docker compose up -d第五步验证面板能正常登录创建的任务还在执行器能重新连上来。全部确认无误后再把刚才的备份文件同步走。这套流程里最关键的就是第一步。很多人直接从第三步开始数据库版本升级时可能自动做迁移但万一迁移失败没有备份就只能找运维花时间修复那个代价可比多敲一行命令大得多。升级后如果发现新版本有兼容问题还能用docker compose down后改回旧版本号、从备份里恢复数据库整个过程可控可回滚。6. 日志管理与日常巡检6.1 日志增长失控怎么限制ZeroClaw 跑起来之后日志增长是个慢性病。任务一多每个任务执行的输出都写进容器日志而 Docker 默认的 json-file 日志驱动是不限制体积的。如果不加控制几个月后/var/lib/docker/containers/目录能把磁盘塞满。解决办法是在 Compose 文件里给每个服务加日志轮转配置logging: driver: json-file options: max-size: 10m max-file: 3意思是单个日志文件最大 10MB最多保留 3 个文件。这样每个容器日志占用最多 30MB四个容器加起来也就一百多兆完全可控。这个配置加好后需要docker compose up -d重建容器才生效已经产生的历史日志要手动清理docker system prune -f --volumes注意这条命令会删除所有未被容器使用的数据卷执行前确认没有其他项目的重要数据依赖在这些卷上。稳妥一点的做法是只清理日志文件truncate -s 0 /var/lib/docker/containers/*/*-json.logtruncate清空文件内容但不删除文件正在写入日志的容器不会受影响这是我最常用的应急手段。6.2 日常巡检清单部署完成不等于可以甩手不管。任务调度工具是典型的“平时没事出事才知道重要”的服务。我建议每周花五分钟跑一遍下面这张巡检表检查项命令预期结果容器运行状态docker compose ps四个容器均为 Uppostgres/redis 为 healthy磁盘剩余空间df -h /opt/zeroclaw使用率低于 80%内存占用free -h无持续增长的 Swap 使用最近执行失败的任务面板任务列表无意外失败任务备份文件是否生成ls -lh /opt/zeroclaw/backup/最近 7 天内有备份文件容器日志体积du -sh /opt/zeroclaw/data/logs/无明显暴涨这些命令不需要每天跑做成每周一次的固定动作就足够。如果你想再省事一点可以写一个简单的巡检脚本通过系统 cron 任务执行发现异常时发一封提醒邮件。巡检的意义不在于“做样子”而是把故障消灭在萌芽阶段。磁盘满了数据库会直接拒写内存泄漏会导致 OOM 杀容器这些都是事后处理很痛苦的问题。定期看一眼几秒钟的事情能省掉后面大半天的排查时间。最后分享一个我在实际操作中踩过几次坑之后的体会安装部署这类工具最忌讳的是一上来就照着网上教程猛敲命令。先把每个配置项的含义搞清楚知道它为什么存在后面排查问题时你才有方向感。ZeroClaw 这套流程走通之后你会对 Docker Compose 的网络、健康检查、数据卷有了更直觉的理解下次部署任何自托管服务都会快很多。如果你在部署过程中卡在某个具体环节比如执行器注册不上、面板选项加载不出来或者数据库迁移报错回头对照这篇文章里的排查顺序走一遍大部分问题都能锁定到具体原因。实在解决不了的再带着日志去项目社区提问。请记住提问时一定要附上docker compose logs的输出这是对方帮你定位问题的最快途径。