OpnForm v2.8.1 Docker Compose 部署指南

OpnForm v2.8.1 官方 Docker Compose 七服务部署实测闭环:一键 setup、端口 !override 纠偏、管理员初始化与建表单/提交/CSV 导出 API 闭环、改 env 重建与 ingress 502 排障实录

OpnFormOpnForm表单构建DockerDocker ComposePostgreSQLRedis2026/11/02

OpnForm v2.8.1 Docker Compose 部署指南

目标:在真实 Linux 宿主上用官方 Docker Compose 路径部署 OpnForm v2.8.1 自托管实例,完成管理员初始化、建表单、匿名提交、提交列表与 CSV 导出的端到端闭环。全部命令与回显取自 2026-11-01/02 真实环境实测。

⚠️ 验收方式说明:本实验环境无浏览器自动化,页面验收降级为 HTTP 状态码 + <title> 标签 + API 闭环验证(curl 调用的正是前端页面同组使用的 Laravel 路由,服务端行为等价)。编辑器拖拽等纯浏览器交互未做 UI 级验收。

一、环境信息

  • 宿主:Linux x86_64(内网 IP 192.168.1.172)
  • Docker:29.9.2(docker --version → Docker version 29.9.2, build a82e528)
  • Docker Compose:v5.6.1(docker compose version --short → 5.6.1)
  • OpnForm:v2.8.1(2026-10-29 发布;容器内实测 APP_VERSION_DOCKER=2.8.1)
  • 部署目录:/opt/data/quzuo/tmp/opnform/OpnForm(v2.8.1 源码 tarball 解压,约 28.8 MiB)
  • 对外端口:8161(宿主 0.0.0.0:8161->80/tcp;8160 已被其他服务占用,实测改用 8161)
  • 7 个服务容器:opnform-api、opnform-api-worker、opnform-api-scheduler、opnform-client、opnform-db、opnform-redis、opnform-ingress
  • 演示账号(setup 期创建):g25admin@example.com / G25test@12345
  • 实测日期:2026-11-01/02(Asia/Shanghai)

二、镜像选型说明

官方 docker-compose.yml(v2.8.1,133 行)引用 5 个镜像、7 个服务:

服务镜像角色
apijhumanj/opnform-api:latestLaravel 12 API(PHP-FPM,监听 9000)
api-workerjhumanj/opnform-api:latest队列消费者,php artisan queue:work
api-schedulerjhumanj/opnform-api:latest定时任务,php artisan schedule:work
uijhumanj/opnform-client:latestNuxt SSR 前端(Vue 3 + Nuxt,监听 3000)
redisredis:7缓存 / 队列 / 会话
dbpostgres:16主数据库
ingressnginx:1反向代理入口(唯一发布端口)

三个 Laravel 服务共享同一镜像锚点(YAML &api-environment),仅 command 不同。

实测版本核对:latest 标签容器内 printenv APP_VERSION_DOCKER 输出 2.8.1,与 GitHub v2.8.1 release(2026-10-29)一致。⚠️ 网络情况注明:本宿主访问 Docker Hub 的 tags API 超时,无法逐 tag 比对,版本钉住依据为容器内 APP_VERSION_DOCKER 实测值;镜像拉取本身走默认渠道成功。若需严格钉版,官方镜像发布同名版本 tag,可将 latest 改为对应 tag。

💡 技术拓展:入口反向代理不止 nginx 一种。OpnForm 官方 compose 内置 nginx,但代码与配置同时预留了 Caddy 集成(路由 caddy/ask-certificate/{secret}、环境变量 CADDY_SECRET/CADDY_AUTHORIZED_IPS)。常见选型对比——

代理特点适用场景
nginx官方 compose 内置,模板变量注入,生态最成熟与官方部署路径保持一致
Caddy自动签发/续期 HTTPS,OpnForm 有原生联动端点公网单机快速上 TLS
Traefik服务发现式路由,标签驱动容器动态调度、多服务网关

本文按官方路径采用内置 nginx ingress,最省事且与文档一致;上 TLS 时可再套一层 Caddy/Traefik 或云 LB。

三、前置条件

  1. Docker 与 Compose v2。实测回显:
docker --version
# Docker version 29.9.2, build a82e528
docker compose version --short
# 5.6.1

