HeyForm 3.0.6 Docker Compose 部署指南
HeyForm 3.0.6 三容器(server+Mongo+KeyDB) Docker Compose 部署实测:官方 compose 样例逐段核对与端口过时纠偏(9513:8000→9157)、EAI_AGAIN 就绪故障、注册/建表单/发布/匿名提交/CSV导出 API 闭环走查、持久化与生产化清单(实测日期 2026-11-01/02)。
HeyForm 3.0.6 Docker Compose 部署指南
目标:把 HeyForm v3.0.6 三容器栈(server + MongoDB + KeyDB)从零部署到可注册、可建表单、可匿名提交、可导出 CSV 的完整闭环。全部命令与回显来自实测环境(Linux / Docker + Docker Compose v2,宿主 IP 192.168.1.172,实测日期 2026-11-01/02)。官方文档的 compose 样例存在端口过时等问题(见 2.3 实测纠偏),请以本文为准。
⚠️ 验收方式说明:本次实测环境无浏览器自动化工具,「注册/建表单向导」的页面级走查降级为 HTTP + GraphQL API 闭环(与浏览器前端调用的是同一组 resolver,服务端行为等价验证);SPA 与公开表单页仅做 HTTP 200/内容长度验收。另注意:会话 Cookie 带
Secure属性,纯 HTTP 访问时浏览器可能拒发该 Cookie,生产建议前置 HTTPS 反向代理(见第六节)。
一、环境信息与前置条件
| 项目 | 值 |
|---|---|
| 应用版本 | HeyForm v3.0.6(镜像 heyform/community-edition:v3.0.6,Alpine 3.23.7 / Node v22.23.4) |
| 依赖服务 | MongoDB(示例用 percona/percona-server-mongodb:4.4,实测 mongo:8.2 亦可)、KeyDB eqalpha/keydb:x86_64_v6.3.3(Redis 协议兼容) |
| 监听端口 | 容器内 9157(APP_LISTEN_PORT 默认值,源码 `+process.env.APP_LISTEN_PORT |
| 宿主映射 | 8150:9157(本项目实验端口,可自选) |
| 磁盘 | 数据目录 mongodb_data/、keydb_data/、assets/(上传持久化) |
| 内存 | 全栈实测 <500MB(server 159MiB / mongo 278MiB / keydb 16MiB) |
前置:已安装 Docker 与 docker compose 插件;两个 64 位十六进制随机密钥(下文生成方法)。
二、官方文档口径与实测纠偏
2.1 官方 compose 样例(docs.heyform.net/open-source/self-hosting,2026-11-02 抓取)
官方给出四种场景(本机/带认证 Mongo/局域网/公网),核心服务定义一致:
services:
heyform:
image: heyform/community-edition:latest
restart: always
volumes:
- ./assets:/app/static/upload
depends_on:
- mongo
- keydb
ports:
- '9513:8000'
environment:
APP_HOMEPAGE_URL: http://127.0.0.1:9513
SESSION_KEY: key1
FORM_ENCRYPTION_KEY: key2
MONGO_URI: 'mongodb://mongo:27017/heyform'
REDIS_HOST: keydb
REDIS_PORT: 6379
mongo:
image: percona/percona-server-mongodb:4.4
...
keydb:
image: eqalpha/keydb:6.3.3
command: keydb-server --appendonly yes --protected-mode no
...
2.2 官方文档强调的要点
APP_HOMEPAGE_URL必须与你实际访问的地址一致,官方原文警告「Incorrect settings may result in login issues」——实测确实如此:Cookie 的Domain直接取自该 URL 的主机名(见 4.2 回显)。- HeyForm 自身不做 TLS 终结,只跑 HTTP;公网部署需 nginx 反代(官方给了样例,但其
proxy_pass http://heyform:9513同样受下述端口问题影响)。
2.3 实测纠偏(v3.0.6)
| 官方样例 | 实测结论 |
|---|---|
ports: '9513:8000' | 过时。v3.0.6 容器监听 9157,映射到 8000 会连接拒绝。容器内验证:wget http://127.0.0.1:8000/ → Connection refused,wget http://127.0.0.1:9157/ → OK。正确写法如 '9513:9157' |
SESSION_KEY: key1 / FORM_ENCRYPTION_KEY: key2 | 占位值。FORM_ENCRYPTION_KEY 用于加密分享链接 token(AES),SESSION_KEY 用于会话签名,生产必须换强随机值;且两者不可随意更换——换 FORM_ENCRYPTION_KEY 会使已分发的表单链接 token 失效 |
image: ...:latest | 建议钉 v3.0.6(2026-11-01 查询 latest==v3.0.6,但项目约 1-3 周发新版) |
depends_on 无 healthcheck | 见 3.3 实测故障:server 先于 mongo 就绪会反复 EAI_AGAIN 重试,靠自愈恢复;建议加 healthcheck + condition: service_healthy |
三、实操部署
3.1 目录与密钥
mkdir -p heyform/{assets,mongodb_data,keydb_data} && cd heyform
openssl rand -hex 32 # SESSION_KEY
openssl rand -hex 32 # FORM_ENCRYPTION_KEY
.env(与 compose 同目录):
SESSION_KEY=e374ae3d...(64hex,实测值略)
FORM_ENCRYPTION_KEY=d9b8f5c3...(64hex,实测值略)
3.2 实测可用的 docker-compose.yml
networks:
keydb:
mongo:
services:
heyform:
image: heyform/community-edition:v3.0.6
container_name: heyform-306-server
restart: always
volumes:
- ./assets:/app/static/upload
depends_on:
- mongo
- keydb
ports:
- '8150:9157'
networks:
- mongo
- keydb
environment:
APP_HOMEPAGE_URL: http://192.168.1.172:8150
SESSION_KEY: ${SESSION_KEY}
FORM_ENCRYPTION_KEY: ${FORM_ENCRYPTION_KEY}
MONGO_URI: 'mongodb://mongo:27017/heyform'
REDIS_HOST: keydb
REDIS_PORT: 6379
mongo:
image: mongo:8.2
container_name: heyform-306-mongo
restart: always
volumes:
- ./mongodb_data:/data/db
networks:
- mongo
keydb:
image: eqalpha/keydb:x86_64_v6.3.3
container_name: heyform-306-keydb
restart: always
command: keydb-server --appendonly yes --protected-mode no
volumes:
- ./keydb_data:/data
networks:
- keydb
要点:MONGO_URI 主机名 mongo、REDIS_HOST 用 keydb,靠 compose 网络别名解析;APP_HOMEPAGE_URL 填你将来的访问地址(局域网 IP 或域名),改此值后需重建容器生效。
3.3 启动与首次就绪
docker compose up -d
实测输出:
Network heyform_mongo Created
Container heyform-306-mongo Started
Container heyform-306-keydb Started
Container heyform-306-server Started
实测故障案例:首次启动时 server 早于 mongo 可用,日志出现 MongooseServerSelectionError: getaddrinfo EAI_AGAIN mongo,重试若干次后自愈。这是 depends_on 只保证容器启动、不保证服务就绪的典型表现。稳妥做法:
mongo:
healthcheck:
test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping')"]
interval: 5s
timeout: 3s
retries: 12
heyform:
depends_on:
mongo:
condition: service_healthy
3.4 健康验收
$ curl -s http://localhost:8150/health
{"status":"ok","service":"heyform-server","timestamp":"2026-11-01T19:44:32.024Z","uptime":6654}
$ curl -s http://localhost:8150/api/config
{"homepageURL":"http://192.168.1.172:8150","websiteURL":"http://192.168.1.172:8150","appDisableRegistration":false,"cookieDomain":"192.168.1.172","enableGoogleFonts":true,"uploadOrigins":["http://192.168.1.172:8150"],"verifyEmailResendCooldownSeconds":1,"verifyUserEmail":true,"disableSubmissionIp":false,"disableLoginWithPassword":false,"disableLoginWithGoogle":true,"disableLoginWithApple":true,"disableLoginWithOidc":true,"oidcDisplayName":"SSO","oidcDisplayIconUrl":""}
$ curl -s -o /dev/null -w '%{http_code}' http://localhost:8150/
200 # SPA 首页 3011B
/api/config 是公开配置快照,可用来确认 APP_HOMEPAGE_URL、注册开关、三方登录启用状态是否按预期生效。
四、首次注册与建表单向导闭环(API 等价走查)
浏览器向导(注册→工作区→新建表单→编辑→发布→分享链接)对应的后端调用链如下,全部为实测回显。GraphQL 端点
POST /graphql,所有请求必须带x-device-id请求头(任意 UUID,后续须与 session 绑定值一致,缺失→403Forbidden request error)。
4.1 注册(signUp)
$ curl -X POST http://localhost:8150/graphql -H 'Content-Type: application/json' \
-H "x-device-id: 34afb252-..." \
-d '{"query":"mutation { signUp(input: {name: \"admin\", email: \"admin@heyform.local\", password: \"Q1w2e3r4\"}) }"}'
{"data":{"signUp":true}}
密码策略(源码正则实测):至少 8 位,必须同时含小写、大写、数字——q1w2e3r4 会被拒(Invalid password),Q1w2e3r4 通过。
注意:VERIFY_USER_EMAIL 默认 true,注册即要求邮箱验证码;未配置 SMTP 时无人能收到验证码(官方 .env.example 明确警告),实验环境可设 VERIFY_USER_EMAIL=false(实测未验证账号 API 仍可用,但 UI 可能拦截,建议直接关掉)。
4.2 登录与会话
$ curl -si -X POST .../graphql -H "x-device-id: 34afb252-..." \
-d '{"query":"query { login(input: {email: \"admin@heyform.local\", password: \"Q1w2e3r4\"}) }"}'
Set-Cookie: HEYFORM_SESSION=YZlmz%2BkNp6RuF4fJAoO53...; Max-Age=1296000; Domain=192.168.1.172; Path=/; HttpOnly; Secure; SameSite=Lax
{"data":{"login":true}}
观察点:Cookie Domain 取自 APP_HOMEPAGE_URL 主机名、Max-Age=1296000(SESSION_MAX_AGE=15d 默认值)、带 Secure 标志。因此浏览器必须用与 APP_HOMEPAGE_URL 一致的主机名访问,且纯 HTTP 下 Secure Cookie 存在被浏览器拒发的风险——这是官方文档所说 login issues 的根源,公网务必上 HTTPS。
4.3 建工作区 → 建表单 → 编辑 → 发布
$ createTeam(input:{name:"My Workspace", projectName:"My Project"})
{"data":{"createTeam":"QCs9H4kR"}} # 返回团队短id,自动创建默认项目 ah1jLWFf
$ createForm(input:{projectId:"ah1jLWFf", name:"Webhook E2E Form", interactiveMode:1, kind:1})
{"data":{"createForm":"VUnqcZMz"}} # 表单id即8位短id
$ updateFormSchemas(input:{formId:"VUnqcZMz", drafts:[...字段...], logics:[], version:<时间戳>})
{"data":{"updateFormSchemas":{"canPublish":true,"version":1793554696483}}}
$ publishForm(input:{formId:"VUnqcZMz", drafts:[...], logics:[], version:1793554696483})
{"data":{"publishForm":true}}
坑(实测):
publishForm的version必须等于updateFormSchemas返回的服务端时间戳,否则报INVALID_DRAFT_VERSION。- 字段
kind必须是 FieldKindEnum 值(short_text/multiple_choice/rating/…);自造值(如text)不报错但提交后 answers 为空,静默丢数据。 - 多选答案格式:
{"value":["c2"]}(对象包选项 id 数组),裸数组报This field is required。
4.4 匿名访问与提交
$ curl ... -H 'x-anonymous-id: <uuid>' -d '{"query":"query { openForm(input:{formId:\"VUnqcZMz\"}) }"}'
{"data":{"openForm":"mWLwmr5MUSmUK9aI8GNYV45CVPh2kFf8S7K9KvFatvA++STUyQvmVFBIcIGoOTZ4"}} # AES加密token
$ completeSubmission(input:{formId,answers,hiddenFields:[],openToken:<上一步token>})
{"data":{"completeSubmission":{"clientSecret":null}}}
$ GET http://localhost:8150/form/VUnqcZMz
200 # 公开表单页(form-renderer)
4.5 提交验证与导出
$ submissions(input:{formId:"VUnqcZMz",page:1,limit:10})
{"data":{"submissions":{"total":2,...}}}
$ curl -s "http://localhost:8150/api/export/submissions?formId=VUnqcZMz" \
-H "x-device-id: 34afb252-..." -b "HEYFORM_SESSION=<登录cookie>"
"#","Your Name","Plan","Score","Start Date (UTC)","Submit Date (UTC)"
"6ae77bfcedc7952478a66c44","Fail Case","Free",2,"2026-11-01T17:50:52.000Z","2026-11-01T17:50:52.000Z"
HTTP:200
无 session 访问导出 → 401 Unauthorized;表单无提交时导出 → 400 {"message":"The submissions does not exist"}。CSV 导出链路(登录→设备ID校验→表单守卫→导出)实测闭环。
五、数据持久化与备份
- 上传的图片存
./assets(容器/app/static/upload),Mongo 数据在./mongodb_data,KeyDB 开 AOF(./keydb_data,实测appendonly yes、CONFIG GET appendonly回显 yes)。 - 备份实测:
docker exec heyform-306-mongo mongodump --db heyform --archive --gzip > backup.archive.gz(6109B)+mongorestore --dryRun校验通过,详见 CLI 篇。
六、生产化清单
APP_HOMEPAGE_URL改为正式域名并前置 nginx + TLS(官方样例端口需按 2.3 修正为 9157)。- 更换
SESSION_KEY/FORM_ENCRYPTION_KEY为强随机值;上线后不要再改FORM_ENCRYPTION_KEY。 - 配 SMTP 或显式
VERIFY_USER_EMAIL=false;生产建议APP_DISABLE_REGISTRATION=true后再邀请账号(改参闭环见配置篇)。 - Mongo 加认证(官方有带
MONGO_USER/MONGO_PASSWORD的 compose 变体),KeyDB 所在网络不要暴露公网。 depends_on加 healthcheck(3.3)。- 钉版本 tag,升级流程见 CLI 篇。
关联文章:配置参数全量分类与改参生效闭环见《HeyForm 3.0.6 配置参数详解》;compose 运维、Mongo/KeyDB 巡检与数据导出链路见《HeyForm 3.0.6 常用 CLI 命令与工具详解》;组件选型背景见《HeyForm 3.0.6 介绍与选型指南》。