Loomio 3.14.0 Docker Compose 部署指南
Loomio 3.14.0 官方 compose 形态主机实测部署:版本实查、内网无域名变体(去 nginx-proxy/acme/haraka 加 mailpit)、6 服务起栈回显、建管理员与群组投票全流程、CSRF 双提交与 stance 422 等 3 条排障实录、升级与备份。
Loomio 3.14.0 Docker Compose 部署指南
目标:在 Ubuntu 主机上用 Docker Compose 从零部署 Loomio 3.14.0(含内网无域名变体),走通"版本实查 → 编写 compose → 起栈 → 建管理员 → 建群组/投票/投票"全流程;所有命令与回显均为 2026-11-02 主机实测。认知与选型见 《Loomio 3.14.0 介绍与选型指南》,参数语义见 《Loomio 3.14.0 配置参数详解》,运维命令见 《Loomio 3.14.0 常用 CLI 命令与工具详解》。
一、环境信息
- 操作系统:Ubuntu(主机),Docker 29.9.2 / Docker Compose v5.6.1
- 主机资源:6 核 CPU / 15 GiB 内存(部署时可用 9.5 GiB)
- 主机地址:
192.168.1.172(内网 IP 直访,无域名、无 TLS) - 端口规划:
8144=Web(app:3000)、8145=hocuspocus 协同编辑(容器 80)、8146=mailpit Web UI(8025);SMTP 1025 仅内部网络 - 内部网络:
loomio_main,子网10.89.68.0/24 - 组件版本(
docker compose images与bin/rails about实测):
| 组件 | 版本 | 说明 |
|---|---|---|
| loomio/loomio | 3.14.0(镜像 ID d2db5b238845,3.48 GB) | app/worker/hocuspocus 三服务共用 |
| postgres | 17(PostgreSQL 17.11) | 数据库 |
| redis | 8.4(redis_version:8.4.8) | 缓存与 Action Cable |
| axllent/mailpit | v1 | 内网变体新增的邮件捕获服务 |
| Rails | 8.1.3.1 | 应用框架 |
| Ruby | 4.0.9 | 语言运行时 |
| Puma | 8.0.2 | Web 服务器 |
| Solid Queue | 1.7.0 | 后台任务队列 |
💡 技术拓展:Loomio 官方发行形态只有 Docker Compose(仓库
deploy/目录),没有独立二进制或 apt 包。同类自托管协作软件常见发行形态对比——
形态 代表 特点 Docker Compose 全家桶 Loomio、Rallly 应用+DB+缓存一键起栈,官方唯一支持路径 单镜像 + 外部依赖 Bitpoll、IHateMoney 只发应用镜像,DB 自备 裸机包安装 Framadate(PHP) 依赖现成 LAMP 环境 本文按官方唯一的 Compose 形态部署,并给出内网无域名变体。
二、镜像选型说明
2.1 版本实查
以 GitHub Releases API 查询最新稳定版(2026-11-02):
curl -s https://api.github.com/repos/loomio/loomio/releases/latest | python3 -c \
"import json,sys;d=json.load(sys.stdin);print(d['tag_name'],d['published_at'],d['html_url'])"
实测输出:
v3.14.0 2026-11-01T02:08:42Z https://github.com/loomio/loomio/releases/tag/v3.14.0
近期发布序列 3.13.2 / 3.13.1 / 3.13.0 / 3.12.0,节奏约每 1–2 周一版;仓库 AGPL-3.0,stars 2624 / forks 711(2026-11-02 实查)。
2.2 镜像与 tag 核对
Docker Hub 镜像 loomio/loomio 存在 3.14.0 tag(与 latest/master 同日构建)。注意:官方 env_template 默认 LOOMIO_CONTAINER_TAG=3.5,明显滞后于当前版本,实查后必须手动钉到 3.14.0。
docker compose images
实测输出(节选,实测首尾):
CONTAINER REPOSITORY TAG IMAGE ID SIZE
loomio-app loomio/loomio 3.14.0 d2db5b238845 3.48GB
loomio-db postgres 17 3b0cda93be7c 640MB
loomio-redis redis 8.4 5d4cea9e22db 202MB
loomio-mailpit axllent/mailpit v1 0790692be935 51.1MB
loomio/loomio:3.14.0 单镜像 3.48 GB(内含 Rails 应用、Node 协同服务与前端资产),是本组件最大的磁盘开销项;app、worker、hocuspocus 三个服务共用同一镜像,只占一份磁盘。
三、前置条件
3.1 主机要求
- Docker 20.10+ 与 Compose v2+(实测 29.9.2 / v5.6.1)
- 空闲端口:Web 端口(本文 8144)、协同编辑端口(8145)、邮件 UI(8146);若走官方 HTTPS 形态则需 80/443 空闲
- 磁盘 ≥ 15 GB(镜像 3.48 GB + 数据卷 + 升级冗余);内存 ≥ 2 GB(实测全栈稳态约 1.1 GB)
3.2 官方部署资产获取
官方 compose 与配套脚本位于 loomio/loomio 仓库 deploy/ 目录(GitHub API contents 实查,2026-11-02):docker-compose.yml、env_template、create_env.sh、update.sh、UPGRADING.md。
mkdir -p /opt/loomio && cd /opt/loomio
git clone --depth 1 https://github.com/loomio/loomio loomio-src
cp -r loomio-src/deploy ./ && cd deploy
3.3 create_env.sh 的 FQDN 限制
官方 create_env.sh 用镜像内 Ruby 生成 .env,对主机名做 FQDN 校验(源码实测读取):
abort "hostname must be a fully qualified domain name" if hostname.length > 253 || !hostname.include?(".")
即参数必须是含点的域名(如 loomio.example.com),192.168.1.172:8144 这类 IP:端口直接被拒绝。脚本还会生成 POSTGRES_PASSWORD、SECRET_COOKIE_TOKEN、RAILS_INBOUND_EMAIL_PASSWORD、SERVER_API_KEY(各 32 字节 hex)与 VAPID 推送密钥对。内网无域名场景必须手写 .env(见 4.2)。
⚠️ 注意:
SERVER_API_KEY必须 ≥17 字符,否则应用拒绝启动(env_template 注释原文:"It must be at least 17 characters; Loomio will not start with a shorter key")。手写时用openssl rand -hex 32生成。
四、部署步骤
4.1 官方 compose 形态与本方案变体
官方 deploy/docker-compose.yml 共 8 服务:nginx-proxy / nginx-proxy-acme(自动签 Let's Encrypt 证书)/ app / worker / db / redis / haraka(收信 SMTP)/ hocuspocus(Yjs 协同编辑),app 仅 expose: 3000 由 nginx-proxy 反代。
内网无域名、无公网证书场景按下表裁剪(本文实测变体,6 服务):
| 官方服务 | 变体处理 | 理由 |
|---|---|---|
| nginx-proxy / nginx-proxy-acme | 删除,app 直接 ports: "8144:3000" | 无域名无法签 LE 证书 |
| haraka | 删除 | 不需要邮件收件回帖 |
| hocuspocus | 保留,ports: "8145:80" | 协同编辑必需 |
| app/worker/db/redis | 保留 | 核心四件套 |
| mailpit | 新增 axllent/mailpit:v1,ports: "8146:8025" | 内网捕获发信,验证 SMTP 链路 |
4.2 编写 .env
完整 .env(密钥值均为本次实验实值,生产环境请全部重生成):
# 关键密钥生成方式
openssl rand -hex 32 # SERVER_API_KEY / SECRET_COOKIE_TOKEN / RAILS_INBOUND_EMAIL_PASSWORD
# /opt/loomio/deploy/.env(内网无域名部署)
POSTGRES_CONTAINER_TAG=17
LOOMIO_CONTAINER_IMAGE=loomio/loomio
LOOMIO_CONTAINER_TAG=3.14.0
SITE_NAME=Loomio
CANONICAL_HOST=192.168.1.172
CANONICAL_PORT=8144
PUBLIC_APP_URL=http://192.168.1.172:8144
PRIVATE_APP_URL=http://app:3000
SUPPORT_EMAIL=admin@loomio.local
HOCUSPOCUS_URL=ws://192.168.1.172:8145
RAILS_INBOUND_EMAIL_URL=http://app:3000/rails/action_mailbox/relay/inbound_emails
SMTP_DOMAIN=mailpit
SMTP_SERVER=mailpit
SMTP_PORT=1025
WEB_CONCURRENCY=2
MIN_THREADS=3
MAX_THREADS=3
# FORCE_SSL=1 # 无 TLS 场景必须整行注释(设 0 无效)
USE_RACK_ATTACK=1
RACK_ATTACK_RATE_MULTPLIER=1
RACK_ATTACK_TIME_MULTPLIER=1
POSTGRES_PASSWORD=q1w2e3r4
POSTGRES_DB=loomio_production
DATABASE_URL=postgresql://postgres:q1w2e3r4@db/loomio_production
REDIS_CACHE_URL=redis://redis:6379/0
REDIS_MAXMEMORY=256mb
ACTIVE_STORAGE_SERVICE=local
LOOMIO_SYSTEM_RELOAD=1
LOOMIO_DISABLE_DEMO_GROUPS=1
SERVER_API_KEY=e1af7a533f349783e691a57bfef50c592af9994f9dbd2ba45b0a710c764fe034
SECRET_COOKIE_TOKEN=aed935f4d492c2014b1dead298b72e1b04675b55962c4424320a45a59f4f9a75
RAILS_INBOUND_EMAIL_PASSWORD=47600e7b9a55d8d9645b23f9e622c13ba3d762a2e1b5cba98a5649612dbdcf7f
RAILS_ENV=production
逐项语义见 《Loomio 3.14.0 配置参数详解》。最关键的书写规则在 env_template 首行原文:要禁用某项必须注释/删除整行,设 KEY=0 仍是启用态。
4.3 编写 docker-compose.yml
变体 compose 全文(/opt/loomio/deploy/docker-compose.yml):
name: loomio
services:
app:
container_name: loomio-app
image: ${LOOMIO_CONTAINER_IMAGE}:${LOOMIO_CONTAINER_TAG}
restart: unless-stopped
healthcheck:
test: ['CMD-SHELL', 'curl --fail http://localhost:3000/ || exit 1']
ports:
- "8144:3000"
env_file: ./.env
volumes:
- ./uploads:/loomio/public/system
- ./storage:/loomio/storage
- ./files:/loomio/public/files
- ./plugins:/loomio/plugins/docker
- ./import:/import
- ./tmp:/loomio/tmp
- client3:/loomio/public/client3
networks:
- main
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
worker:
container_name: loomio-worker
image: ${LOOMIO_CONTAINER_IMAGE}:${LOOMIO_CONTAINER_TAG}
restart: always
networks:
- main
env_file: ./.env
environment:
- TASK=worker
depends_on:
app:
condition: service_healthy
volumes:
- ./uploads:/loomio/public/system
- ./storage:/loomio/storage
- ./files:/loomio/public/files
- ./plugins:/loomio/plugins/docker
- ./tmp:/loomio/tmp
db:
container_name: loomio-db
image: postgres:${POSTGRES_CONTAINER_TAG}
restart: unless-stopped
healthcheck:
test: ['CMD', 'pg_isready', '-U', 'postgres']
networks:
- main
env_file: ./.env
volumes:
- ./pgdata:/pgdata
environment:
- PGDATA=/pgdata
redis:
container_name: loomio-redis
image: redis:8.4
restart: unless-stopped
command:
- redis-server
- --save
- ""
- --appendonly
- "no"
- --maxmemory
- "${REDIS_MAXMEMORY:-256mb}"
- --maxmemory-policy
- allkeys-lru
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
networks:
- main
hocuspocus:
container_name: loomio-hocuspocus
image: ${LOOMIO_CONTAINER_IMAGE}:${LOOMIO_CONTAINER_TAG}
restart: unless-stopped
networks:
- main
env_file: ./.env
ports:
- "8145:80"
environment:
- TASK=hocuspocus
mailpit:
container_name: loomio-mailpit
image: axllent/mailpit:v1
restart: unless-stopped
networks:
- main
ports:
- "8146:8025"
volumes:
client3:
networks:
main:
ipam:
config:
- subnet: 10.89.68.0/24
要点解读:
app的 7 个卷把上传件、附件存储、前端资产全部落到宿主机(client3是唯一例外,用命名卷,原因见排障实录 1);worker复用同一组卷以便处理附件类任务。worker与hocuspocus靠环境变量TASK切换启动形态(镜像入口docker_start.sh按$TASK分支 exec),与官方设计一致。redis关闭持久化(--save "" --appendonly no):官方定位 Redis 仅做缓存与 Action Cable,丢了可重建。db的PGDATA=/pgdata配合 bind mount./pgdata,数据直接落宿主机目录。
⚠️ 注意(排障实录 1):初稿删掉官方 compose 顶部
volumes: client3:声明块时,docker compose up直接报错:service "app" refers to undefined volume client3: invalid compose projectapp 服务引用了命名卷
client3,就必须在顶层volumes:声明它——删服务可以连带删卷声明,删卷声明不能留引用。补上后恢复。
4.4 起栈与健康检查
cd /opt/loomio/deploy
docker compose up -d
等待约 2 分钟后实测 docker compose ps:
loomio-app Up 8 minutes (healthy) 80/tcp, 0.0.0.0:8144->3000/tcp
loomio-db Up 9 minutes (healthy) 5432/tcp
loomio-hocuspocus Up 9 minutes 0.0.0.0:8145->80/tcp
loomio-mailpit Up 26 minutes (healthy) 1025/tcp, 0.0.0.0:8146->8025/tcp
loomio-redis Up 26 minutes (healthy) 6379/tcp
loomio-worker Up 8 minutes 80/tcp
首次启动时 app 入口脚本自动执行 script/sync_client_assets 与 bundle exec rake db:prepare(见 docker_start.sh 源码,4.5 节),无需手工建库。
验证 Web 与协同服务:
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' http://192.168.1.172:8144/
curl -s http://192.168.1.172:8144/up
curl -s -o /dev/null -w '%{http_code}\n' http://192.168.1.172:8145/
实测输出:
302 http://192.168.1.172:8144/dashboard
ok
200
根路径 302 跳 /dashboard、健康检查 /up 返回 ok、hocuspocus 端口 200,栈即起净。
4.5 数据库初始化确认
db:prepare 在 app 启动入口自动执行。实测建表数:
docker compose exec db psql -U postgres -d loomio_production -tAc \
"select count(*) from information_schema.tables where table_schema='public'"
80
⚠️ 注意:若手工跑
rake db:setup,会提示Database 'loomio_production' already exists - 跳过——因为POSTGRES_DB=loomio_production已由 postgres 镜像初始化时创建,属正常现象,schema 仍会载入。官方推荐入口用的就是幂等的db:prepare。
4.6 创建管理员账号
Loomio 首次启动没有任何用户,注册入口虽开放,但演示/管理账号用 rails runner 直建最可控:
docker compose exec app bin/rails runner '
u = User.new(name: "admin", username: "admin_lmo", email: "admin@loomio.local")
u.password = "q1w2e3r4"
puts u.save
puts u.errors.full_messages.inspect'
实测输出(弱口令被拒):
false
["Password This password has previously appeared in a data breach and should never be used"]
⚠️ 注意:Loomio 内置泄露密码库校验,固定测试口令
q1w2e3r4会被直接拒绝,这是与其他组件演示账号规范不同的实测点。演示账号改用叠加后缀的强口令(本次实测环境为admin/admin@loomio.local/q1w2e3r4!Loomio):
docker compose exec app bin/rails runner '
u = User.new(name: "admin", username: "admin_lmo", email: "admin@loomio.local")
u.password = "q1w2e3r4!Loomio"
u.is_admin = true
u.email_verified = true
puts u.save!'
创建成功(实测终态核验):
docker compose exec db psql -U postgres -d loomio_production -tAc \
"select id,name,username,email,is_admin,email_verified from users"
1|Admin|admin_lmo|admin@loomio.local|t|t
4.7 登录验证(CSRF 双提交)
Loomio 的 JSON API 走浏览器会话时受 Rails CSRF 保护,POST 必须携带与 csrftoken cookie 同值的 X-CSRF-Token 头:
curl -s -c lk.txt http://192.168.1.172:8144/dashboard -o /dev/null
TOK=$(grep csrftoken lk.txt | awk '{print $7}')
echo "cookie_len=${#TOK}"
curl -s -b lk.txt -H "Content-Type: application/json" -H "X-CSRF-Token: $TOK" \
-X POST http://192.168.1.172:8144/api/v1/sessions \
-d '{"user":{"email":"admin@loomio.local","password":"q1w2e3r4!Loomio"}}' | python3 -c \
"import json,sys;d=json.load(sys.stdin);print('current_user_id=',d['current_user_id'],'user=',d['users'][0]['username'],'flash=',d['flash']['notice'])"
实测输出:
cookie_len=86
current_user_id= 1 user= admin_lmo flash= Signed in successfully
⚠️ 注意(排障实录 2):漏带或错值
X-CSRF-Token时,所有 POST/PATCH 返回 422 且响应体只有{"status":422},看不到任何原因;必须翻 app 日志才能看到ActionController::InvalidAuthenticityToken。这是本文档实测踩过的最隐蔽坑。
4.8 建群组、发投票、投第一票
登录会话内依次调用(均带 X-CSRF-Token 头,载荷为实测成功形态):
# 1) 建群组
curl -s -b lk.txt -H "Content-Type: application/json" -H "X-CSRF-Token: $TOK" \
-X POST http://192.168.1.172:8144/api/v1/groups -d '{"group":{
"name":"决策实验组","handle":"demo-group","group_privacy":"open",
"membership_granted_upon":"request","description_format":"md"}}'
HTTP 200,响应 groups[0]:id=1、handle=demo-group、name=决策实验组、group_privacy=open(群组字段是 name 而非 title)。
# 2) 发 proposal 投票(一次到位的正确载荷)
curl -s -b lk.txt -H "Content-Type: application/json" -H "X-CSRF-Token: $TOK" \
-X POST http://192.168.1.172:8144/api/v1/polls -d '{"poll":{
"group_id":1,"title":"是否采用弹性加班制度","details":"试行三个月后复盘",
"details_format":"md","poll_type":"proposal",
"options":["agree","abstain","disagree","block"],
"closing_at":"2026-11-09T07:00:00Z"}}'
HTTP 200,polls[0]:id=1、key=9bvixmJ0、poll_type=proposal;opened_at 自动置位(closing_at 有值且未设 opening_at 即立即开放);4 个选项自动生成。
# 3) 投票(proposal 的 score 必须为 1)
curl -s -b lk.txt -H "Content-Type: application/json" -H "X-CSRF-Token: $TOK" \
-X POST http://192.168.1.172:8144/api/v1/stances -d '{"stance":{
"poll_id":1,"reason":"同意试行,建议先跑三个月","reason_format":"md",
"stance_choices_attributes":[{"score":1,"poll_option_id":1}]}}'
HTTP 200,stances[0]:id=1、cast_at 置位、option_scores={"1":1}。
终态核验(psql 实测):
docker compose exec db psql -U postgres -d loomio_production -tAc \
"select id,key,title,poll_type,opened_at,closing_at from polls"
docker compose exec db psql -U postgres -d loomio_production -tAc \
"select id,name from poll_options order by id"
docker compose exec db psql -U postgres -d loomio_production -tAc \
"select id,poll_id,option_scores,reason,cast_at from stances"
1|9bvixmJ0|是否采用弹性加班制度|proposal|2026-11-02 07:08:09.41894|2026-11-09 07:00:00
1|Agree
2|Abstain
3|Disagree
4|Block
1|1|{"1": 1}|同意试行,建议先跑三个月|2026-11-02 07:08:18.4367
⚠️ 注意(排障实录 3):建投票时若不带
poll_type,后续投票必 422。根因链(源码实测):poll_type缺省 → 模板值不加载 →ballot_rule=nil→ValidatesBallot的ballot_policy_invalid?走 else 分支报"stance_choices is not a valid value"。proposal 的 defaults 为score_min=score_max=1、choice_count 1..1,所以 score 必须传 1。
4.9 邮件链路验证(mailpit)
SMTP 指向内置 mailpit(mailpit:1025,无认证无 TLS)。验证捕获服务在线:
curl -s http://192.168.1.172:8146/api/v1/messages | head -c 120
{"total":0,"unread":0,"count":0,"messages_count":0,"messages_unread":0,"start":0,"tags":[],"messages":[]}
单用户实例无通知对象,0 封属正常;邀请成员、投票截止提醒等邮件都会在此可见(Web UI 端口 8146)。
4.10 演示群组脏数据与库重置
新库首次建群组时实测被自动塞入 3 个假用户与 24 个演示投票——源码 app/extras/app_config.rb:203 的 demo_groups_enabled? 只要 CANONICAL_HOST 有值默认启用演示群组。处置:.env 增加 LOOMIO_DISABLE_DEMO_GROUPS=1 后重置数据库:
docker compose stop app worker hocuspocus
docker compose exec db psql -U postgres -c \
"DROP DATABASE IF EXISTS loomio_production WITH (FORCE);" -c \
"CREATE DATABASE loomio_production;"
docker compose up -d # 入口 db:prepare 自动载 schema,80 表、0 用户
重置后核验:users=0、群组数 0,再走 4.6–4.8 全流程即得干净终态(1 user / 1 group / 1 poll / 1 stance / 4 options)。
五、数据持久化说明
| 宿主路径 | 挂载点 | 内容 | 备份优先级 |
|---|---|---|---|
| ./pgdata | /pgdata | PostgreSQL 数据目录 | 最高 |
| ./uploads | /loomio/public/system | 旧式上传件 | 高 |
| ./storage | /loomio/storage | Active Storage 本地附件(ACTIVE_STORAGE_SERVICE=local) | 高 |
| ./files | /loomio/public/files | 主题 logo 等静态文件 | 中 |
| ./import | /import | 数据导入目录 | 中 |
| ./tmp | /loomio/tmp | 运行时临时文件 | 低 |
| 命名卷 loomio_client3 | /loomio/public/client3 | 前端构建资产(镜像内同步而来,可再生) | 低 |
client3 用命名卷而非 bind mount:入口脚本 script/sync_client_assets 每次启动把镜像内新资产合并进卷("New files win"),升级时自动滚动。
六、环境变量表
.env 关键项与实测值(完整语义见 《Loomio 3.14.0 配置参数详解》):
| 变量 | 实测值 | 作用 |
|---|---|---|
| LOOMIO_CONTAINER_TAG | 3.14.0 | 钉版本(默认 3.5 滞后) |
| CANONICAL_HOST / CANONICAL_PORT | 192.168.1.172 / 8144 | 应用生成 URL 的主机与端口 |
| PUBLIC_APP_URL / PRIVATE_APP_URL | http://192.168.1.172:8144 / http://app:3000 | 浏览器侧 / 内部侧基址 |
| HOCUSPOCUS_URL | ws://192.168.1.172:8145 | 协同编辑 WebSocket |
| SMTP_SERVER / SMTP_PORT | mailpit / 1025 | 发信出口 |
| WEB_CONCURRENCY / MAX_THREADS | 2 / 3 | Puma 并发(官方 auto=CPU 数) |
| FORCE_SSL | 整行注释 | 无 TLS 必须注释而非置 0 |
| USE_RACK_ATTACK | 1 | 限速中间件开关 |
| POSTGRES_PASSWORD / DATABASE_URL | q1w2e3r4 / postgresql://postgres:q1w2e3r4@db/loomio_production | 库连接 |
| REDIS_CACHE_URL / REDIS_MAXMEMORY | redis://redis:6379/0 / 256mb | 缓存 |
| ACTIVE_STORAGE_SERVICE | local | 附件本地盘 |
| LOOMIO_DISABLE_DEMO_GROUPS | 1 | 禁演示群组 |
| LOOMIO_SYSTEM_RELOAD | 1 | 升级后通知客户端重载 |
| SERVER_API_KEY | openssl rand -hex 32 | Server API Bearer 密钥(≥17 字符) |
| SECRET_COOKIE_TOKEN / RAILS_INBOUND_EMAIL_PASSWORD | openssl rand -hex 32 | 会话签名 / 收信 relay 口令 |
七、配置参数说明
本变体与官方形态的三处结构性差异,均已在 4.1–4.3 落地:
- 无 nginx-proxy:app 直接发布
8144:3000,官方 compose 里 app 是expose: 3000且带VIRTUAL_HOST/ACME_HOST标签,变体全部删除。 - 无 haraka:收信链路(邮件回帖、bounce 处理)整体不启用,
RAILS_INBOUND_EMAIL_URL保留默认值无害。 - SMTP 无认证:
SMTP_SERVER=mailpit且未设SMTP_USERNAME/SMTP_PASSWORD/SMTP_USE_SSL——注意SMTP_USE_SSL同样必须整行删除,置空串仍算启用。
八、配置文件挂载
Loomio 无独立应用配置文件,全部配置经 .env 注入(compose 的 env_file: ./.env 同时给 app/worker/db/hocuspocus)。官方形态另有一个 nginx 层配置:configs: nginx-custom → /etc/nginx/conf.d/custom.conf(设置 client_max_body_size,配合 FILE_UPLOAD_MAX_MB),变体删了 nginx-proxy 即不需要。主题静态文件放 ./files(对应 THEME_*_SRC 变量引用的 /files/ 前缀路径)。
九、等价 docker run
compose 六服务对应的单命令等价形态(网络/卷先行创建;仅演示依赖关系,生产建议仍用 compose):
docker network create --subnet 10.89.68.0/24 loomio_main
docker volume create loomio_client3
docker run -d --name loomio-db --network loomio_main \
-e POSTGRES_PASSWORD=q1w2e3r4 -e POSTGRES_DB=loomio_production -e PGDATA=/pgdata \
-v /opt/loomio/deploy/pgdata:/pgdata postgres:17
docker run -d --name loomio-redis --network loomio_main \
redis:8.4 redis-server --save "" --appendonly no --maxmemory 256mb --maxmemory-policy allkeys-lru
docker run -d --name loomio-app --network loomio_main --env-file /opt/loomio/deploy/.env \
-p 8144:3000 \
-v /opt/loomio/deploy/uploads:/loomio/public/system \
-v /opt/loomio/deploy/storage:/loomio/storage \
-v /opt/loomio/deploy/files:/loomio/public/files \
-v /opt/loomio/deploy/plugins:/loomio/plugins/docker \
-v /opt/loomio/deploy/import:/import \
-v /opt/loomio/deploy/tmp:/loomio/tmp \
-v loomio_client3:/loomio/public/client3 \
loomio/loomio:3.14.0
docker run -d --name loomio-worker --network loomio_main --env-file /opt/loomio/deploy/.env \
-e TASK=worker \
-v /opt/loomio/deploy/uploads:/loomio/public/system \
-v /opt/loomio/deploy/storage:/loomio/storage \
-v /opt/loomio/deploy/files:/loomio/public/files \
-v /opt/loomio/deploy/plugins:/loomio/plugins/docker \
-v /opt/loomio/deploy/tmp:/loomio/tmp \
loomio/loomio:3.14.0
docker run -d --name loomio-hocuspocus --network loomio_main --env-file /opt/loomio/deploy/.env \
-e TASK=hocuspocus -p 8145:80 loomio/loomio:3.14.0
十、常用运维命令
docker compose ps # 服务状态
docker compose logs -f app # 应用日志
docker compose logs worker | grep -i recurring # 定时任务流水
docker compose restart app # 改 .env 后重启生效
docker compose exec app bin/jobs check # 队列健康(见 D1 篇)
docker compose exec db pg_dump -U postgres loomio_production > backup_$(date +%F).sql
docker compose down # 停止(数据保留);加 -v 才会删命名卷
升级按官方 deploy/update.sh(原文全文):
#!/bin/bash
docker system prune -a -f && docker compose pull && docker compose down && \
docker compose run --rm app rake db:migrate && docker compose down && \
docker compose up -d --remove-orphans
即"清旧镜像 → 拉新 → 停栈 → 一次性跑迁移 → 起新栈";LOOMIO_SYSTEM_RELOAD=1 会让浏览器端收到 boot/version 的 reload:true 提示自动重载。
十一、常见问题
Q1:起栈报 service "app" refers to undefined volume client3: invalid compose project?
删了顶层 volumes: client3: 声明但 app 仍引用该卷。补回声明即可(见 4.3 排障实录 1)。
Q2:API POST 一律 422,响应体只有 {"status":422}?
漏带 X-CSRF-Token(须与 csrftoken cookie 同值)。看 app 日志确认 InvalidAuthenticityToken(见 4.7 排障实录 2)。若走 Server API(/api/server0/* + Bearer)则免 CSRF,见 《Loomio 3.14.0 常用 CLI 命令与工具详解》。
Q3:投票 422 报 stance_choices is not a valid value?
建 poll 时没带 poll_type,ballot_rule 为空;proposal 的 score 必须为 1(见 4.8 排障实录 3)。
Q4:新群组里莫名多出 3 个假用户和一堆演示投票?
演示群组默认启用。.env 加 LOOMIO_DISABLE_DEMO_GROUPS=1 并重置库(见 4.10)。
Q5:create_env.sh 拒绝 192.168.1.172 这种主机名?
它强制 FQDN(含点)。内网场景手写 .env,密钥用 openssl rand -hex 32(见 3.3)。
Q6:FORCE_SSL=0 了还是强制跳 https?
禁用必须注释/删除整行,任何非空值都算启用(env_template 首行警告原文)。
Q7:rake db:setup 提示 Database already exists 是否失败?
不是失败:POSTGRES_DB 已由 postgres 镜像初始化建库,schema 仍正常载入(实测 80 表)。入口用的 db:prepare 天然幂等。
十二、参考链接
- Loomio 仓库与部署资产:https://github.com/loomio/loomio/tree/master/deploy
- Release v3.14.0:https://github.com/loomio/loomio/releases/tag/v3.14.0
- Docker Hub 镜像:https://hub.docker.com/r/loomio/loomio
- 官网:https://loomio.com
- 站内互链:《Loomio 3.14.0 介绍与选型指南》、《Loomio 3.14.0 配置参数详解》、《Loomio 3.14.0 常用 CLI 命令与工具详解》