Formbricks Docker Compose 部署实战

Formbricks 6.1.2 官方 Docker Compose 全栈部署实测:12 项必填密钥体系、迁移一次性任务机制、初始化向导、健康验证、资源基线与 INVALID_ORIGIN 等真实排障实录。

FormbricksFormbricks自托管问卷调查2026/10/28

Formbricks Docker Compose 部署实战

目标:在一台 Ubuntu 22.04 服务器上,用官方 Docker Compose 编排从零部署 Formbricks v6.1.2(含 PostgreSQL、Redis/Valkey、Hub、Cube、SpiceDB 全套基线服务),完成密钥配置、首次启动、初始化向导与健康验证,并沉淀迁移机制、密钥体系与常见故障的实测排障过程。本文全部命令与回显均在 6.1.2 环境实测;产品背景与选型见 《Formbricks 介绍与选型指南》。

一、环境信息

本次实测环境与部署基准:

项目值
操作系统Ubuntu 22.04(内核 5.15+)
Docker / ComposeDocker 24+,Compose v2 插件(docker compose 子命令)
Formbricks 版本6.1.2(官方 ghcr.io/formbricks/formbricks:latest 当前指向版本,容器内 package.json 实测 6.1.2)
服务监听Web 应用端口 8129(服务器公网/内网 IP 192.168.146.128)
编排项目目录/opt/formbricks/docker(compose 文件与 .env 所在目录)
演示管理账号admin@formbricks.local / q1w2e3r4

二、编排与镜像选型说明

2.1 v6 基线栈的服务组成

Formbricks 自 v5 起从"单体应用 + 数据库"演进为多服务架构,v6 官方编排文件(仓库 docker/docker-compose.yml)包含以下常驻服务与一次性任务:

服务官方镜像端口(编排网络内)职责
formbricksghcr.io/formbricks/formbricks:latest3000(映射宿主 8129)主应用:管理后台、问卷投放、响应收集 API
postgrespgvector/pgvector:pg185432(不发布宿主)业务数据库(含 pgvector 扩展)
redisvalkey/valkey(官方 digest 固定)6379(不发布宿主)会话缓存、限流、审计日志、任务队列
hubghcr.io/formbricks/hub:latest8080(不发布宿主)实时通道、Webhook 分发、数据富化
hub-worker同 hub 镜像—异步任务 worker(Webhook/富化实际执行者)
cubeghcr.io/formbricks/cube:1.7.50-14000(不发布宿主)语义层分析 API(洞察图表数据源)
spicedbauthzed/spicedb:v1.52.050051(不发布宿主)集中授权服务(组织/团队权限裁决)

一次性初始化任务(执行完即退出,restart: "no"):formbricks-migrate(主库 Prisma 迁移)、hub-migrate(Hub 库 goose/river 迁移)、authzed-db-bootstrap(创建 SpiceDB 库与账号)、spicedb-migrate(SpiceDB schema 迁移)、authzed-initialize(授权数据初始化)。

编排文件里有两处安全设计值得注意,均来自官方注释原文:

  • 数据库与 Redis 不发布宿主端口:Redis 是 Better Auth 的会话令牌存储,把 6379 发布到宿主会暴露所有活跃会话令牌(且 Docker 的端口规则会绕过 ufw 等宿主防火墙),PostgreSQL 同理;
  • 迁移前置:主应用 depends_on 等待 postgres/redis/cube 健康检查通过,迁移任务通过 service_completed_successfully 串行化在应用启动之前。

2.2 镜像版本策略

官方编排对 Formbricks/Hub/Cube 一律跟踪 latest,仅 SpiceDB 固定 v1.52.0、Redis 用 digest 锁定。实测当前 latest 即 6.1.2:

$ docker inspect formbricks-6.1.2-app --format '{{.Config.Image}}'
ghcr.io/formbricks/formbricks:latest
$ docker exec formbricks-6.1.2-app sh -c 'cat package.json | grep -m1 version'
  "version": "6.1.2",

如需版本锁定(可复现部署),可把 image: 改为具体 tag,或用官方支持的 HUB_IMAGE_REF 环境变量统一 pin Hub 系列镜像(hub/hub-worker/hub-migrate 三个服务共享该变量,官方注释明确"no drift")。本文按官方默认 latest 部署并实测。

