ZenTao Docker Compose 部署实战

ZenTao 22.6 Docker Compose 部署实战:官方镜像选型、compose 编排、安装向导分步实录、业务全链路验收、备份首验与排障实录

ZenTao项目管理Docker ComposeMySQL2026/10/27

ZenTao Docker Compose 部署实战

版本基线:ZenTao 开源版 22.6(镜像 easysoft/zentao:22.6,2026-09-15 构建,amd64/arm64,压缩体积约 266MB) 实测环境:Linux 服务器 + Docker Compose v2,全部命令与回显均来自实际操作记录 读者前置:已安装 Docker 与 Docker Compose 插件;对 compose 编排有基础了解

一、本篇目标与环境说明

本篇解决"从零到可用"的完整落地:拉取官方镜像 → 编写 compose 编排 → 启动 → 完成安装向导 → 管理员登录 → 业务全链路验收(产品/项目/执行/版本/任务/Bug)→ 首次备份。全部步骤在一台 Linux 服务器上实测通过,文中回显均为实测原样记录。

本篇的部署形态采用官方镜像的单容器一体化模式(MYSQL_INTERNAL=true,数据库与 Web 服务运行时全部内置),这是禅道官方 Docker 部署文档推荐的最小形态;外部数据库模式在《ZenTao 22.6 配置参数详解》的数据库一节单独讨论。

实验资源规划(多服务共存的宿主机上,先规划后占用):

  • 自定义 bridge 网络:zentao-net,显式子网 10.89.65.0/24(先 docker network ls 确认 10.89.56~75 段未被占用再取值,避开 192.168/100.64 段);
  • 宿主机端口:8143(先 ss -ltn 确认未占用);
  • 数据目录:宿主机 ./data 挂载为镜像约定路径 /data。

二、镜像选型与获取

2.1 官方镜像源

禅道官方每次发版同时推送两个镜像仓库,二者内容一致:

镜像仓库适用场景
easysoft/zentao(Docker Hub)海外/默认源
hub.zentao.net/app/zentao国内直连,免加速

版本与 tag 的对应规则(官方文档 405 篇约定):开源版 tag 为纯数字(如 22.6),专业版 biz 前缀、企业版 max 前缀、IPD 版 ipd 前缀;latest 恒指向开源版最新。通过 Docker Hub API 实查 easysoft/zentao 的 22.6 相关 tag:

$ curl -s "https://hub.docker.com/v2/repositories/easysoft/zentao/tags?name=22.6" | python3 -m json.tool
...
22.6            amd64+arm64  265.8 MB  pushed 2026-09-16
22.6-php7       amd64+arm64  269.1 MB  PHP 7.2~7.4 兼容版
22.6-20260915   amd64+arm64  构建日期版

老版本镜像(docker_zentao.zip 自行构建、挂载 /app/zentaopms 与 /var/lib/mysql)已被上述新版镜像取代,升级旧栈请参考官方《旧版Docker镜像升级说明》。

2.2 镜像内运行时清单

拉取后 docker inspect 可确认镜像的内置组件版本(选型排障时有用):

$ docker inspect easysoft/zentao:22.6 --format '{{json .Config.Env}}'
["ZENTAO_VER=22.6","PHP_VER=8.1.31","MYSQL_VER=10.6.20","OS=debian-11","TZ=Asia/Shanghai", ...]

即:ZenTao 22.6 + PHP 8.1.31 + MariaDB 10.6.20 + Apache + s6 服务管理器,Debian 11 底座。镜像暴露 80 端口,约定数据卷为 /data。

三、compose 编排

项目目录结构(数据目录随用随建,权限交给镜像初始化):

/opt/data/zentao/
├── docker-compose.yml
└── data/               # 首次 up 时自动生成,挂载为 /data

docker-compose.yml 全文(实测可用):

name: zentao-22.6-stack
services:
  app:
    image: easysoft/zentao:22.6
    container_name: zentao-22.6-app
    ports:
      - "8143:80"
    environment:
      - MYSQL_INTERNAL=true
    volumes:
      - ./data:/data
    networks:
      - zentao-net
networks:
  zentao-net:
    driver: bridge
    ipam:
      config:
        - subnet: 10.89.65.0/24

要点:

  • MYSQL_INTERNAL=true 显式声明使用镜像内置 MariaDB(新版镜像该开关默认 false,不设置会导致启动后缺数据库服务);
  • /data 是唯一的持久化卷,覆盖配置文件、附件、日志、session、内置数据库数据与备份目录,整机迁移时只需拷贝该目录;
  • 生产环境建议显式写 name(本例 zentao-22.6-stack)便于多套实例共存管理。

四、启动栈

$ docker compose up -d
[+] Running 3/3
 ✔ Network zentao-226-stack_zentao-net  Created
 ✔ Container zentao-22.6-app            Started