官方脚本 docker-setup.sh 自动探测 docker compose(v2 插件)或 docker-compose(经典版),两者都没有时报错并提示 sudo apt-get install -y docker-compose-plugin。

  1. 端口空闲。现场 docker ps 实测 8149/8152/8157 已被其他项目占用,任务原计划端口 8160 被 mobilizon-mobilizon-1(0.0.0.0:8160->4000)占用,因此本部署改用 8161。查占用:
docker ps --format '{{.Names}}\t{{.Ports}}' | grep 816
# 其他项目的映射行(本部署避开)
  1. 内存。全栈空闲态实测约 411 MiB(docker stats --no-stream 七项合计:client 216.7 / api-scheduler 54.5 / api 49.3 / api-worker 43.2 / db 31.1 / ingress 9.3 / redis 7.1 MiB),2 GB 内存的宿主即可跑通,生产建议 4 GB 以上。

  2. 脚本行尾(Windows 检出源码时)。官方警告:api/artisan 若被转成 CRLF,容器会卡在 "Waiting for DB to be ready"。Linux 宿主无需处理;Windows 检出后执行 sed -i 's/\r$//' api/artisan 或 dos2unix api/artisan。

四、部署步骤

4.1 获取 v2.8.1 源码

官方推荐 git clone(组织化迁移后仓库为 OpnForm/OpnForm,旧地址 JhumanJ/OpnForm 301 重定向仍可用):

git clone https://github.com/OpnForm/OpnForm.git
cd OpnForm
git checkout v2.8.1

本实测采用 GitHub release 源码 tarball(opnform-v2.8.1.tar.gz,28.8 MiB)解压到 /opt/data/quzuo/tmp/opnform/OpnForm,与 checkout v2.8.1 内容等价。compose 文件、docker/nginx.conf、scripts/ 均随源码携带,缺一不可。

4.2 端口 override(含 !override 纠偏)

官方 compose 的 ingress 固定 80:80。宿主 80 已被占用,需要改端口。实测纠偏:Compose 对 ports 列表默认追加合并,首版 override 只写 - 8161:80 导致 ingress 同时映射 80:80 与 8161:80;必须用 !override 标签整体替换。最终实测文件 /opt/data/quzuo/tmp/opnform/OpnForm/docker-compose.override.yml:

services:
  ingress:
    ports: !override
      - 8161:80

后续所有 compose 命令统一带双文件前缀(本文档内命令均可照抄执行):

docker compose -f docker-compose.yml -f docker-compose.override.yml <子命令>

4.3 运行官方一键安装脚本

cd /opt/data/quzuo/tmp/opnform/OpnForm
chmod +x scripts/docker-setup.sh
./scripts/docker-setup.sh --public-url http://192.168.1.172:8161

--public-url 传浏览器访问的 origin(不带路径与尾斜杠;生产用 HTTPS)。脚本实测行为(EXIT=0):

  1. 调 setup-env.sh --docker --public-url …:从 api/.env.docker、client/.env.docker 模板生成两个 .env,生成 APP_KEY=base64:…、JWT_SECRET、FRONT_API_SECRET/NUXT_API_SECRET 共享密钥,并把四元组 URL 写齐;
  2. 拒绝 JWT_SKIP_IP_UA_VALIDATION=true 的生产配置(直接 exit 1);
  3. 探测到 docker-compose.override.yml 自动追加 -f;
  4. docker compose up -d 拉镜像并启动全部服务。

setup.log 关键回显(输出节选(实测首尾),中间为镜像层下载进度):

Starting OpnForm Docker setup...
Setting up environment files...
OpnForm setup detected the --docker flag. Preparing Docker-specific environment...
Creating OpnForm's main .env file from the template...
Creating OpnForm's client .env file from the template...
Configured OpnForm's public URL as http://192.168.1.172:8161.
✅ OpnForm environment setup is now complete. Enjoy building your forms!
Production mode - using docker-compose.yml
Found docker-compose.override.yml - including local overrides
Starting Docker containers...
 Image nginx:1 Pulled
 Image jhumanj/opnform-api:latest Pulled
 Image jhumanj/opnform-client:latest Pulled
 Network opnform_default Creating
 Volume opnform_postgres-data Creating
 Volume opnform_opnform_storage Creating
 Volume opnform_redis-data Creating
 Container opnform-db Healthy
 Container opnform-redis Healthy
 Container opnform-api Started
 Container opnform-api Healthy
 Container opnform-client Started
 Container opnform-ingress Started
Production environment setup complete!
Please wait a moment for all services to start
Then visit: http://192.168.1.172:8161