三、前置条件

  1. 一台 Ubuntu 22.04 服务器,已安装 Docker 24+ 与 Compose v2 插件;
  2. 服务器与 ghcr.io、docker.io 的网络可达(离线环境需提前导入镜像,见第十章常见问题);
  3. 一个空闲的宿主端口用于 Web 入口(本文用 8129,官方默认 3000,可按需改映射);
  4. openssl(Ubuntu 自带),用于生成全套密钥。

确认 Compose 可用:

$ docker compose version
Docker Compose version v2.29.7

四、部署步骤

4.1 准备编排目录

官方建议把仓库 docker/ 目录整体作为部署目录(内含 docker-compose.yml、Cube 配置、SAML 挂载目录与 .env 模板)。把官方编排落到 /opt/formbricks/docker:

mkdir -p /opt/formbricks && cd /opt/formbricks
git clone --depth 1 https://github.com/formbricks/formbricks.git repo
cp -r repo/docker ./docker && cd ./docker
ls -la

编排目录应包含(节选):

authzed-postgres-bootstrap.sh
docker-compose.yml
saml-connection/
cube/
  cube.js
  schema/FeedbackRecords.js

其中 authzed-postgres-bootstrap.sh 是一次性任务 authzed-db-bootstrap 挂载执行的脚本,cube/ 两个文件是 Cube 服务的语义层模型(均由官方编排以只读方式挂载,必须保留,缺失会导致 Cube 起不来)。

4.2 生成密钥并编写 .env

官方模板要求所有密钥类变量用 openssl rand -hex 32 生成。逐项生成:

$ openssl rand -hex 32
78ef473ef44263a26e20666b8cab9fda85acbd79a7bea3d934dffdd9bb49dd1d
$ openssl rand -hex 32
787ce7a41967b0ef612825af3186d61dbc5936e17c948903b1af3b8552362760

(以上为本次实测环境使用的演示密钥值;生产环境请自行重新生成,切勿照抄。)

按官方 .env 模板编写 /opt/formbricks/docker/.env,本次实测的完整内容如下(密钥为实测演示值):

WEBAPP_URL=http://192.168.146.128:8129
BETTER_AUTH_URL=http://192.168.146.128:8129
POSTGRES_PASSWORD=78ef473ef44263a26e20666b8cab9fda85acbd79a7bea3d934dffdd9bb49dd1d
POSTGRES_PASSWORD_URL_ENCODED=78ef473ef44263a26e20666b8cab9fda85acbd79a7bea3d934dffdd9bb49dd1d
BETTER_AUTH_SECRET=787ce7a41967b0ef612825af3186d61dbc5936e17c948903b1af3b8552362760
ENCRYPTION_KEY=ade992ff6b2035f5e78bc91945df6d41f9880e315402fa7e8a01a6eb56b01db9
CRON_SECRET=0654a5250d324fd65bd23cea96a9ab8f489a5b0cb83d7177050c1d89e375ae86
HUB_API_KEY=1244ccd713f527b8d88b8a7ba87d1a384baf0d6b28126abbf4a680d68f598744
CUBEJS_API_SECRET=c404f29931acf180f8f3498e646a6a600d5a9ceadb26d03cf8c7cf76462de43a
CUBEJS_JWT_ISSUER=formbricks-web
CUBEJS_JWT_AUDIENCE=formbricks-cube
AUTHZED_TOKEN=6b9d376dd17277e082175c5c163aa64ab4050156ff31c421642eeffb1da74a1d
AUTHZED_DATABASE_PASSWORD=f6a4f158e77e0521a0ca196e4dcd85590a96d5b9b3898d8cb7305aece2d50c7a

逐项说明(全部为官方标注 REQUIRED 的必填项):