启动后先别急着开向导,等内置服务就绪(首次启动需要初始化数据库目录,实测 30~60 秒):

$ docker compose ps
NAME              IMAGE                  COMMAND                  SERVICE   STATUS
zentao-22.6-app   easysoft/zentao:22.6   "/usr/bin/entrypoint…"   app       Up 40 seconds

$ docker logs zentao-22.6-app 2>&1 | tail -5

看到 Apache 与 MySQL 就绪日志且首页可访问(curl -I http://127.0.0.1:8143/ 返回 302/200)即进入下一步。

五、安装向导分步实录

浏览器访问 http://<服务器IP>:8143/,自动跳转到安装向导。向导共六步,以下按实测顺序记录每步的关键内容与选择。

5.1 第 1 步:语言与时区

首次进入显示安装引导页,选择系统默认语言(简体中文 zh-cn)与默认时区(实测环境选择 Asia/Shanghai,国内服务器建议显式选择,避免日志与定时任务时间偏移)。

5.2 第 2 步:许可协议

展示 ZPL 1.2 与 AGPL 双许可协议文本,勾选同意后进入下一步。

5.3 第 3 步:环境检查

向导自动检查 PHP 版本与扩展(session、pdo_mysql、json、openssl、mbstring、zlib、curl、gd 等)、目录写权限与 session 存储路径。实测该页全部通过项显示绿色。常见失败项:/data 目录写权限不足、session 路径不可写——两者都可通过保持 /data 整体可写解决。

5.4 第 4 步:数据库配置

字段实测取值说明
数据库驱动mysql固定
服务器地址127.0.0.1单容器模式填容器内地址
端口3306内置 MariaDB 默认端口
数据库用户root内置默认用户
数据库密码123456内置默认密码(可用环境变量 MYSQL_ROOT_PASSWORD 修改,改后此处同步)
数据库名zentao向导自动创建
表前缀zt_保持默认

提交后向导自动建库建表(22.6 共 339 张表),过程以进度条展示,无需人工干预。

5.5 第 5 步:使用模式

三种模式按团队形态选择,实测选择 ALM 全生命周期管理(开源版全功能):

模式启用能力
ALM(全生命周期)产品+项目+执行+测试全流程
经典项目管理弱化产品线,适合非研发团队
看板轻量可视化协作

5.6 第 6 步:管理员账号

设置公司名称与超级管理员账号。注意:禅道内置密码策略强制要求"至少 6 位且同时包含大写字母、小写字母、数字",纯小写+数字的弱密码会被拒绝(实测 q1w2e3r4 提交后向导弹窗提示"密码必须至少6位,包含大写字母、小写字母和数字")。本系列文档统一使用管理员账号 admin / 密码 Q1w2e3r4(首字母大写以满足策略)。同页可勾选"导入演示数据",实测勾选后自动生成演示产品/项目/用户,便于新团队熟悉界面。

5.7 完成与入口

向导完成后进入登录页。若此后再访问 install.php 出现 404 属正常现象:镜像内置的 sentry 守护进程检测到安装完成后会自动将 install.php/upgrade.php 移除(安全加固)。排障细节见第十节。

六、首次登录与验证

登录页会显示公司名称(安装向导第 6 步填写,实测显示"示例科技有限公司项目管理系统")。使用 admin / Q1w2e3r4 登录成功后进入"地盘"首页。

集成提示:禅道登录密码在客户端经 md5(md5(明文) + 随机数) 变换后提交,服务端再校验,浏览器登录无需关心;若用脚本做接口集成,需先请求 index.php?m=user&f=refreshRandom 获取随机数再按同样规则变换,参见《ZenTao 22.6 常用 CLI 命令与运维实战》的 API 一节。

七、业务全链路验收

部署验收的标准不是"页面能打开",而是核心对象能完整流转。以下用接口方式(curl)演示全链路,等效于界面操作;所有请求必须携带与站点同源的 Referer 头,否则 POST 体被 CSRF 防御机制清空(排障实录第一条)。

登录态保持:先按第六节规则取 rand 并以变换后的密码 POST 登录,获得会话 Cookie 后按对象顺序提交。

7.1 创建产品

$ curl -s -b cookies.txt -X POST "http://127.0.0.1:8143/index.php?m=product&f=create" \
    -e "http://127.0.0.1:8143/index.php?m=product&f=create" \
    -H "X-Requested-With: XMLHttpRequest" \
    --data-urlencode "name=智慧园区管理平台" \
    --data-urlencode "code=smartpark" \
    --data-urlencode "line=0" \
    --data-urlencode "type=normal" \
    --data-urlencode "PO=admin" \
    --data-urlencode "acl=open" \
    --data-urlencode "status=active"
{"result":"success","message":"保存成功","load":"\/index.php?m=product&f=browse&productID=3","closeModal":true,"changes":{"type":"add","objectType":"product","objectList":[3]}}

7.2 创建 Scrum 项目

$ curl -s -b cookies.txt -X POST "http://127.0.0.1:8143/index.php?m=project&f=create" \
    -e "http://127.0.0.1:8143/index.php?m=project&f=create" \
    -H "X-Requested-With: XMLHttpRequest" \
    --data-urlencode "name=智慧园区一期研发" \
    --data-urlencode "code=park-p1" \
    --data-urlencode "model=scrum" \
    --data-urlencode "hasProduct=1" \
    --data-urlencode "products[0]=3" \
    --data-urlencode "begin=2026-10-20" \
    --data-urlencode "end=2026-12-31" \
    --data-urlencode "days=50" \
    --data-urlencode "PM=admin" \
    --data-urlencode "acl=open"
{"result":"success","message":"保存成功",...,"objectList":[5]}

7.3 创建执行(冲刺)

$ curl -s -b cookies.txt -X POST "http://127.0.0.1:8143/index.php?m=execution&f=create&projectID=5" \
    -e "http://127.0.0.1:8143/index.php?m=execution&f=create&projectID=5" \
    -H "X-Requested-With: XMLHttpRequest" \
    --data-urlencode "project=5" \
    --data-urlencode "name=S1 迭代一" \
    --data-urlencode "code=park-p1-s1" \
    --data-urlencode "begin=2026-10-20" \
    --data-urlencode "end=2026-11-10" \
    --data-urlencode "lifetime=short" \
    --data-urlencode "days=15"
(创建成功,数据库内生成 sprint 对象;注意 project 字段为必填,缺省报"所属项目不能为空")

7.4 创建版本与任务

版本(Bug 关联必填项):

$ curl -s -b cookies.txt -X POST "http://127.0.0.1:8143/index.php?m=build&f=create&productID=3" \
    -e "http://127.0.0.1:8143/index.php?m=build&f=create&productID=3" \
    -H "X-Requested-With: XMLHttpRequest" \
    --data-urlencode "product=3" --data-urlencode "execution=7" \
    --data-urlencode "name=v1.0.0" --data-urlencode "build=v1.0.0" \
    --data-urlencode "date=2026-10-24" --data-urlencode "builder=admin" \
    --data-urlencode "desc=首个内部联调版本"
{"load":"\/index.php?m=build&f=view&buildID=2","id":2,...,"result":"success","message":"保存成功"}

任务:

$ curl -s -b cookies.txt -X POST "http://127.0.0.1:8143/index.php?m=task&f=create&executionID=7" \
    -e "http://127.0.0.1:8143/index.php?m=task&f=create&executionID=7" \
    -H "X-Requested-With: XMLHttpRequest" \
    --data-urlencode "type=devel" \
    --data-urlencode "name=设计门禁一体机接入协议模块" \
    --data-urlencode "pri=1" --data-urlencode "estimate=8" \
    --data-urlencode "assignedTo=admin" \
    --data-urlencode "estStarted=2026-10-21" \
    --data-urlencode "deadline=2026-10-30" \
    --data-urlencode "after=toTaskList"
{"result":"success","message":"保存成功","closeModal":true,"load":"\/index.php?m=execution&f=task&executionID=7&status=unclosed&param=0&orderBy=id_desc","changes":{"type":"add","objectType":"task","objectList":[9]}}

7.5 提交 Bug(闭环)

$ curl -s -b cookies.txt -X POST "http://127.0.0.1:8143/index.php?m=bug&f=create&productID=3&branch=0&extra=executionID=7" \
    -e "http://127.0.0.1:8143/index.php?m=bug&f=create&productID=3&branch=0&extra=executionID=7" \
    -H "X-Requested-With: XMLHttpRequest" \
    --data-urlencode "product=3" --data-urlencode "execution=7" \
    --data-urlencode "openedBuild[]=2" \
    --data-urlencode "title=门禁设备心跳超时未触发重连" \
    --data-urlencode "severity=2" --data-urlencode "pri=1" \
    --data-urlencode "type=codeerror" \
    --data-urlencode "assignedTo=admin" \
    --data-urlencode "steps=<p>[步骤] 设备断网 60s 后恢复<br/>[结果] 未重连<br/>[期望] 自动重连并上报状态</p>" \
    --data-urlencode "keywords=心跳,重连"
{"result":"success","message":"保存成功","load":"\/index.php?m=bug&f=browse&productID=3&branch=0&browseType=byModule&param=0&orderBy=id_desc"}

到此,产品→项目→执行→版本→任务→Bug 全链路打通。登录界面逐项核对:产品视图可见"智慧园区管理平台",项目 5 下有冲刺"S1 迭代一",执行 7 的任务列表与 Bug 列表各有一条记录。

八、首次备份

安装完成后立刻做一次全量备份。管理员登录 → 后台 → 系统设置 → 备份(22.6 中备份模块路径为 index.php?m=backup&f=index),点击"开始备份":

实测备份产物写入数据卷内 /data/zentao/tmp/backup/(宿主机对应 ./data/zentao/tmp/backup/):

20261027184439822_6.sql.php     # 数据库备份(PHP 头伪装的 SQL 归档,约 2.3MB)
20261027184439822_6.file/       # 附件备份目录
summary                          # 备份结果摘要(JSON)

页面返回"备份成功!"。定时自动备份在"后台 → 系统 → 定时"中开启(每日 00:30 自动备份的 cron 项默认内置),命令行备份与恢复详见《ZenTao 22.6 常用 CLI 命令与运维实战》。

九、升级与回滚

官方升级路径(405 篇约定):

  1. docker compose stop,备份宿主机 ./data 整目录(cp -rp ./data ./data_bak_$(date +%Y%m%d));
  2. 修改 compose 中镜像 tag 到新版本号(如 22.7),docker compose up -d;
  3. 访问站点按提示完成数据库结构升级向导(部分版本需在站点目录创建 ok.txt 确认)。

回滚 = 停栈 + 还原 compose 旧 tag + 还原 ./data 备份目录 + 起栈。由于配置、附件、数据库全部收敛在 /data 单卷内,该流程可安全重复。

十、排障实录

以下六条均为本次实测真实遇到的问题与解法,按出现顺序整理。

1)接口方式提交表单被静默清空(CSRF 防御) 现象:用 curl POST 安装向导/业务表单,服务端不报错但数据不落库(例如数据库始终未创建)。 原因:禅道框架的 CSRF 防御校验 POST 请求的 Referer,缺失或不同源时直接清空 $_POST(逻辑位于 framework/base/router.class.php)。 解法:所有 POST 加 -e 指定与请求 URL 同源的 Referer。这是脚本化集成禅道的第一坑。