启动顺序由 depends_on + healthcheck 保证:db/redis 先 healthy → api 起 → api healthy → ui 起 → ingress 起,全程约 1 分钟。

4.4 验证服务状态

docker compose -f docker-compose.yml -f docker-compose.override.yml ps

实测回显(7 服务全 healthy):

NAME                    IMAGE                           COMMAND                  SERVICE         CREATED          STATUS                    PORTS
opnform-api             jhumanj/opnform-api:latest      "/usr/local/bin/opnf…"   api             9 minutes ago    Up 9 minutes (healthy)    9000/tcp
opnform-api-scheduler   jhumanj/opnform-api:latest      "/usr/local/bin/opnf…"   api-scheduler   9 minutes ago    Up 9 minutes (healthy)    9000/tcp
opnform-api-worker      jhumanj/opnform-api:latest      "/usr/local/bin/opnf…"   api-worker      9 minutes ago    Up 9 minutes (healthy)    9000/tcp
opnform-client          jhumanj/opnform-client:latest   "docker-entrypoint.s…"   ui              9 minutes ago    Up 9 minutes (healthy)
opnform-db              postgres:16                     "docker-entrypoint.s…"   db              20 minutes ago   Up 20 minutes (healthy)   5432/tcp
opnform-ingress         nginx:1                         "/docker-entrypoint.…"   ingress         18 minutes ago   Up 8 minutes (healthy)    0.0.0.0:8161->80/tcp, [::]:8161->80/tcp
opnform-redis           redis:7                         "docker-entrypoint.s…"   redis           20 minutes ago   Up 20 minutes (healthy)   6379/tcp

端口列只有 ingress 一行有宿主映射,!override 纠偏生效。自托管专用健康端点实测:

curl -s http://192.168.1.172:8161/api/healthcheck
# {"status":"ok","dependencies":{"database":true,"redis":true}}

💡 说明:/api/healthcheck 路由仅在 self_hosted 配置下注册(api/routes.php L54-56),返回体直接报告数据库与 Redis 依赖状态,适合挂拨测。

4.5 初始化管理员(/setup)

访问 http://192.168.1.172:8161,setup 未完成时实测 302 → /setup,页面 <title>Setup - OpnForm - OpnForm</title>。在页面创建管理员,或用 API 等价完成(本实测即此路径):

curl -s -X POST http://192.168.1.172:8161/api/register \
  -H 'Content-Type: application/json' \
  -d '{"name":"G25 Admin","email":"g25admin@example.com","password":"G25test@12345","password_confirmation":"G25test@12345","hear_about_us":"self-hosted eval"}'

实测两次纠偏:首次 422 The hear_about_us field is required.(自托管注册必填该字符串字段,agree_terms 仅云端要求);补齐后 200,返回 {"token":"eyJ0eXAi…","token_type":"bearer","expires_in":86400,"new_user":true},用户 id=1、plan_tier=pro。

⚠️ 密码策略纠偏:RegisterController 规则为 min:8 + 必须含字母、数字、特殊字符,且 confirmed(必须带 password_confirmation)。演示惯用口令 q1w2e3r4 实测 422 The password format is invalid.(缺特殊字符),故本文档演示账号采用 G25test@12345。

setup 完成后公开注册即关闭(实测再调 /api/register 返回 Registration is not allowed.),后续用户由管理员邀请;免费版自托管实例上限 2 用户。

4.6 端到端功能闭环(登录 → 建表单 → 提交 → 导出)

TOKEN=$(curl -s -X POST http://192.168.1.172:8161/api/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"g25admin@example.com","password":"G25test@12345","remember":false}' \
  | python3 -c 'import json,sys;print(json.load(sys.stdin)["token"])')

实测要点:登录路径是 /api/login(/api/auth/login 404);响应字段名是 token 而非 access_token。

建表单(字段枚举实测纠偏:presentation_style ∈ classic/focused、width ∈ centered/full、size ∈ sm/md/lg,首次 422 即因枚举值非法):

curl -s -X POST http://192.168.1.172:8161/api/open/forms \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"title":"G25 反馈表单","workspace_id":1,"visibility":"public","type":"form",
       "properties":{"presentation_style":"classic","size":"md","width":"centered","theme":"default",
       "fields":[{"id":"field-name","name":"Your Name","type":"text","required":true},
                 {"id":"field-email","name":"Email","type":"email"},
                 {"id":"field-plan","name":"Plan","type":"select","properties":{"choices":["Free","Pro"]}},
                 {"id":"field-rating","name":"Rating","type":"rating"}]}}'
