博客
教程

用 Docker 部署 Simmetric Chat:完整指南

Simmetric Chat 的完整部署地图--六服务 Compose 栈、面向气隙主机的单容器 all-in-one 镜像,以及一键 Coolify 路径。

Simmetric Team7 分钟阅读10/03/2026

一个产品,三种部署形态

Simmetric Chat 是一个多服务平台--API 服务器、文档处理 collector、可嵌入 widget 服务、前端和 PostgreSQL 数据库--所以部署这件事的核心是为你的环境选择合适的打包方式,而不是手工接线各个服务。生产环境有三条路径,所有工件都在仓库的 docker/ 目录中:

  • 多容器 Compose--六个服务,每个都在自己的容器中并带健康检查。推荐的生产布局,具有服务级隔离和独立重启。
  • 单容器 all-in-one--一个镜像运行一切:Postgres、服务器、collector 和 widget,由 supervisord 监管。面向气隙主机和演示机,在这些场景中让多个镜像穿越边界才是昂贵的一步。
  • Coolify--一条自托管 PaaS 路径,由 Traefik 终止 TLS,平台会根据一个按 Coolify 约定定制的 Compose 文件为你克隆并构建。

所有镜像都是基于 Node 24 的多阶段构建,每个服务都暴露健康检查,整个栈的启动顺序由它们决定。本文走遍每条路径、配置模型,以及在生产中会咬人的细节。

六服务 Compose 栈

生产 Compose 文件定义了六个活动服务:

  • frontend--由 Node builder 构建为 React 19 应用,再由 nginx 提供服务;nginx 用在镜像构建时生成的自签名证书终止 TLS,提供 SPA,并将 /api 和 /widget 代理到后端服务。
  • server--Express 5 API:认证、RBAC、聊天编排和 ReAct agent。你的用户与之对话的服务。
  • collector--文档管线:上传文件的解析、切分和 embedding。基于 slim Node(而非 Alpine)构建,因为其原生依赖(LanceDB、本地 embedding transformers、canvas 绑定)都需要 glibc。
  • widget--面向外部网站的可嵌入聊天 widget 服务。
  • postgres--包含 pgvector 扩展的 PostgreSQL 16,因此默认向量 provider 不需要单独的向量数据库服务。
  • redis--可选的扩展缓存,启用 append-only 持久化和 LRU 淘汰。每个 Redis 消费者都会优雅降级到内存中,所以这个服务可有可无,不会有问题。

有两个服务在标准文件中被有意禁用而非省略:ollama 和 qdrant。它们的配置块以注释形式出现,启用它们只需取消一行注释加一个变量--本地模型主机用 OLLAMA_BASE_URL=http://ollama:11434,或者如果你想要远程向量存储来替代默认的基于文件的 LanceDB,则用 VECTOR_DB_PROVIDER=qdrant。被注释掉的默认值让最小安装真正保持最小。

启动顺序由健康检查决定:基于 wget 的探针访问服务器和 collector 的 GET /api/health、widget 的 /health、Postgres 的 pg_isready 和 Redis 的 redis-cli ping,前端会等待服务器声明健康后才启动。

启动整个栈只需一条命令:

bash
docker compose -f docker/docker-compose.yml up --build -d

单容器 all-in-one

第二条路径把整个平台压缩成一个镜像。单个 Dockerfile 编译 PostgreSQL 16,pgvector 扩展在镜像构建时从源码编译,supervisord 在容器内同时运行数据库、服务器、collector 和 widget。暴露 3000、3210 和 3211 三个端口,健康检查查询 localhost 上服务器的 /api/health。

bash
docker build -f docker/Dockerfile -t simmetric-chat-all-in-one .
docker run -d -p 3000:3000 -p 3210:3210 -p 3211:3211 simmetric-chat-all-in-one

all-in-one 镜像就是气隙的故事:只有一个工件需要穿越边界,安装时无需从 registry 拉取,从数据库到聊天 UI 的一切都靠一条 docker run 启动。pgvector 源码之所以在构建期间离线编译安装,正是为了让生成的镜像在安装阶段没有任何网络依赖。它的环境默认值是刻意设计的脚手架占位符--操作者通过 .env 或 -e 覆盖真实密钥,镜像从不假装开箱即生产安全。

有一条边界值得了解:enterprise 插件永远不会被烘焙进任何镜像。这是刻意的--它让社区的 AGPL 镜像不含专有代码,并允许气隙客户以单独、可验证的 tarball 形式获取插件,在运行时通过 bind 挂载。

Coolify 路径