变量作用说明
WEBAPP_URL对外访问地址填用户浏览器实际访问的 http://<IP或域名>:<端口>,链接问卷、邮件里的 URL 都由它生成
BETTER_AUTH_URL认证基准地址v5 起认证体系为 Better Auth,必须与 WEBAPP_URL 一致,否则浏览器登录会被拒(实测见 10.4)
POSTGRES_PASSWORD数据库口令首次初始化写入数据卷后不可再改(改了连不上库)
POSTGRES_PASSWORD_URL_ENCODEDURL 编码版口令拼接 DATABASE_URL 用;hex 字符串无需转义时与原值相同
BETTER_AUTH_SECRET会话/令牌签名密钥更换后所有已登录会话失效,需重新登录
ENCRYPTION_KEY数据加密密钥用于 2FA、单次链接加密、审计哈希;更换会导致已加密数据不可解
CRON_SECRET定时任务 API 口令内部 cron 调用口令
HUB_API_KEYHub 内部 API 密钥主应用与 Hub 之间调用鉴权
CUBEJS_API_SECRETCube 访问密钥分析 API 鉴权
CUBEJS_JWT_ISSUER / CUBEJS_JWT_AUDIENCECube JWT 签发方/受众官方固定值 formbricks-web / formbricks-cube
AUTHZED_TOKENSpiceDB gRPC 预共享密钥主应用连接授权服务用
AUTHZED_DATABASE_PASSWORDSpiceDB 数据库账号口令由 bootstrap 脚本创建的库账号

⚠️ 注意:编写完成后收紧权限,避免同机其他用户读取密钥:

$ chmod 600 .env

4.3 校验编排文件

启动前用 docker compose config 做一次语法与变量校验(无输出即通过):

$ docker compose config --quiet
compose config OK exit=0

若密钥变量缺失,此处会直接报 Set POSTGRES_PASSWORD in .env 类错误,比启动后排查快得多。

4.4 拉起全栈

docker compose up -d

首次启动时,编排引擎会按依赖顺序先跑 5 个一次性迁移任务,再启动 7 个常驻服务。全栈就绪后 docker compose ps 实测:

$ docker compose ps --format "table {{.Name}}\t{{.Service}}\t{{.Status}}"
NAME                          SERVICE      STATUS
formbricks-6.1.2-app          formbricks   Up 35 seconds
formbricks-6.1.2-cube         cube         Up About an hour (healthy)
formbricks-6.1.2-hub          hub          Up About an hour (healthy)
formbricks-6.1.2-hub-worker   hub-worker   Up About an hour
formbricks-6.1.2-postgres     postgres     Up About an hour (healthy)
formbricks-6.1.2-redis        redis        Up About an hour (healthy)
formbricks-6.1.2-spicedb      spicedb      Up About an hour (healthy)

7 个常驻服务全部 Up,其中 5 个带 (healthy)(hub-worker 官方未定义健康检查,属正常)。只重启单个服务(如改了 .env 后):

$ docker compose up -d formbricks
 Container formbricks-6.1.2-redis Healthy
 Container formbricks-6.1.2-app Starting
 Container formbricks-6.1.2-app Started

4.5 观察迁移与应用启动日志

主应用迁移任务(一次性)日志实测:

$ docker logs formbricks-6.1.2-migrate
Environment variables validated successfully

迁移脚本先做环境变量校验(validate-env.mjs),再执行 Prisma 迁移;本次为重启场景,schema 已就绪所以输出极简。首次部署会在此处打印逐条迁移执行记录。Hub 迁移任务日志(幂等重跑,实测):

$ docker logs formbricks-6.1.2-hub-migrate
2026/10/28 00:25:11 goose: no migrations to run. current version: 27
no migrations to apply

主应用启动日志(实测完整头部):

$ docker logs formbricks-6.1.2-app
Validating environment variables...
Environment variables validated successfully
⏭️ Skipping startup migrations (handled by migration job)
🗃️ Running SAML database setup...
Using timeout (60 seconds) for SAML database setup
✅ Database setup completed
🚀 Starting Next.js server...
✓ Running next.config took 1.1ms

注意第二行的提示:v6 编排把迁移职责完全交给 formbricks-migrate 一次性任务(编排里通过 SKIP_STARTUP_MIGRATION: "true" 显式跳过应用内迁移),应用本体不再做 schema 变更。

4.6 健康验证

官方内置健康检查端点,实测:

$ curl -s http://192.168.146.128:8129/health
{"status":"ok"}

返回 {"status":"ok"} 即应用与依赖链(数据库/缓存/Hub/Cube)连通正常。浏览器打开 http://192.168.146.128:8129 进入初始化向导。

4.7 初始化向导与管理账号

