Formbricks Docker Compose 部署实战
Formbricks 6.1.2 官方 Docker Compose 全栈部署实测:12 项必填密钥体系、迁移一次性任务机制、初始化向导、健康验证、资源基线与 INVALID_ORIGIN 等真实排障实录。
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 / Compose | Docker 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)包含以下常驻服务与一次性任务:
| 服务 | 官方镜像 | 端口(编排网络内) | 职责 |
|---|---|---|---|
| formbricks | ghcr.io/formbricks/formbricks:latest | 3000(映射宿主 8129) | 主应用:管理后台、问卷投放、响应收集 API |
| postgres | pgvector/pgvector:pg18 | 5432(不发布宿主) | 业务数据库(含 pgvector 扩展) |
| redis | valkey/valkey(官方 digest 固定) | 6379(不发布宿主) | 会话缓存、限流、审计日志、任务队列 |
| hub | ghcr.io/formbricks/hub:latest | 8080(不发布宿主) | 实时通道、Webhook 分发、数据富化 |
| hub-worker | 同 hub 镜像 | — | 异步任务 worker(Webhook/富化实际执行者) |
| cube | ghcr.io/formbricks/cube:1.7.50-1 | 4000(不发布宿主) | 语义层分析 API(洞察图表数据源) |
| spicedb | authzed/spicedb:v1.52.0 | 50051(不发布宿主) | 集中授权服务(组织/团队权限裁决) |
一次性初始化任务(执行完即退出,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 部署并实测。
三、前置条件
- 一台 Ubuntu 22.04 服务器,已安装 Docker 24+ 与 Compose v2 插件;
- 服务器与
ghcr.io、docker.io的网络可达(离线环境需提前导入镜像,见第十章常见问题); - 一个空闲的宿主端口用于 Web 入口(本文用 8129,官方默认 3000,可按需改映射);
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_ENCODED | URL 编码版口令 | 拼接 DATABASE_URL 用;hex 字符串无需转义时与原值相同 |
BETTER_AUTH_SECRET | 会话/令牌签名密钥 | 更换后所有已登录会话失效,需重新登录 |
ENCRYPTION_KEY | 数据加密密钥 | 用于 2FA、单次链接加密、审计哈希;更换会导致已加密数据不可解 |
CRON_SECRET | 定时任务 API 口令 | 内部 cron 调用口令 |
HUB_API_KEY | Hub 内部 API 密钥 | 主应用与 Hub 之间调用鉴权 |
CUBEJS_API_SECRET | Cube 访问密钥 | 分析 API 鉴权 |
CUBEJS_JWT_ISSUER / CUBEJS_JWT_AUDIENCE | Cube JWT 签发方/受众 | 官方固定值 formbricks-web / formbricks-cube |
AUTHZED_TOKEN | SpiceDB gRPC 预共享密钥 | 主应用连接授权服务用 |
AUTHZED_DATABASE_PASSWORD | SpiceDB 数据库账号口令 | 由 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>_postgres | postgres 数据目录 | 全部业务数据(问卷/响应/联系人/用户/组织) |
<project>_redis | /data | Valkey 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_DISABLED | 1(关) | 无 SMTP 时保持 1;配好邮件后设 0 开启邮箱验证 |
PASSWORD_RESET_DISABLED | 1(关) | 同上,重置密码依赖邮件发送 |
DANGEROUSLY_ALLOW_WEBHOOK_INTERNAL_URLS | 未设 | 内网测试 Webhook 目标(如 http://192.168.x.x/hook)时需设 1,生产环境切勿开启 |
完整参数逐项详解见 《Formbricks 6.1.2 配置参数详解》。
七、配置文件挂载说明
官方编排中三类挂载文件需要理解,改动前先看它们被谁消费:
./cube/cube.js与./cube/schema/→ Cube 服务(只读):语义层数据模型。Cube 用它把 PostgreSQL 业务表映射为分析指标;正常使用不需要改,升级大版本后若图表报模型错误,从官方仓库同步这两个文件;- `./saml-connection/ → 主应用(读写):SAML 企业版连接配置目录,空目录即可启动(本文实测即空目录),配置 SSO 后由应用写入;
./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 看一次性任务的退出码即可定位。
十一、参考链接
- 官方自托管部署文档:https://formbricks.com/docs/self-hosting/deployment
- 官方编排文件(docker 目录):https://github.com/formbricks/formbricks/tree/main/docker
- 环境变量参考:https://formbricks.com/docs/self-hosting/configuration/environment-variables
- 升级与迁移指南:https://formbricks.com/docs/self-hosting/migration-guide
- 本专栏系列:《Formbricks 介绍与选型指南》、《Formbricks 6.1.2 配置参数详解》、《Formbricks 常用命令与运维实战》