Chartbrew Docker Compose 部署指南
Ubuntu 26.04 服务器上以 Docker Compose 实测部署 Chartbrew 6.0.0 全栈(应用+MySQL 8.4+Redis 7 依赖链),含首启迁移取证、持久化验证、等价 docker run 与四条排障实录。
Chartbrew Docker Compose 部署指南
目标:在一台 Ubuntu 26.04.1 LTS 服务器上,用 Docker Compose 从零部署 Chartbrew 6.0.0 全栈(应用 + MySQL 8.4 元数据库 + Redis 7 队列缓存的依赖链),完成首启迁移、账号初始化与功能验证。全部命令与回显均为 2026-11-03 实测。产品认知与选型见 《Chartbrew 介绍与选型指南》,参数逐项讲解见 《Chartbrew 6.0.0 配置参数详解》。
一、环境信息
- 操作系统:Ubuntu 26.04.1 LTS(x86_64),服务器地址 192.168.1.200
- Docker:29.9.2(build a82e528);Docker Compose:v5.6.1
- 应用镜像:
razvanilin/chartbrew:6.0.0(Chartbrew 官方 Docker Hub 镜像,Digestsha256:549842ec1f71…,体积 4.38 GB,内含 Node v22.23.4 / npm 10.9.9 与 Chromium 渲染器) - 依赖链:MySQL 8.4(
mysql:8.4,1.12 GB,服务端 8.4.13)→ Redis 7(redis:7-alpine,57.8 MB,redis_version 7.4.12)→ Chartbrew 应用 - 端口规划:
8129前端(Web UI)、4019后端 API;MySQL 3306 / Redis 6379 仅栈内互通、不对宿主发布 - 数据目录:
/opt/chartbrew-stack/data/{mysql,redis,uploads}(bind mount 持久化) - 网络:自定义 bridge
chartbrew-v6.0.0-net(subnet 10.89.57.0/24) - 演示账号:
admin@quzuo.net/q1w2e3r4(管理员)、ops@quzuo.net/q1w2e3r4(普通成员)
⚠️ 网络说明:本机 Docker 守护进程已配置代理,
docker pull直连 Docker Hub 正常;若你的环境拉取官方源超时,先换国内镜像加速器,仍不通再为守护进程配置代理(不要改 compose 文件本身)。
二、镜像选型说明
| 镜像 | 来源 | 结论 |
|---|---|---|
razvanilin/chartbrew:6.0.0 | Docker Hub,Chartbrew 官方发布镜像(共 42 个 tag,latest=6.0.0) | 本指南采用。tag 与应用版本一一对应,锁 6.0.0 可复现 |
chartbrew/chartbrew | — | 不存在(Docker Hub API 返回 404,2026-11-03 实查)。不少二手资料误写此名,拉取会失败 |
源码自建(仓库根 docker-compose.yml 的 build: .) | GitHub 仓库 Dockerfile(node:22.23.4-slim 起) | 适合改代码的二开场景;构建需 npm 全量依赖与 UI 编译,耗时远大于拉官方镜像 |
💡 技术拓展:Chartbrew 的元数据库方言二选一——
方言 版本要求 说明 MySQL 5+(本实测 8.4) 官方镜像默认方言, CB_DB_DIALECT=mysqlPostgreSQL 12.5+ 把 db 服务换成 postgres 镜像并改 CB_DB_*四件套即可,迁移脚本自动适配本方案采用 MySQL 8.4:与官方 compose 示例一致,健康检查用
mysqladmin ping最省事。
三、前置条件
- 服务器已安装 Docker 29.x 与 Compose v2+ 插件(
docker compose version可回显即可); - 宿主 8129、4019 端口空闲(
ss -ltn | grep -E '8129|4019'无输出); - 磁盘余量 ≥ 10 GB(应用镜像 4.38 GB + MySQL 镜像 1.12 GB + 数据卷增长);
- 准备部署目录:
mkdir -p /opt/chartbrew-stack/data/{mysql,redis,uploads}
cd /opt/chartbrew-stack
四、部署步骤
4.1 编写 .env(应用配置 + compose 变量共享)
/opt/chartbrew-stack/.env,compose 的变量插值与应用的 env_file 共用这一个文件:
# 镜像选择
CHARTBREW_IMAGE=razvanilin/chartbrew:6.0.0
# 元数据库(compose 的 db 服务与应用共用)
CB_DB_NAME=chartbrew
CB_DB_USERNAME=chartbrew
CB_DB_PASSWORD=q1w2e3r4
CB_DB_ROOT_PASSWORD=q1w2e3r4
# 加密密钥:32 字节十六进制。生成:node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
CB_ENCRYPTION_KEY=ad56af461d70c64832d1ed6918383b971b9d00f561e15feadcef0bfaa8df8781
# 旧版遗留密钥(新装可留空,此处保留演示值)
CB_SECRET=9d835f8826d949e7830dc02bcbc1c311
# BullMQ 队列面板 /apps/queues 的 Basic Auth 账号
CB_BULLMQ_USERNAME=chartbrew
CB_BULLMQ_PASSWORD=q1w2e3r4
# 对外访问地址(前端构建期烘焙进 dist,改后必须重建 UI,见「常见问题」)
VITE_APP_CLIENT_HOST=http://192.168.1.200:8129
VITE_APP_API_HOST=http://192.168.1.200:4019
# MCP 集成的公网 URL(HTTPS 校验的规避项,见「常见问题」4019 崩溃实录)
CB_MCP_PUBLIC_URL=https://192.168.1.200:4019/mcp
密钥值用生成命令实跑得到:
$ node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
ad56af461d70c64832d1ed6918383b971b9d00f561e15feadcef0bfaa8df8781
4.2 编写 compose.yaml
/opt/chartbrew-stack/compose.yaml(显式 name: 固定项目名,避免多栈互扰):
name: chartbrew-v6.0.0-stack
services:
db:
image: mysql:8.4
container_name: chartbrew-6.0.0-db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${CB_DB_ROOT_PASSWORD}
MYSQL_DATABASE: ${CB_DB_NAME}
MYSQL_USER: ${CB_DB_USERNAME}
MYSQL_PASSWORD: ${CB_DB_PASSWORD}
MYSQL_DEFAULT_AUTH: caching_sha2_password
volumes:
- ./data/mysql:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
networks:
- chartbrew-net
redis:
image: redis:7-alpine
container_name: chartbrew-6.0.0-redis
restart: unless-stopped
command: ["redis-server", "--appendonly", "yes"]
volumes:
- ./data/redis:/data
networks:
- chartbrew-net
chartbrew:
image: ${CHARTBREW_IMAGE}
container_name: chartbrew-6.0.0-server
restart: unless-stopped
env_file: .env
environment:
CB_API_HOST: 0.0.0.0
CB_API_PORT: 4019
CB_DB_HOST: db
CB_DB_PORT: 3306
CB_DB_DIALECT: mysql
CB_REDIS_HOST: redis
CB_REDIS_PORT: 6379
CB_REDIS_PASSWORD: ""
VITE_APP_CLIENT_PORT: 4018
ports:
- "192.168.1.200:8129:4018" # Frontend
- "192.168.1.200:4019:4019" # API
volumes:
- ./data/uploads:/code/server/uploads
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
networks:
- chartbrew-net
networks:
chartbrew-net:
name: chartbrew-v6.0.0-net
driver: bridge
ipam:
config:
- subnet: 10.89.57.0/24
依赖链设计说明:db 用 mysqladmin ping 健康检查、chartbrew 以 condition: service_healthy 等库就绪才启动——Chartbrew 启动时会自动跑 Sequelize 迁移建表,抢跑会因连不上库而失败;redis 无就绪语义要求,service_started 即可。
4.3 启动并观察首启日志
$ docker compose up -d
Container chartbrew-6.0.0-server Creating
Container chartbrew-6.0.0-redis Creating
Container chartbrew-6.0.0-db Creating
...
Container chartbrew-6.0.0-db Healthy
Container chartbrew-6.0.0-server Starting
Container chartbrew-6.0.0-server Started
首启日志(docker compose logs chartbrew,输出节选(实测首尾)):
chartbrew-6.0.0-server | The UI is rebuilding. Please wait...
chartbrew-6.0.0-server | Set up CB_ENCRYPTION_KEY
chartbrew-6.0.0-server | Set up CB_BULLMQ_USERNAME
chartbrew-6.0.0-server | Set up CB_BULLMQ_PASSWORD
chartbrew-6.0.0-server | vite v8.3.5 building client environment for production...
chartbrew-6.0.0-server | [runtime-cache] using Redis via CB_REDIS settings
chartbrew-6.0.0-server | ✓ 6185 modules transformed.
chartbrew-6.0.0-server | Socket.IO Redis Sub Client ready
chartbrew-6.0.0-server | Socket.IO Redis Pub Client ready
chartbrew-6.0.0-server | ✓ Socket.IO Redis adapter enabled for cross-process communication
chartbrew-6.0.0-server | Running server on port 4019
...
chartbrew-6.0.0-server | INFO Accepting connections at http://localhost:4018
解读:Set up CB_* 是应用启动时对 .env 中留空密钥的自动补写机制(详见配置篇);Running server on port 4019 表示后端完成迁移并监听;最后一行 serve 开始托管前端。首启约需 1~2 分钟(后台重建 UI 不影响后端就绪)。
4.4 验证部署
$ docker compose ps
NAME IMAGE COMMAND SERVICE STATUS PORTS
chartbrew-6.0.0-db mysql:8.4 "docker-entrypoint.s…" db Up 2 minutes (healthy) 3306/tcp, 33060/tcp
chartbrew-6.0.0-redis redis:7-alpine "docker-entrypoint.s…" redis Up 2 minutes 6379/tcp
chartbrew-6.0.0-server razvanilin/chartbrew:6.0.0 "./entrypoint.sh" chartbrew Up About a minute 192.168.1.200:4019->4019/tcp, 192.168.1.200:8129->4018/tcp
前端可达性:
$ curl -s -o /dev/null -w "GET / -> %{http_code}\n" http://192.168.1.200:8129/
GET / -> 200
迁移建表结果(63 张表即 v6.0.0 完整 schema):
$ docker compose exec -T db mysql -uchartbrew -pq1w2e3r4 -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='chartbrew';"
63
4.5 初始化账号与登录验证
Chartbrew 无预置管理员:开放注册期间先到先得,第一个注册的账号即超级管理员。浏览器打开 http://192.168.1.200:8129 注册 admin@quzuo.net / q1w2e3r4,或用 API 注册:
$ curl -s -X POST http://192.168.1.200:4019/user -H 'Content-Type: application/json' \
-d '{"email":"ops@quzuo.net","password":"q1w2e3r4","name":"Ops User"}'
{"id":2,"email":"ops@quzuo.net","name":"Ops User","icon":"OP","admin":false,"active":true,"tutorials":{},"createdAt":"2026-11-02T22:53:18.000Z","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}
⚠️ 注册请求体必须带
name字段:v6.0.0 的UserRoute.js直接执行req.body.name.substring(0, 2)生成头像缩写,缺name会抛TypeError: Cannot read properties of undefined (reading 'substring')返回 HTTP 500(实测,见「常见问题」)。
登录验证:
$ curl -s -X POST http://192.168.1.200:4019/user/login -H 'Content-Type: application/json' \
-d '{"email":"admin@quzuo.net","password":"q1w2e3r4"}'
{"id":1,"email":"admin@quzuo.net","name":"admin","icon":"AD","admin":true,"active":true,"tutorials":{},"createdAt":"2026-11-02T22:38:44.000Z","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiZW1haWwiOiJhZG1pbkBxdXp1by5uZXQiLCJzZXNzaW9uVmVyc2lvbiI6MCwiaWF0IjoxNzkzNjYxMDg3LCJleHAiOjE3OTYyNTMwODd9.txXbqjIx1hVK1_xc84mAC9K6B9CPrRTkinkgddQ29ug"}
队列面板 Basic Auth 验证:
$ curl -s -o /dev/null -w "no-auth: %{http_code}\n" http://192.168.1.200:4019/apps/queues
no-auth: 401
$ curl -s -o /dev/null -w "with-auth: %{http_code}\n" -u chartbrew:q1w2e3r4 http://192.168.1.200:4019/apps/queues
with-auth: 200
五、数据持久化说明
| 宿主目录 | 栈内路径 | 内容 |
|---|---|---|
./data/mysql | /var/lib/mysql | 全部元数据(63 张表:用户/连接/数据集/图表/仪表盘…),核心资产 |
./data/redis | /data | 队列任务与运行时缓存(AOF 已开:appendonly yes),丢了可重建 |
./data/uploads | /code/server/uploads | 用户上传的静态资源(logo 等) |
./.env | (env_file 注入) | 配置与 CB_ENCRYPTION_KEY——密钥丢失则库内数据源密码不可解,必须随备份保存 |
持久化实测:整套栈 docker compose down 后重新 up -d,管理员登录接口仍返回 200 与用户记录(id:1),data/mysql 目录内容跨重建保留,证明 bind mount 生效。
六、环境变量表
应用侧关键变量完整清单(/code/.env-template 共 90 余行,此处列部署必配项;逐项语义与实测行为见 《Chartbrew 6.0.0 配置参数详解》):
| 变量 | 默认/示例 | 作用 |
|---|---|---|
CB_DB_DIALECT | mysql | 元数据库方言(mysql/postgres) |
CB_DB_HOST/PORT/NAME/USERNAME/PASSWORD | localhost/3306/chartbrew/… | 元数据库连接,compose 内主机名写服务名 db |
CB_REDIS_HOST/PORT/PASSWORD | localhost/6379/空 | 队列与缓存,compose 内写 redis |
CB_ENCRYPTION_KEY | 空→自动生成 | AES-256 密钥,加密库内数据源凭证 |
CB_API_HOST / CB_API_PORT | localhost / 4019 | 后端监听地址/端口;容器内必须 0.0.0.0 才能被端口映射穿透 |
VITE_APP_CLIENT_HOST / VITE_APP_API_HOST | http://localhost:4018/4019 | 前端烘焙地址,改动需重建 UI |
VITE_APP_CLIENT_PORT | 4018 | 前端静态服务端口 |
CB_BULLMQ_USERNAME/PASSWORD | chartbrew/随机生成 | /apps/queues 面板 Basic Auth |
CB_MCP_PUBLIC_URL | 取 VITE_APP_API_HOST | MCP 端点公网 URL(HTTPS 校验规避) |
七、配置参数说明
Chartbrew 的配置加载优先级(v6.0.0 源码实测结论):
- 真实进程环境变量最优先——
server/index.js用dotenv.config()加载.env,dotenv 的语义是「不覆盖已存在的环境变量」,因此 compose 的environment:段会压过env_file/镜像内置.env的同名值;本方案把「栈内主机名类」参数放environment:、把「密钥与对外地址类」放env_file,两层互不打架。 .env文件其次——容器内位于/code/.env(应用根目录),启动时由setUpEncryptionKeys先补写空密钥再被 dotenv 读取。- 代码默认值兜底——如 API 端口兜底 4019(
process.env.PORT || app.settings.port || 4019)。
八、配置文件挂载
本方案不挂载应用配置文件:Chartbrew 的全部配置走环境变量/.env,无独立 conf 文件。需要持久化的是三个数据目录(见第五节)。若希望以「文件」形态管理 .env,把宿主 /opt/chartbrew-stack/.env 经 env_file: .env 注入即为唯一入口;compose 变量插值(${CB_DB_NAME} 等)与应用读取共用同一文件,改一处即可。
九、等价 docker run
不用 compose、手工三行起同栈(复用已存在的网络与库):
$ docker run -d --name chartbrew-run --network chartbrew-v6.0.0-net \
--env-file .env \
-e CB_API_HOST=0.0.0.0 -e CB_API_PORT=4019 \
-e CB_DB_HOST=db -e CB_DB_PORT=3306 -e CB_DB_DIALECT=mysql \
-e CB_REDIS_HOST=redis -e CB_REDIS_PORT=6379 -e CB_REDIS_PASSWORD="" \
-e VITE_APP_CLIENT_PORT=4018 \
-p 192.168.1.200:8130:4018 -p 192.168.1.200:4028:4019 \
razvanilin/chartbrew:6.0.0
f243dda82dc72576a3f07cf218a5cf5f901b45189deef881046833a2904a1a83
$ curl -s -o /dev/null -w "run-frontend: %{http_code}\n" http://192.168.1.200:8130/
run-frontend: 200
$ curl -s -o /dev/null -w "run-api-queues: %{http_code}\n" http://192.168.1.200:4028/apps/queues
run-api-queues: 401
$ docker rm -f chartbrew-run
chartbrew-run
要点:--env-file 注入应用配置,-e 覆盖栈内主机名;同网络内以服务名 db/redis 直连依赖;前端临时映射到 8130 验证后已删除该实例。
十、常用运维命令
docker compose ps # 栈内三服务状态
docker compose logs -f chartbrew # 跟踪应用日志
docker compose restart chartbrew # 仅重启应用(不动库)
docker compose up -d --force-recreate chartbrew # 改 .env 后重建生效
docker compose stop / start # 停机/恢复(保留数据与容器)
docker compose down # 拆栈(数据在 ./data,安全)
docker compose exec -T db mysql -uchartbrew -pq1w2e3r4 chartbrew # 进元数据库
docker compose exec -T redis redis-cli # 进 Redis
升级(改 tag 即回滚同理):
sed -i 's#CHARTBREW_IMAGE=razvanilin/chartbrew:6.0.0#CHARTBREW_IMAGE=razvanilin/chartbrew:latest#' .env
docker compose up -d chartbrew
⚠️ 跨大版本升级(如 5.x→6.0.0)先读官方迁移说明:v6 移除了带
_DEV后缀的旧变量名并强制要求 Redis,直接换 tag 可能起不来;升级前务必mysqldump备份(命令见运维实操篇)。
十一、常见问题
11.1 启动即崩:Error: MCP requires a public HTTPS URL ending in /mcp
- 现象:
NODE_ENV=production下容器反复重启,日志见上述报错。 - 定位:
server/modules/mcp/oauth.js:43——生产模式下校验由VITE_APP_API_HOST推导的 MCP 端点必须是 https 且以/mcp结尾;本部署 API 走 http 明文,校验不过。 - 解决:
.env显式加CB_MCP_PUBLIC_URL=https://192.168.1.200:4019/mcp(oauth.js:28优先读该变量),docker compose up -d --force-recreate chartbrew后恢复。官方文档 integrations/mcp 认可此变量。
11.2 端口冲突:failed to bind host port 0.0.0.0:8129/tcp: address already in use
- 现象:
up -d报端口占用——宿主上已有进程监听 127.0.0.1:8129(本机为 IDE 服务)。 - 解决:把映射改为绑定具体网卡 IP
"192.168.1.200:8129:4018",与 127.0.0.1 上的监听不冲突;或换宿主端口(同步改VITE_APP_CLIENT_HOST并重建 UI)。
11.3 健康检查 404:Cannot GET /api/health
- 现象:按常规思路探
/api/health返回 404。 - 结论:Chartbrew v6 没有 health 端点。存活判定用两条实测:前端
GET http://<host>:8129/→ 200;APIPOST /user/login空体 → 400(服务在、路由在)。监控脚本可据此写拨测。
11.4 注册返回 500:Cannot read properties of undefined (reading 'substring')
- 现象:
POST /user报 500,日志栈指向UserRoute.js:86。 - 原因:请求体缺
name字段(v6.0.0 用req.body.name.substring(0, 2)生成头像缩写,无空值保护)。 - 解决:注册体固定带
name,如 4.5 节示例。前端页面注册自带该字段,仅 API 注册时易踩。
11.5 日志一直刷 The UI is rebuilding. Please wait...
正常行为:镜像启动时会在后台按当前 VITE_APP_* 重建前端(约 1~2 分钟),完成打印 UI built successfully!。期间 API 已可用,仅前端可能短暂 404/旧版;若改了 VITE_APP_CLIENT_HOST 发现页面仍跳旧地址,等重建完成或手动 docker compose restart chartbrew。
十二、参考链接
- 官方 Docker 部署文档:https://docs.chartbrew.com/deployment/run-on-docker
- 官方仓库(compose/Dockerfile 参考):https://github.com/chartbrew/chartbrew
- Docker Hub 镜像:https://hub.docker.com/r/razvanilin/chartbrew
- 平台内相关篇:《Chartbrew 介绍与选型指南》、《Chartbrew 6.0.0 配置参数详解》、《Chartbrew 6.0.0 常用 CLI 命令与运维实操》