首次访问按向导完成三步:创建管理员账号、创建组织、创建第一个工作空间。本文实测使用演示账号:

  • 邮箱:admin@formbricks.local
  • 密码:q1w2e3r4

向导完成后进入管理后台首页。自托管默认关闭注册开放(signup 仅邀请制),如需开放或对接 SSO 见 《Formbricks 6.1.2 配置参数详解》5.2 节的认证开关一节。

⚠️ 完成后请第一时间在后台 Settings → API Keys 生成一枚管理 API 密钥(形如 fbk_…),后续 API 运维会用到(详见运维篇)。

五、数据持久化说明

官方编排声明的数据卷(docker volume ls 可见,卷名带项目前缀):

卷挂载点内容
<project>_postgrespostgres 数据目录全部业务数据(问卷/响应/联系人/用户/组织)
<project>_redis/dataValkey AOF 持久化(会话缓存与队列,可丢但建议随卷备份)
<project>_qwen-model-cache/data仅 qwen profile(可选 AI 服务)使用,默认栈不创建数据

编排默认项目名 formbricks-6.1.2-stack(编排文件显式 name: 声明),所有卷与网络名都带该前缀。备份的核心是 postgres 数据卷:最直接的方式是定期 pg_dump 逻辑备份(实测与恢复流程见运维篇),快照级备份则需先停写(docker compose stop formbricks)再复制卷。

六、环境变量表(部署相关全量必填项速查)

官方 .env.example 中标注 REQUIRED 的变量即第六章表格所列 12 项,缺一不可。补充三个容易踩坑的"准必填"项:

