Cronicle Docker Compose 部署指南
部署篇:从零用官方镜像 cronicle/cronicle 完成单机 Cronicle 的 Docker Compose 部署、首启初始化、管理员改密、测试作业验证与数据持久化,并给出等价 docker run 形态。
Cronicle Docker Compose 部署指南
目标:在一台 Linux 服务器上从零开始,用官方镜像以 Docker Compose 方式部署单机版 Cronicle 分布式任务调度器,完成首启初始化、管理员密码修改、测试作业执行验证与数据持久化,并给出等价 docker run 形态。已在 Ubuntu 22.04(x86_64,Docker 29.x + Compose v2)、Cronicle 0.9.131(官方镜像)实测通过。
一、环境信息
本篇实测环境与关键可复现信息如下:
- 操作系统:Ubuntu 22.04(x86_64),内核 5.15+
- Docker:29.x(含 Compose v2 插件,
docker compose version可执行) - Cronicle 版本:0.9.131(官方 Docker 镜像
cronicle/cronicle当前最新 tag,2026-09-06 推送;GitHub 最新稳定 Release 为 0.9.137,镜像 tag 更新存在滞后,以 Docker Hub 实际可拉取版本为准) - 镜像:
cronicle/cronicle:0.9.131(基于 Alpine 3.24,内置 Node.js v24.18.1,含 tini 作为 1 号进程) - 网络:自定义 bridge 网络
cronicle-net,显式子网10.89.43.0/24 - 端口规划:宿主机
8115→ 容器3012(Web UI),宿主机3013→ 容器3013(HTTPS,默认未开启) - 数据目录:宿主机
/opt/data/cronicle/{data,logs,queue,conf}分别挂载到容器/opt/cronicle/对应子目录 - 验证方式:HTTP 探活 + REST API 登录 + 测试作业执行回显
二、镜像选型说明
Cronicle 官方源码仓库(jhuckaby/Cronicle)本身不带 Dockerfile,官方通过 Docker Hub 组织 cronicle 发布镜像。2026-10-19 在 Docker Hub 实查的候选镜像如下:
| 镜像 | 说明 | 拉取量(实查) | 推荐度 |
|---|---|---|---|
cronicle/cronicle | 官方组织镜像,tag 与上游版本号对齐(0.9.x 系列) | 13 万+ | ★★★ 本篇采用 |
cronicle/edge | Cronicle Edge——实验性分支(官方描述"experimental fork"),面向边缘场景 | 7 万 | ★★ 特定场景 |
soulteary/cronicle | 第三方简化打包 | 38 万 | ★★ 可用但非官方 |
elestio/cronicle | Elestio 商业托管打包 | 4.3 万 | ★ 托管场景 |
intelliops/cronicle | 第三方单主节点打包 | 56 万 | ★★ 可用但非官方 |
关键差异与如何选:跟随官方组织镜像 cronicle/cronicle 的版本对齐度与安全更新最可控;本篇选用 cronicle/cronicle:0.9.131(当前最新 tag,latest 指向同层)。镜像内置的启动入口为 bin/manager(主节点模式,首启自动初始化存储)与 bin/worker(工作节点模式),单机部署以 manager 命令启动即可。
三、前置条件
开始部署前,逐项确认以下条件(每条都给出实测核对命令):
3.1 端口复核
确认宿主机 8115/3012/3013 端口未被占用(8115 为本方案规划的管理入口,3012/3013 为容器内服务端口,仅需检查 8115 是否空闲):
ss -ltn | grep -E ':(8115|3012|3013)\b'
实测输出为空(无占用,退出码 1),端口可用。若已被占用,请更换宿主端口映射。
3.2 磁盘空间
df -h /opt/data | tail -1
实测输出:
/dev/sda3 786G 264G 488G 36% /
剩余 488G,满足要求。Cronicle 单机数据量很小(本篇实测 data 目录约 856K),但作业日志会随时间增长,建议预留 10G 以上。
3.3 创建网络(显式子网)
创建专用 bridge 网络并显式指定子网,避免依赖 Docker 自动分配落入与本机物理网段冲突的地址池:
docker network create -d bridge --subnet 10.89.43.0/24 cronicle-net
实测输出(网络 ID):
8f2dd466d412f0eb5c945ecffc77465cdf5663c7663b4fc5f5d568e435975382
创建后自查两件事——子网确实落在规划段、宿主机默认路由未被网桥抢占:
docker network inspect cronicle-net --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'
ip route get 192.168.1.62
实测输出:
10.89.43.0/24
192.168.1.62 via 192.168.146.2 dev ens33 src 192.168.146.128 uid 0
cache
子网为 10.89.43.0/24,到局域网网关的默认路由仍走物理网卡 ens33,网络自查通过。
3.4 准备持久化与配置目录
首次挂载空目录即可让 Docker 自动创建宿主路径;但 conf 目录建议先从镜像内拷出默认配置再挂载,否则空目录会覆盖镜像内置的 config.json(Cronicle 虽有内建默认值,但首启 setup 需要读取 conf/setup.json 完成管理员账号等初始化数据写入):
mkdir -p /opt/data/cronicle/{data,logs,queue}
docker run --rm -v /opt/data/cronicle/conf:/mnt/conf cronicle/cronicle:0.9.131 sh -c 'cp -a /opt/cronicle/conf/. /mnt/conf/'
ls /opt/data/cronicle/conf/
实测输出:
config.json
emails
setup.json
ssl.crt
ssl.key
镜像内置 conf/config.json 已带随机生成的 secret_key(单机部署可直接沿用;多服务器部署时所有节点必须使用同一个值)。
四、部署步骤
4.1 编写 docker-compose.yml
在工作目录创建 docker-compose.yml(本篇实测路径为部署专用目录,读者可放在任意工作目录):
services:
cronicle:
image: cronicle/cronicle:0.9.131
container_name: cronicle-0.9.131-server
hostname: cronicle-server
command: manager
restart: unless-stopped
ports:
- "8115:3012"
- "3013:3013"
environment:
TZ: Asia/Shanghai
CRONICLE_base_app_url: "http://192.168.1.62:8115"
volumes:
- /opt/data/cronicle/data:/opt/cronicle/data
- /opt/data/cronicle/logs:/opt/cronicle/logs
- /opt/data/cronicle/queue:/opt/cronicle/queue
- /opt/data/cronicle/conf:/opt/cronicle/conf
networks:
- cronicle-net
networks:
cronicle-net:
external: true
name: cronicle-net
逐项说明:
command: manager:镜像 CMD 默认是/bin/sh,必须显式传manager才会执行「首启 setup + 前台主节点启动」的完整流程(worker为工作节点模式,仅适用于已有主节点的集群)hostname: cronicle-server:Cronicle 集群以主机名标识节点(UI 的 Servers 页签显示的就是它),显式固定便于管理CRONICLE_base_app_url:告诉 Cronicle 对外访问地址,用于邮件/Webhook 里的详情链接(环境变量覆盖 config.json 同名键,规则见第五节)TZ:容器默认时区为 America/New_York,改为 Asia/Shanghai 使调度与界面时间符合本地- 四个 volumes:数据、日志、队列、配置全部落宿主机,重建容器不丢数据
4.2 启动
docker compose up -d
实测输出:
Container cronicle-0.9.131-server Created
Container cronicle-0.9.131-server Starting
Container cronicle-0.9.131-server Started
4.3 首启初始化确认
首启时 manager 脚本会自动执行 bin/control.sh setup(初始化存储、创建管理员)。查看日志确认:
docker logs cronicle-0.9.131-server 2>&1 | head -12
实测输出:
Setup completed successfully!
This server (cronicle-server) has been added as the single primary master server.
An administrator account has been created with username 'admin' and password 'admin'.
You should now be able to start the service by typing: '/opt/cronicle/bin/control.sh start'
Then, the web interface should be available at: http://cronicle-server:3012/
Please allow for up to 60 seconds for the server to become master.
[1792388709.066][2026-10-19 13:45:09][cronicle-server][7][Cronicle][debug][2][Cronicle v0.9.131 Starting Up][{"pid":7,"ppid":1,"node":"v24.18.1","arch":"x64","platform":"linux",...}]
[1792388709.068][2026-10-19 13:45:09][cronicle-server][7][Cronicle][debug][2][Server Info:][{"hostname":"cronicle-server","ip":"10.89.43.2","daemon":false}]
[1792388709.068][2026-10-19 13:45:09][cronicle-server][7][Cronicle][debug][3][Starting component: Storage][]
[1792388709.069][2026-10-19 13:45:09][cronicle-server][7][Storage][debug][5][Setting up storage system v4.2.2][]
[1792388709.07][2026-10-19 13:45:09][cronicle-server][7][Filesystem][debug][2][Setting up filesystem storage][]
[1792388709.071][2026-10-19 13:45:09][cronicle-server][7][Cronicle][debug][3][Starting component: WebServer][]
关键三行:Setup completed successfully!(初始化完成)、username 'admin' and password 'admin'(初始账号)、Cronicle v0.9.131 Starting Up(版本确认,Node v24.18.1)。
4.4 服务状态确认
docker compose ps --format 'table {{.Name}}\t{{.Status}}\t{{.Ports}}'
实测输出:
NAME STATUS PORTS
cronicle-0.9.131-server Up 10 seconds 0.0.0.0:3013->3013/tcp, [::]:3013->3013/tcp, 0.0.0.0:8115->3012/tcp, [::]:8115->3012/tcp
宿主 8115 已监听并转发到容器 3012。
4.5 Web 探活与登录验证
HTTP 探活:
curl -s -o /dev/null -w "HTTP:%{http_code}\n" http://localhost:8115/
实测输出:
HTTP:200
用初始账号登录 REST API 验证(同时是后续 API 操作的会话获取方式):
curl -s -X POST http://localhost:8115/api/user/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"admin"}' | head -c 300
实测输出(节选):
{"code":0,"username":"admin","user":{"username":"admin","full_name":"Administrator","email":"admin@cronicle.com","active":1,...,"privileges":{"admin":1}},"session_id":"319435c9bcaf5795cc45c24bd5353ed630c38f1f0ad2d34246eea2fe22b1d3ca",...}
code:0 表示登录成功。浏览器访问 http://192.168.1.62:8115/ 可进入 Web UI(Schedule 日程视图),默认已带 General 分类与 Shell Script、HTTP Request、Test Plugin 三个内置插件。
4.6 修改管理员密码
初始 admin/admin 必须立即修改。Cronicle 的用户更新接口是 /api/user/update,改密字段是 new_password 且必须携带 old_password 验证(传 password 字段不会改密,只会把它存成普通用户属性,详见第十节常见问题 Q1):
SID=$(curl -s -X POST http://localhost:8115/api/user/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"admin"}' | python3 -c "import json,sys; print(json.load(sys.stdin)['session_id'])")
curl -s -X POST http://localhost:8115/api/user/update \
-H 'Content-Type: application/json' -H "X-Session-ID: $SID" \
-d '{"username":"admin","old_password":"admin","new_password":"q1w2e3r4"}'
实测输出:
{"code":0,"user":{"username":"admin","full_name":"Administrator","email":"admin@cronicle.com","active":1,"modified":1792388807,"created":1792388708,"privileges":{"admin":1},...}}
用新密码重新登录确认改密生效:
curl -s -X POST http://localhost:8115/api/user/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"q1w2e3r4"}' | head -c 120
实测输出:
{"code":0,"username":"admin","user":{"username":"admin","full_name":"Administrator",...}}
⚠️ 注意:本篇为演示保留
q1w2e3r4明文示例口令;生产环境请替换为强口令,或改用独立的非 admin 运维账号并停用 admin。
4.7 创建测试作业并执行
用 API 创建一个按需触发的 Shell 事件(不传 timing 即为纯手动触发),脚本第一行必须带 shebang(原因见第十节 Q2):
curl -s -X POST http://localhost:8115/api/app/create_event/v1 \
-H 'Content-Type: application/json' -H "X-Session-ID: $SID" \
-d '{"title":"测试-Shell回显","enabled":1,"category":"general","plugin":"shellplug","target":"cronicle-server","params":{"script":"#!/bin/sh\necho hello-cronicle-test; date; hostname"},"timeout":60}'
实测输出:
{"code":0,"id":"emveu1c8y01"}
立即触发并查询结果:
JID=$(curl -s -X POST http://localhost:8115/api/app/run_event/v1 \
-H 'Content-Type: application/json' -H "X-Session-ID: $SID" \
-d '{"id":"emveu1c8y01"}' | python3 -c "import json,sys; print(json.load(sys.stdin)['ids'][0])")
sleep 3
curl -s "http://localhost:8115/api/app/get_job_status/v1?id=$JID" -H "X-Session-ID: $SID" \
| python3 -c "import json,sys; j=json.load(sys.stdin)['job']; print('code:', j['code'], '| complete:', j['complete'], '| elapsed:', j['elapsed'])"
实测输出:
code: 0 | complete: 1 | elapsed: 0.053999900817871094
code: 0 + complete: 1 即执行成功。拉取作业日志确认输出回显:
curl -s "http://localhost:8115/api/app/get_job_log/v1?id=$JID" -H "X-Session-ID: $SID" \
| python3 -c "import sys,zlib; print(zlib.decompress(sys.stdin.buffer.read(),16+zlib.MAX_WBITS).decode('utf-8'))"
实测输出:
# Job ID: jmveu3ypg04
# Event Title: 测试-Shell回显
# Hostname: cronicle-server
# Date/Time: 2026/10/19 13:49:22 (GMT+8)
hello-cronicle-test
Mon Oct 19 05:49:22 UTC 2026
cronicle-server
# Job completed successfully at 2026/10/19 13:49:22 (GMT+8).
# End of log.
作业链路(创建→触发→执行→日志)全通,部署验证完成。日志第二行 date 显示 UTC 是容器缺 tzdata 包所致(Cronicle 界面与时间戳仍是 GMT+8),处理见第十节 Q3。
4.8 重启持久化验证
重建或重启容器后,数据(账号、事件、历史)应原样保留:
docker compose restart
sleep 10
curl -s -X POST http://localhost:8115/api/user/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"q1w2e3r4"}' | python3 -c "import json,sys; print('relogin code:', json.load(sys.stdin)['code'])"
实测输出:
relogin code: 0
改过的密码重启后依然有效,说明 data 目录持久化生效。
五、数据持久化说明
Cronicle 的全部状态都在 /opt/cronicle 工作目录下,本方案拆成四个挂载点:
| 挂载点(容器内) | 宿主机目录 | 内容 | 必须持久化 |
|---|---|---|---|
/opt/cronicle/data | /opt/data/cronicle/data | 存储引擎数据(Filesystem 引擎的 JSON 文件:调度表、用户、分类、插件定义、作业日志与历史) | ★★★ 必须 |
/opt/cronicle/logs | /opt/data/cronicle/logs | 组件运行日志(Cronicle.log/Error.log 等)与作业日志目录 jobs/ | ★★ 建议 |
/opt/cronicle/queue | /opt/data/cronicle/queue | 事件队列暂存目录(queue_dir) | ★ 建议 |
/opt/cronicle/conf | /opt/data/cronicle/conf | config.json、setup.json、SSL 证书、邮件模板 | ★★★ 必须 |
实测各目录占用(部署当日):
856K data
196K logs
数据量极小但 value 极高——data 目录丢了等于集群重置(账号、调度表、历史全没)。备份建议见《Cronicle 日常运维手册》。
六、环境变量表
镜像支持以 CRONICLE_ 前缀的环境变量覆盖 config.json 中的同名键:顶层键直接用后缀,嵌套键用双下划线连接(如 CRONICLE_WebServer__https_port)。本方案实测用到的与镜像 manager 脚本支持的变量:
| 环境变量 | 示例值 | 说明 |
|---|---|---|
TZ | Asia/Shanghai | 系统时区;镜像默认 America/New_York,影响调度与界面时间显示 |
CRONICLE_base_app_url | http://192.168.1.62:8115 | 对外访问地址;邮件/Webhook 中的详情链接使用它 |
CRONICLE_secret_key | 任意随机串 | 多服务器共享密钥;单机可不设(沿用 conf 内随机值) |
CRONICLE_WebServer__http_port | 3012 | Web 端口覆盖(manager 脚本 --port 参数同义) |
CRONICLE_storage_config | 绝对路径 | 自定义存储配置文件(manager --storage 参数同义) |
覆盖优先级实测:宿主机 conf/config.json 中 base_app_url 仍为默认值 http://localhost:3012,而运行中的服务在 Webhook 报文与邮件正文中输出的链接均为环境变量注入的 http://192.168.1.62:8115(实测作业邮件正文 Job Details: http://192.168.1.62:8115/#JobDetails?id=...),证明环境变量覆盖生效且优先于配置文件。
七、配置参数说明
除环境变量外,持久化后的 conf/config.json 是配置主体(完整逐项解读见《Cronicle 0.9.131 配置参数详解》)。Compose 部署场景最常改的键:
| 键 | 默认值(镜像内 config.json) | 说明 |
|---|---|---|
base_app_url | http://localhost:3012 | 对外地址,建议用环境变量覆盖而非改文件 |
smtp_hostname / smtp_port | localhost / 25 | 邮件通知的 SMTP 服务器,通知功能必配 |
email_from | admin@localhost | 通知邮件发件人 |
universal_web_hook | 空 | 全局 Webhook,作业启动/完成/失败都会 POST JSON,对接告警必配 |
job_data_expire_days | 180 | 作业历史数据保留天数 |
maintenance | 04:00 | 每日维护窗口(过期数据清理等) |
Storage.engine | Filesystem | 存储引擎;多主集群需换 Couchbase/S3 |
修改 config.json 后需重启容器生效(docker compose restart)。本篇实测:将 universal_web_hook 从空改为接收端地址并重启后,失败作业的 Webhook 即时送达(实测报文见《Cronicle 日常运维手册》监控告警章)。
八、配置文件挂载
conf 目录整体挂载(目录挂载而非单文件挂载)有三个原因:其一,镜像内置 conf 除 config.json 外还有 setup.json(首启初始化数据)、ssl.crt/ssl.key(HTTPS 证书)、emails/(邮件模板),单文件挂载会漏掉其余文件;其二,目录挂载后宿主机编辑、容器内读取是同一份 inode,docker compose restart 即生效;其三,容器内进程以 cronicle 用户(gid 1099)运行,目录挂载保持镜像内的权限设置不动。
⚠️ 注意:若首次部署时把空目录挂到
/opt/cronicle/conf,首启 setup 会因缺少 setup.json 而无法创建初始 admin 账号。补救方式:停容器,把镜像内 conf 拷出后再挂载(见第三节 3.4 的命令),重新up -d。
九、等价 docker run 形态
不使用 Compose 时,以下 docker run 命令与本篇 compose 定义完全等价(参数一一对应):
docker run -d \
--name cronicle-0.9.131-server \
--hostname cronicle-server \
--restart unless-stopped \
-p 8115:3012 -p 3013:3013 \
-e TZ=Asia/Shanghai \
-e CRONICLE_base_app_url=http://192.168.1.62:8115 \
-v /opt/data/cronicle/data:/opt/cronicle/data \
-v /opt/data/cronicle/logs:/opt/cronicle/logs \
-v /opt/data/cronicle/queue:/opt/cronicle/queue \
-v /opt/data/cronicle/conf:/opt/cronicle/conf \
--network cronicle-net \
cronicle/cronicle:0.9.131 manager
形态等价性已实测验证:在独立端口(18115)以相同参数形态启动等价实例,Web 探活返回 HTTP:200,服务正常完成存储加载与主节点接管;验证后该临时实例已停止。
⚠️ 注意:等价实例不要与 Compose 实例共用同一套数据目录同时运行——两个主节点进程竞争同一份 Filesystem 存储,后启动的实例在 setup 阶段会直接报错(实测报错见第十节 Q4)。
十、常见问题
Q1:用 password 字段调 user/update 改密,返回成功但新密码登录不了?
改密字段是 new_password,且必须带 old_password。实测传 {"username":"admin","password":"q1w2e3r4","old_password":"admin"} 返回 {"code":0},但随后用 q1w2e3r4 登录报 {"code":"login","description":"Username or password incorrect."}——password 键被当作普通用户属性存储而非密码哈希。正确写法见第四节 4.6。
Q2:Shell 作业立刻失败,报 Child N crashed with code: 1?
Shell Script 插件把脚本内容原样写入临时 .sh 文件后直接执行,脚本第一行没有 shebang 时会抛 spawn ENOEXEC。实测作业日志(get_job_log 输出节选):
Error: spawn ENOEXEC
at ChildProcess.spawn (node:internal/child_process:441:11)
...
errno: -8,
code: 'ENOEXEC',
syscall: 'spawn'
修复:脚本第一行加 #!/bin/sh(或 #!/bin/bash)。
Q3:容器内 date 显示 UTC,TZ=Asia/Shanghai 不生效?
官方 Alpine 基础镜像不带 tzdata 包(实测 apk info -e tzdata 为空),系统 shell 的 date 恒为 UTC;但 Cronicle 应用层(调度、界面、日志时间戳)读 TZ 变量自行换算,显示 GMT+8 正常(实测作业日志头 # Date/Time: 2026/10/19 13:49:22 (GMT+8) 与正文 Mon Oct 19 05:49:22 UTC 2026 并存)。需要 shell 层时区一致时,在 Dockerfile 基础上追加 apk add tzdata 构建自有镜像即可。
Q4:第二个实例启动报 ERROR: Please stop Cronicle before running this script.?
manager 脚本启动时会先执行 setup,检测到同一份存储数据上有运行中的 Cronicle 就拒绝执行。实测复现:对已在运行的主实例的数据目录再启动一个等价容器,日志出现该报错。结论:一套数据目录同时只能有一个主节点进程;等价 docker run 验证、灾备演练都请用独立数据目录或先停原实例。
Q5:Web 能打开但登录报用户名密码错误?
首启 setup 只在数据目录为空时执行。若 data 目录里已有旧数据(比如换镜像重建容器),admin 密码沿用旧数据里的值。确认方法:查看 /opt/data/cronicle/data/ 是否有历史文件;确需重置则停容器、清空 data 目录后重新 up -d(会重新生成 admin/admin)。
十一、参考链接
- 官方镜像 Docker Hub 页:https://hub.docker.com/r/cronicle/cronicle
- Cronicle GitHub 仓库:https://github.com/jhuckaby/Cronicle
- 安装文档 Setup.md:https://github.com/jhuckaby/Cronicle/blob/master/docs/Setup.md
- 配置文档 Configuration.md:https://github.com/jhuckaby/Cronicle/blob/master/docs/Configuration.md
- Web UI 使用文档 WebUI.md:https://github.com/jhuckaby/Cronicle/blob/master/docs/WebUI.md
- REST API 文档 APIReference.md:https://github.com/jhuckaby/Cronicle/blob/master/docs/APIReference.md
- 本专栏配套文档:《Cronicle 0.9.137 介绍与选型指南》《Cronicle 0.9.131 配置参数详解》《Cronicle 日常运维手册》