Coolify 把技术栈变成 PaaS 部署:它克隆仓库、构建镜像、接通内部网络,并在前面放上 Traefik 做 TLS 终止。一个专用的 Compose 文件自动化了标准 Compose 留给操作者的部分--镜像构建、内部网络、卷、健康检查和 enterprise 插件挂载。

它还通过 :? 语法在部署时强制要求必需的密钥:JWT_SECRET、COLLECTOR_SECRET、WIDGET_API_KEY、POSTGRES_PASSWORD、APP_URL 和 ALLOWED_ORIGINS。缺少任何一个,Coolify 都会拒绝部署,这让一整类配置到一半的部署在启动任何东西之前就变成可见的错误。

与标准栈的差异来自 Traefik 的存在:应用服务不向主机发布端口(把域名分配给 frontend 服务,端口 80),nginx 配置仅用 HTTP,因为 Traefik 终止 TLS,Docker socket 默认不挂载--它只用于 Ollama Cloud 的登录流程。一份分步 runbook 覆盖了手动步骤:rsync enterprise 目录树、填写密钥、插入许可证密钥。

配置:一个 .env,由 Compose 挂载

容器通过 Compose 的 env_file 接收配置,它指向仓库根目录的 .env--平台在各处使用的同一个运行时配置文件。如果文件缺失,技术栈也能仅靠 compose 层默认值启动:足以做第一次冒烟测试,但不足以用于生产。

有两个密钥是严格必需的,缺少它们服务器会退出:

env
# 根目录 .env - 生产环境必需
JWT_SECRET=...          # openssl rand -hex 32
COLLECTOR_SECRET=...    # openssl rand -hex 32

ENCRYPTION_KEY 和 API_KEY_HMAC_SECRET 完全不需要预先配置:在生产启动时,服务器的 entrypoint 会生成一次、持久化到 server-storage 卷、每次重启时恢复,并在操作者提供了值的时候优先采用它。Provisioning 从不在已有密钥材料上重新生成,并在任何数据库步骤之前大声失败,因此半写入的卷绝不会静默变成另一把密钥。

有一个值得点名的 Compose 陷阱,因为它会静默降级部署:不要给生产服务添加 LICENSE_KEY=${LICENSE_KEY:-} 这类透传条目。Shell 插值会把未设置的变量解析为空字符串,而显式置空的 environment 条目会覆盖通过 env_file 传入的值--于是空插值的 LICENSE_KEY 会把 Enterprise 部署变成 Community,空插值的 ENCRYPTION_KEY 会覆盖 entrypoint 的自动 provisioning。把这些密钥留给 env_file 就好。

生产中真正重要的细节

  • collector 需要 glibc。 collector 运行在 slim(Debian)而非 Alpine 基线上,因为 LanceDB、本地 embedding transformers 和 canvas 都需要 glibc。不要「简化」成 Alpine--构建会失败。
  • 数据库 URL 有三种形态。 在 Compose 网络内部,服务器与 postgres:5432 通信(在 compose 的 environment 中硬编码);根 .env 的默认值对主机侧服务器指向 host.docker.internal;all-in-one 镜像用 localhost,因为 Postgres 在同一容器中运行。
  • 迁移只做加法。 服务器 entrypoint 每次启动都运行 prisma migrate deploy,所以向前滚动镜像是策略上安全的;回滚意味着重新部署一个已知良好的镜像,而代码回滚不会撤销已应用的迁移。
  • 扩展要等待 provisioning。 密钥自动 provisioning 必须先完成一次,服务器才能扩展到多副本--首次启动时并发的副本可能生成不一致的密钥。单实例是文档记录的默认值;多实例扩展有专门的指南。
  • 健康端点是外部的。 /api/health 返回带逐依赖检查的状态对象,适合外部 uptime 探针和负载均衡器检查;结构化日志输出到 stdout,用 docker compose logs -f <service> 查看。

发布的镜像推送到 GHCR(server、frontend、collector、widget 和 all-in-one,打 latest 加版本号标签),所以部署可以固定版本而不是本地构建--不过从仓库执行 docker compose up --build 仍然是完全自托管的路径。

从克隆到运行中的平台

最短的路径,从头到尾:

  1. 克隆仓库并把 .env.example 复制为 .env。
  2. 设置 JWT_SECRET 和 COLLECTOR_SECRET(entrypoint 会自行 provision 加密密钥)。
  3. docker compose -f docker/docker-compose.yml up --build -d。
  4. 跟踪 docker compose logs -f server 直到迁移和 seeding 完成,然后打开前端并登录。

气隙主机请换成 all-in-one 镜像和 tarball 传输流程。Coolify 则导入仓库并填写必需的密钥--平台会完成剩下的部署。

在文档中阅读完整指南 →

相关文章