2)管理员密码不符合策略 现象:向导最后一步提交后弹窗"密码必须至少6位,包含大写字母、小写字母和数字"。 解法:禅道强制密码复杂度,纯小写+数字的 q1w2e3r4 不合格,改用 Q1w2e3r4(文档示例统一采用该值)。

3)安装未完成时 install.php 消失(sentry 抢跑) 现象:向导进行到一半,install.php 返回 404/302,无法继续。 原因:镜像内置 sentry 守护进程轮询 /data/zentao/config/my.php,一旦检测到配置文件非空即认为安装完成,把 install.php/upgrade.php 移除并写入 /data/zentao/.version。 解法:从同版本镜像中取回原始 install.php 放回站点根目录继续向导(docker create 临时实例 + docker cp);若存在 install.php.tmp 直接改名恢复亦可。完成后 sentry 会再次移除,不影响使用。

4)脚本登录失败:密码需二重 md5 变换 现象:接口 POST 明文密码登录被 302 打回登录页。 原因:22.6 登录时服务端生成 session->rand,客户端提交 md5(md5(明文)+rand),服务端用同样规则比对。 解法:先 GET index.php?m=user&f=refreshRandom 取随机数,再按公式变换后提交,同时带上 verifyRand 字段。完整可用脚本见运维篇 API 一节。