变量默认建议值与理由
EMAIL_VERIFICATION_DISABLED1(关)无 SMTP 时保持 1;配好邮件后设 0 开启邮箱验证
PASSWORD_RESET_DISABLED1(关)同上,重置密码依赖邮件发送
DANGEROUSLY_ALLOW_WEBHOOK_INTERNAL_URLS未设内网测试 Webhook 目标(如 http://192.168.x.x/hook)时需设 1,生产环境切勿开启

完整参数逐项详解见 《Formbricks 6.1.2 配置参数详解》。

七、配置文件挂载说明

官方编排中三类挂载文件需要理解,改动前先看它们被谁消费:

  1. ./cube/cube.js 与 ./cube/schema/ → Cube 服务(只读):语义层数据模型。Cube 用它把 PostgreSQL 业务表映射为分析指标;正常使用不需要改,升级大版本后若图表报模型错误,从官方仓库同步这两个文件;
  2. `./saml-connection/ → 主应用(读写):SAML 企业版连接配置目录,空目录即可启动(本文实测即空目录),配置 SSO 后由应用写入;
  3. ./authzed-postgres-bootstrap.sh → bootstrap 一次性任务(只读):创建 SpiceDB 专用库账号的脚本,官方文件不要改。

.env 是唯一需要日常编辑的配置文件;改完执行 docker compose up -d 重建受影响服务即可生效。

八、等价部署形态说明

Formbricks 官方只提供 Docker Compose 编排这一种自托管路径,不提供单容器运行形态——这不是文档省略,而是架构事实:主应用启动即校验并依赖 PostgreSQL、Redis、Hub(HUB_API_URL 默认 http://hub:8080)、Cube(CUBEJS_API_URL 默认 http://cube:4000)与 SpiceDB(AUTHZED_ENDPOINT 默认 spicedb:50051)五类外部服务,单容器无法完成初始化。因此本篇不给出"等价 docker run"命令;读者如需把编排拆解成 systemd 单元或接入手写 Compose,按第六章变量表把各服务地址显式化即可(所有 *_URL/*_ENDPOINT 变量官方都支持覆盖)。

九、常用运维命令

以下命令均在部署目录 /opt/formbricks/docker 内执行,全部实测。

启停与状态:

$ docker compose ps                       # 服务状态与健康检查
$ docker compose stop formbricks          # 只停主应用(数据库与缓存保持运行)
$ docker compose start formbricks         # 再拉起
$ docker compose restart hub-worker       # 重启单个服务
$ docker compose down                     # 停止并移除全部服务(数据卷保留)
$ docker compose down -v                  # ⚠️ 连数据卷一起删除(数据全清,慎用)

日志观测:

$ docker compose logs -f formbricks       # 跟踪主应用日志
$ docker logs formbricks-6.1.2-migrate    # 查看迁移任务是否执行成功
$ docker compose logs hub | grep -i error # 过滤 Hub 错误

版本升级(latest 跟踪模式):

$ docker compose pull && docker compose up -d

升级后必须确认迁移任务执行成功(docker logs formbricks-6.1.2-migrate 无报错),再验证 /health 与后台问卷功能。实测环境下 docker stats --no-stream 的资源基线(供容量规划参考):

$ docker stats --no-stream --format "{{.Name}}\t{{.MemUsage}}\t{{.CPUPerc}}"
formbricks-6.1.2-app          665.4MiB / 19.48GiB   0.00%
formbricks-6.1.2-cube         253.1MiB / 19.48GiB   0.00%
formbricks-6.1.2-hub          18.11MiB / 19.48GiB   0.00%
formbricks-6.1.2-hub-worker   8.543MiB / 19.48GiB   0.39%
formbricks-6.1.2-spicedb      120.6MiB / 512MiB     2.05%
formbricks-6.1.2-postgres     197.7MiB / 19.48GiB   0.04%
formbricks-6.1.2-redis        37.09MiB / 19.48GiB   0.20%

空载全栈内存约 1.3 GB(主应用占一半);SpiceDB 官方编排限制了 512MB 内存上限。小团队生产环境建议服务器内存不低于 4 GB。

十、常见问题(实测排障实录)

10.1 启动报 Set POSTGRES_PASSWORD in .env

编排文件用 ${POSTGRES_PASSWORD:?...} 强制校验,.env 缺失该变量时 docker compose up 直接失败。定位方法:docker compose config --quiet 会复现同样报错;补齐 .env 后重新 up -d。

10.2 数据库口令生成后还能改吗

不能。POSTGRES_PASSWORD 在 postgres 数据卷首次初始化时写入,之后改 .env 只会改变主应用的连接串导致认证失败(数据卷内口令不变)。如必须轮换,走数据库层 ALTER USER 并同步改 .env 的两个 PASSWORD 变量。

10.3 登录报 403 INVALID_ORIGIN

实测现象(用错误 Origin 头模拟"改了接入地址但没改配置"):

$ curl -s -w "\nHTTP:%{http_code}\n" -X POST http://192.168.146.128:8129/api/auth/sign-in/email \
    -H "Content-Type: application/json" -H "Origin: http://192.168.146.128:9999" \
    -d '{"email":"admin@formbricks.local","password":"q1w2e3r4"}'
{"message":"Invalid origin","code":"INVALID_ORIGIN"}
HTTP:403

原因:Better Auth 校验请求 Origin 必须与 BETTER_AUTH_URL 一致。服务器换 IP、接反向代理域名后,必须把 .env 的 WEBAPP_URL 与 BETTER_AUTH_URL 同时改成新地址再 docker compose up -d formbricks。改正后实测登录恢复 200 并返回会话令牌。

附带一个实测结论:WEBAPP_URL 与 BETTER_AUTH_URL 只改其一也会引入诡异行为(链接问卷 URL 与认证基准不一致),两者必须成对修改。

10.4 更换 BETTER_AUTH_SECRET 的副作用

实测确认:密钥更换后服务本身正常启动(无报错),但所有已登录会话立即失效(旧签名 Cookie 不再被接受),用户需重新登录;API Key(fbk_…)不受影响,管理 API 调用照常。排障时如果"升级后大家都被踢出登录",先查是否轮换过该密钥。

10.5 内网 Webhook 收不到推送

官方出于 SSRF 防护默认禁止向内网地址推送 Webhook,实测需要显式加:

DANGEROUSLY_ALLOW_WEBHOOK_INTERNAL_URLS=1

生产环境不要开启(该变量名里的 DANGEROUSLY 即官方态度);把 Webhook 接收端放到有公网域名/证书的环境,或走反代。

10.6 一次性迁移任务反复"没有迁移可执行"

hub-migrate 日志出现 goose: no migrations to run. current version: 27 属正常幂等行为(迁移有版本登记,已执行过则跳过)。真正的故障特征是一次性任务以非 0 退出或日志出现 SQL 报错,此时主应用会因 service_completed_successfully 条件不满足而无法启动——docker compose ps -a 看一次性任务的退出码即可定位。

十一、参考链接