# {"type":"success","message":"Form created."}   → id=1, slug=g25-spqlvr

公开页与匿名提交:

curl -s http://192.168.1.172:8161/forms/g25-spqlvr | grep -o '<title>[^<]*</title>'
# <title>G25 反馈表单 - OpnForm</title>
curl -s http://192.168.1.172:8161/api/forms/g25-spqlvr/view
# {"type":"success","message":"Form viewed."}
curl -s -X POST http://192.168.1.172:8161/api/forms/g25-spqlvr/answer \
  -H 'Content-Type: application/json' \
  -d '{"field-name":"张三","field-email":"zhangsan@example.com","field-plan":"Pro","field-rating":4}'
# {"type":"success","message":"Form submission saved.","submission_id":null,…}

⚠️ 提交经队列异步落库:answer 返回时 submission_id 为 null,实测约 5-8 秒后才能在工作台列表看到,属正常行为。

查列表与导出 CSV:

curl -s "http://192.168.1.172:8161/api/open/forms/1/submissions" -H "Authorization: Bearer $TOKEN"
# data 数组 2 条(李四/Free/5、张三/Pro/4,各带 submission_id UUID;分页响应无 total 字段)

curl -s -X POST "http://192.168.1.172:8161/api/open/forms/1/submissions/export" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"columns":{"Your Name":true,"Email":true,"Plan":true,"Rating":true},"file_type":"csv"}'
# "id","Your Name","Email","Plan","Rating","created_at"   ← 小数据量同步返回 CSV 正文(带 BOM 头)

数据库侧核对(实测 forms=1、form_submissions=2、users=1):

docker compose -f docker-compose.yml -f docker-compose.override.yml exec -T db \
  psql -U forge -d forge -tc "select (select count(*) from forms), (select count(*) from form_submissions), (select count(*) from users);"
#      1 |     2 |     1

五、数据持久化说明

compose 声明 3 个命名卷(实测项目前缀 opnform_):

卷挂载点内容备份优先级
opnform_postgres-datadb:/var/lib/postgresql/data全部业务数据(44 张表)必做
opnform_opnform_storageapi:/usr/share/nginx/html/storage上传附件、表单素材(public/storage LINKED)必做
opnform_redis-dataredis:/data缓存/队列/会话可不备(丢了重新生成)

宿主侧配置状态文件:api/.env、client/.env(含 APP_KEY/JWT_SECRET 等密钥,丢失会导致会话失效、密文无法解密)、docker-compose.override.yml。备份示例:

docker compose -f docker-compose.yml -f docker-compose.override.yml exec -T db \
  pg_dump -U forge forge | gzip > /opt/data/quzuo/tmp/opnform/backup-$(date +%F).sql.gz

⚠️ 注意:卷名带项目前缀,若换目录/换项目名重新 up,Compose 会新建空卷而不是复用旧卷。迁移时先 docker volume ls | grep opnform 核对。

六、环境变量表

docker-setup.sh --public-url 一条命令写齐必配四元组(实测值以本环境为例):

文件变量实测值说明
api/.envAPP_URLhttp://192.168.1.172:8161后端公开 URL
api/.envFRONT_URLhttp://192.168.1.172:8161前端公开 URL
client/.envNUXT_PUBLIC_APP_URLhttp://192.168.1.172:8161浏览器侧应用 origin
client/.envNUXT_PUBLIC_API_BASEhttp://192.168.1.172:8161/api浏览器侧 API base

脚本同时生成并保留(重跑不覆盖)的密钥:APP_KEY(base64:…加密键)、JWT_SECRET(令牌签名)、FRONT_API_SECRET 与 NUXT_API_SECRET(前后端共享密钥,同值)。系统级真实密钥不要提交到版本库;备份 .env 时按密文管理。

setup 生成的 api/.env 关键运行项实测:APP_ENV=production、APP_DEBUG=false、CACHE_DRIVER/QUEUE_CONNECTION/SESSION_DRIVER=redis、LOG_CHANNEL=errorlog、MAIL_MAILER=log、JWT_TTL=1440、JWT_SKIP_IP_UA_VALIDATION=false、PUBLIC_UPLOADS_RATE_LIMIT_PER_MINUTE=30、PUBLIC_UPLOADS_RATE_LIMIT_PER_HOUR=300。

