Chartbrew Docker Compose 部署指南

Ubuntu 26.04 服务器上以 Docker Compose 实测部署 Chartbrew 6.0.0 全栈(应用+MySQL 8.4+Redis 7 依赖链),含首启迁移取证、持久化验证、等价 docker run 与四条排障实录。

ChartbrewDocker ComposeMySQLRedis数据可视化自托管2026/11/03

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 镜像,Digest sha256: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.0Docker 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 的元数据库方言二选一——

方言版本要求说明
MySQL5+(本实测 8.4)官方镜像默认方言,CB_DB_DIALECT=mysql
PostgreSQL12.5+把 db 服务换成 postgres 镜像并改 CB_DB_* 四件套即可,迁移脚本自动适配

本方案采用 MySQL 8.4:与官方 compose 示例一致,健康检查用 mysqladmin ping 最省事。

三、前置条件

  1. 服务器已安装 Docker 29.x 与 Compose v2+ 插件(docker compose version 可回显即可);
  2. 宿主 8129、4019 端口空闲(ss -ltn | grep -E '8129|4019' 无输出);
  3. 磁盘余量 ≥ 10 GB(应用镜像 4.38 GB + MySQL 镜像 1.12 GB + 数据卷增长);
  4. 准备部署目录:
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_DIALECTmysql元数据库方言(mysql/postgres)
CB_DB_HOST/PORT/NAME/USERNAME/PASSWORDlocalhost/3306/chartbrew/…元数据库连接,compose 内主机名写服务名 db
CB_REDIS_HOST/PORT/PASSWORDlocalhost/6379/空队列与缓存,compose 内写 redis
CB_ENCRYPTION_KEY空→自动生成AES-256 密钥,加密库内数据源凭证
CB_API_HOST / CB_API_PORTlocalhost / 4019后端监听地址/端口;容器内必须 0.0.0.0 才能被端口映射穿透
VITE_APP_CLIENT_HOST / VITE_APP_API_HOSThttp://localhost:4018/4019前端烘焙地址,改动需重建 UI
VITE_APP_CLIENT_PORT4018前端静态服务端口
CB_BULLMQ_USERNAME/PASSWORDchartbrew/随机生成/apps/queues 面板 Basic Auth
CB_MCP_PUBLIC_URL取 VITE_APP_API_HOSTMCP 端点公网 URL(HTTPS 校验规避)

七、配置参数说明

Chartbrew 的配置加载优先级(v6.0.0 源码实测结论):

  1. 真实进程环境变量最优先——server/index.js 用 dotenv.config() 加载 .env,dotenv 的语义是「不覆盖已存在的环境变量」,因此 compose 的 environment: 段会压过 env_file/镜像内置 .env 的同名值;本方案把「栈内主机名类」参数放 environment:、把「密钥与对外地址类」放 env_file,两层互不打架。
  2. .env 文件其次——容器内位于 /code/.env(应用根目录),启动时由 setUpEncryptionKeys 先补写空密钥再被 dotenv 读取。
  3. 代码默认值兜底——如 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;API POST /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。

十二、参考链接

Chartbrew Docker Compose 部署指南 - Quzuo