5)创建执行时服务端 500(implode NULL) 现象:POST 创建冲刺返回 PHP Fatal error implode(): Argument #1 ($array) must be of type array(module/execution/control.php)。 原因:请求缺少可选 URL 参数导致后续 implode 收到 null,属程序健壮性问题。 确认:虽返回 500,冲刺对象已成功入库;界面刷新可见。遇同类现象先查数据再重试,勿盲目重复提交。

6)环境变量 ZT_REQUEST_TYPE 未生效 现象:镜像默认环境变量声明 ZT_REQUEST_TYPE=PATH_INFO,但运行时页面配置显示 requestType=GET,.html 后缀的 PATH_INFO URL 全部 302 到查询串形式。 处置:属于镜像层面细节,脚本集成一律使用 GET 形式 URL(index.php?m=xx&f=yy),API 使用 /api.php/v1/<entry> 路径形式,均实测稳定。

十一、小结与下一篇

至此已完成 ZenTao 22.6 的容器化部署、安装向导、业务验收与首次备份。下一篇《ZenTao 22.6 配置参数详解》将拆解 /data/zentao/config/my.php 的生成机制与全部配置段(数据库/会话/附件/邮件),以及外部数据库模式的切换方法;运维操作(命令行备份、计划任务、API 集成)集中在《ZenTao 22.6 常用 CLI 命令与运维实战》。

参考资料