client/.env 还有一个脚本未写、模板自带的内部变量:NUXT_PRIVATE_API_BASE=http://ingress/api——SSR 服务端调用走 compose 网络内的 ingress,而非绕公网 origin。

⚠️ 实测确认 api/.env 中没有任何 DB_* 行:数据库连接完全由 compose environment 注入(DB_HOST=db、REDIS_HOST=redis、DB_DATABASE/DB_USERNAME/DB_PASSWORD=${…:-forge}、DB_CONNECTION=pgsql),容器内 printenv 实测与 compose 变量默认值 forge 一致。改数据库口令应在宿主 shell 或根目录 .env 设置 DB_PASSWORD 等变量(Compose 解析 ${…} 用的是 shell/根 .env,env_file 只负责向容器注入,两层机制不同)。

七、配置参数说明

与部署直接相关的 compose 内参数如下(本表已覆盖部署所需的全部取值):

变量位置实测值影响
PHP_MEMORY_LIMITapi 系 environment1G单进程内存上限
PHP_MAX_EXECUTION_TIMEapi 系 environment600长任务(导出)超时
PHP_UPLOAD_MAX_FILESIZE / PHP_POST_MAX_SIZEapi 系 environment64M上传大小上限
NGINX_MAX_BODY_SIZEingress environment64m经模板注入 client_max_body_size,需 ≥ PHP 上传限制
JWT_TTLapi/.env1440(分钟=24h)登录令牌有效期,实测 expires_in: 86400
OPNFORM_ANONYMOUS_TELEMETRY_DISABLEDapi/.env默认未设置置 true 关闭匿名遥测(仅 production+self_hosted 激活遥测)

四层上传限制要一起对齐:nginx 64m = PHP 64M,而应用层 PUBLIC_UPLOADS_MAX_FILE_SIZE 默认 50000000(50 MB)——实际匿名附件上限取最小者 50 MB。

八、配置文件挂载

宿主文件容器路径用途
./api/.envapi 系 env_file后端全部配置
./client/.envui env_file前端配置
./docker/nginx.confingress:/etc/nginx/templates/default.conf.template入口路由模板

nginx 模板用官方镜像的 envsubst 机制渲染,${NGINX_MAX_BODY_SIZE} 即由 compose environment 注入。实测模板核心路由(节选):

map $request_uri $api_uri {
    ~^/api(/.*$) $1;
    default $request_uri;
}
server {
    listen 80;
    client_max_body_size ${NGINX_MAX_BODY_SIZE};
    location / {
        proxy_pass http://opnform-client:3000;   # 页面与静态资源 → Nuxt SSR
    }
    location ^~ /api/_nuxt_icon/ {
        proxy_pass http://opnform-client:3000;   # 图标例外:仍回前端
    }
    location ~/(api|open|local\/temp|forms\/assets)/ {
        try_files $uri $uri/ /index.php$is_args$args;   # → Laravel
    }
    location ~ \.php$ {
        fastcgi_pass opnform-api:9000;
        fastcgi_param REQUEST_URI $api_uri;      # 剥掉 /api 前缀再进 Laravel
    }
}

解读:/api/* 经 fastcgi 进 Laravel 前由 map 剥前缀;/api/_nuxt_icon/ 被 ^~ 抢先规则送回前端——若自定义反代漏掉这条,管理后台图标会 404(官方 FAQ 场景)。Synology Container Manager 部署时须保持 api/.env、client/.env、docker/nginx.conf 相对目录结构完整(官方文档明示,nginx.conf 要挂成文件而非目录)。

九、等价 docker run

Compose 与 docker run 参数映射(供理解,不建议手敲 7 个容器——depends_on/healthcheck 网络编排是官方路径的核心价值):

Compose 字段docker run 等价
image: postgres:16docker run -d --name opnform-db -e POSTGRES_DB=forge -e POSTGRES_USER=forge -e POSTGRES_PASSWORD=*** -v opnform_postgres-data:/var/lib/postgresql/data postgres:16
ports: - 8161:80docker run -d --name opnform-ingress -p 8161:80 -v $PWD/docker/nginx.conf:/etc/nginx/templates/default.conf.template -e NGINX_MAX_BODY_SIZE=64m nginx:1
env_file: ./api/.envdocker run --env-file ./api/.env …
environment: DB_HOST: db-e DB_HOST=db
volumes: opnform_storage:…:rw-v opnform_opnform_storage:/usr/share/nginx/html/storage
command: ["php","artisan","queue:work"]镜像默认 entrypoint 后追加 php artisan queue:work
network(默认网络)docker network create opnform_default 后各容器 --network opnform_default

十、常用运维命令

统一前缀 docker compose -f docker-compose.yml -f docker-compose.override.yml(下文简写 DC,在部署目录内执行):

DC ps                      # 服务与健康状态
DC logs -f api             # 跟踪某服务日志(LOG_CHANNEL=errorlog → stdout)
DC exec -T api php artisan about   # 运行时全景(见 10.1)
DC pull && DC up -d        # 更新镜像并滚动重建(官方维护路径)
DC down                    # 整栈停止并删除容器(卷保留)

实测纠偏:改 .env 后 restart 不生效。docker compose restart api 后容器内 printenv OPNFORM_ANONYMOUS_TELEMETRY_DISABLED 仍返回 rc=1(未加载新行);必须 DC down api && DC up -d 重建容器才加载 env_file 新值(Compose v5.6.1 实测 down 可带服务名,仅移除该服务容器;伴随的 Network opnform_default Resource is still in use 警告无害)。api/api-worker/api-scheduler 共享同一 env_file,改一次参数三个服务都要重建。

实测新发现:重建 api/ui 后 ingress 会 502。nginx 启动时解析并缓存上游容器 IP,--force-recreate api 换 IP 后仍打旧地址,POST /api/login 实测返回 502 Bad Gateway。修复一条命令:

DC restart ingress
# 之后 /forms/g25-spqlvr title 恢复 "G25 反馈表单 - OpnForm"、/api/login 恢复 200

凡重建过 api 或 ui,顺手 restart ingress(nginx 配置未变时 restart 安全,无 env 加载问题)。

10.1 运行时确认(artisan about 实测节选)

  Application Name ................................................... OpnForm
  Laravel Version .................................................... 12.70.1
  PHP Version ......................................................... 8.3.36
  Environment ..................................................... production
  Debug Mode ............................................................. OFF
  URL ..................................................... 192.168.1.172:8161
  Cache .............................................................. CACHED
  Database ............................................................. pgsql
  Queue ................................................................ redis
  Session .............................................................. redis
  public/storage ...................................................... LINKED

七项 drivers/缓存状态与 compose 注入一致,可作为部署后的验收基线。调度器健康检查(api-scheduler healthcheck 同款命令):

DC exec -T api php artisan app:scheduler-status --mode=check --max-minutes=3
# Scheduler is healthy. Last ran: 32 seconds ago

十一、常见问题

11.1 注册接口 422:hear_about_us 必填

自托管 /api/register 必须携带字符串字段 hear_about_us(实测错误原文 The hear_about_us field is required.);agree_terms 仅云端要求。

11.2 建表单 422:presentation_style/width/size 枚举

合法值实测为 presentation_style ∈ classic/focused、width ∈ centered/full、size ∈ sm/md/lg,传 UI 里见过的其他值会被拒。

11.3 提交成功但列表里没有

POST /api/forms/{slug}/answer 异步落库(Queue=redis,api-worker 消费),实测延迟 5-8 秒;若持续不见,查 DC logs api-worker 与 DC exec -T api php artisan about 的 Queue driver。

11.4 改 .env 不生效

见十、:restart 不加载 env_file 新值,必须 down <svc> + up -d;全栈改动用 DC down && DC up -d。

11.5 重建 api 后 502 Bad Gateway

nginx 缓存旧上游 IP,DC restart ingress 即恢复(实测闭环,见十、)。

11.6 容器卡在 "Waiting for DB to be ready"

官方已知问题:api/artisan 被 Windows 转成 CRLF 行尾。sed -i 's/\r$//' api/artisan 后重建(官方文档 docker.mdx 排障节)。

11.7 ingress 同时监听 80 和 8161

override 未加 !override 时 ports 为追加合并(实测踩坑),按 4.2 文件形态修正后 DC up -d ingress 重建。

11.8 setup 完成后无法再注册新用户

设计如此:自托管 setup 结束即关闭公开注册(实测 Registration is not allowed.),用管理员邀请;免费版实例上限 2 用户,更多用户需 Enterprise license。

十二、参考链接

OpnForm v2.8.1 Docker Compose 部署指南 - Quzuo