Mobilizon Docker Compose 部署指南
Mobilizon 5.2.4 官方镜像 kaihuri/mobilizon + postgis/postgis:15-3.4 双服务栈在 Ubuntu 24.04/Docker 29.9.2 实测部署:entrypoint 自动迁移、.env 全键表与密钥生成、GraphQL 建活动/加入端到端、nodeinfo/webfinger/AS 对象联邦验证、docker stats 资源实测、升级备份与 7 条踩坑 FAQ(实测日期 2026-11-02)
Mobilizon Docker Compose 部署指南
目标:在一台干净的 Ubuntu 24.04 服务器上,用 Docker Compose 从零部署 Mobilizon 5.2.4(当前最新稳定版)双服务栈(mobilizon + PostgreSQL/PostGIS),完成建管理员、建群组、发布活动、成员加入与联邦端点验证的端到端闭环。全部命令与回显均来自实测环境(Ubuntu 24.04.5 LTS / x86_64 / Docker 29.9.2 / Docker Compose v5.6.1,实测日期 2026-11-02)。组件功能与选型评估见站内文章 《Mobilizon 介绍与选型指南》。
⚠️ 验收方式说明:本次实测环境无浏览器自动化工具可用,前端页面验收降级为「HTTP 状态码 + 页面
<title>+ GraphQL API 实测 + 联邦端点取证」组合验证,未做像素级 UI 走查;功能链路(登录/建组/建活动/加入/AS 对象)均通过 API 实测闭环。⚠️ 镜像纠偏(最重要的一条):不要拉
framasoft/mobilizon——该 Docker Hub 镜像已废弃(实测镜像加速源上其所有 tag 均 404),现行官方镜像为kaihuri/mobilizon。另外 Mobilizon 不需要 Redis,数据库只有 PostgreSQL+PostGIS(后台任务由 Oban 跑在 PG 上)。
一、环境信息
| 项目 | 值 |
|---|---|
| 操作系统 | Ubuntu 24.04.5 LTS(x86_64) |
| Docker 引擎 | 29.9.2 |
| Docker Compose | v5.6.1 |
| 内存 | 15GiB(部署后可用仍 >7GiB) |
| 应用镜像 | kaihuri/mobilizon:5.2.4-amd64(2.16GB;arm64 用 5.2.4-arm64) |
| 数据库镜像 | postgis/postgis:15-3.4(844MB;arm64 官方文档建议换 nickblah/postgis) |
| 端口 | 本实例宿主 8160 → 容器 4000(Mobilizon 默认监听容器内 4000) |
| 镜像源 | 本机 Docker daemon 配置了 registry mirror(如 docker.1ms.run);hub.docker.com 直连不可达时按同 tag 换前缀拉取 |
版本核对:docker.1ms.run/v2/kaihuri/mobilizon/tags/list 实测可用 tag 含 5.2.4 / 5.2.4-amd64 / 5.2.4-arm64 / 5.2.5-RC.1 / latest——生产选 5.2.4,5.2.5-RC.1 为预发布不建议。
二、官方 compose 仓库与本文重建说明
官方文档给出的标准路径是克隆 framagit.org/kaihuri/mobilizon-docker 仓库,cp env.template .env 后直接 docker compose up -d。本批次实测环境 framagit.org 不可达,故按官方 Docker 安装文档 + 镜像内 /etc/mobilizon/config.exs 模板(该模板逐行读取 System.get_env,即 .env 键的权威清单)手工重建 compose,结果与官方仓库等价。有外网条件的读者直接用官方仓库更省事;无外网/镜像源受限环境照本文走。
三、目录与文件
mobilizon/
├── docker-compose.yml
└── .env
3.1 docker-compose.yml(实测可用全文)
services:
db:
image: docker.1ms.run/postgis/postgis:15-3.4
restart: unless-stopped
environment:
POSTGRES_DB: mobilizon
POSTGRES_USER: mobilizon
POSTGRES_PASSWORD: ${MOBILIZON_DATABASE_PASSWORD}
volumes:
- mobilizon-db:/var/lib/postgresql/data
mobilizon:
image: docker.1ms.run/kaihuri/mobilizon:5.2.4-amd64
restart: unless-stopped
ports:
- "8160:4000"
env_file: .env
depends_on:
- db
volumes:
- mobilizon-uploads:/var/lib/mobilizon/uploads
volumes:
mobilizon-db:
mobilizon-uploads:
要点:
- 镜像 ENTRYPOINT 为
./docker-entrypoint.sh,逻辑实测为:循环pg_isready等库就绪 →CREATE EXTENSION IF NOT EXISTS pg_trgm/unaccent→mobilizon_ctl migrate→exec mobilizon start。首次启动自动建扩展+跑迁移,无需手工初始化。 - uploads 卷必须持久化(图片/导出文件),与 PG 数据卷构成全部状态。
- 生产环境应把
ports换成反向代理(镜像内/support/nginx、/support/apache有官方模板),并设MOBILIZON_INSTANCE_PORT=4000保持容器内监听。
3.2 .env(实测全文,密钥已脱敏示意)
MOBILIZON_INSTANCE_HOST=mobilizon.localhost # 生产改为你的域名
MOBILIZON_INSTANCE_PORT=4000
MOBILIZON_INSTANCE_NAME=G25 Mobilizon Test
MOBILIZON_INSTANCE_DESCRIPTION=Mobilizon 5.2.4 取证实例
MOBILIZON_INSTANCE_EMAIL=noreply@mobilizon.localhost
MOBILIZON_REPLY_EMAIL=noreply@mobilizon.localhost
MOBILIZON_INSTANCE_REGISTRATIONS_OPEN=true
MOBILIZON_LOGLEVEL=info
MOBILIZON_DATABASE_HOST=db
MOBILIZON_DATABASE_PORT=5432
MOBILIZON_DATABASE_DBNAME=mobilizon
MOBILIZON_DATABASE_USERNAME=mobilizon
MOBILIZON_DATABASE_PASSWORD=<随机>
MOBILIZON_DATABASE_SSL=false
MOBILIZON_INSTANCE_SECRET_KEY_BASE=<96hex随机>
MOBILIZON_INSTANCE_SECRET_KEY=<64hex随机>
MOBILIZON_GEOSPATIAL_SERVICE=Nominatim
MOBILIZON_SMTP_SERVER=localhost
MOBILIZON_SMTP_PORT=25
密钥生成:官方文档建议 gpg --gen-random --armor 1 50;无 gpg 时等价命令实测可用:
MOBILIZON_DATABASE_PASSWORD=$(openssl rand -hex 12)
MOBILIZON_INSTANCE_SECRET_KEY_BASE=$(openssl rand -hex 48)
MOBILIZON_INSTANCE_SECRET_KEY=$(openssl rand -hex 32)
全部 MOBILIZON_* 环境键的权威清单来自镜像内 /etc/mobilizon/config.exs(263 行)的 System.get_env 调用;未设置的键走模板内默认值。完整参数语义见《Mobilizon 5.2.4 配置参数详解》。
四、启动与迁移观察
docker compose up -d
docker compose logs -f mobilizon # 观察首启
实测日志序列(关键行):
Running Mobilizon.Web.Endpoint with Bandit 1.12.0 at 0.0.0.0:4000 (http)
Access Mobilizon.Web.Endpoint at https://mobilizon.localhost
- 迁移自动逐条执行(
== Migrated 20250911120910 … 20251111093015),Oban 提示peer became leader;tzdata 自动下载 2026f。 GeoLite2-City.mmdb 缺失仅 WARNING(GeoIP 可选功能),不影响启动。- 踩坑实录:首次
up -d因宿主端口 8159 被并发服务占用报bind: address already in use,改8160:4000后成功——多实例共存机器先用ss -ltnp | grep <端口>排查。 - PG 扩展实测(
docker compose exec db psql -U mobilizon -d mobilizon -c "\dx"):plpgsql, postgis, postgis_topology, postgis_tiger_geocoder, fuzzystrmatch, pg_trgm, unaccent——与 FAQ 要求一致。 - 资源实测
docker stats:mobilizon 353MiB、db 173MiB,合计约 526MiB,印证 FAQ"512MB 可跑"口径。
五、功能验收(HTTP + CLI + GraphQL 实测)
5.1 页面与联邦端点
curl -s http://127.0.0.1:8160/ | grep '<title>'
# → <title>G25 Mobilizon Test - Mobilizon</title>(SPA 壳 2499B)
curl -s http://127.0.0.1:8160/.well-known/nodeinfo/2.0
# → software.name=Mobilizon, software.version=5.2.4,
# protocols=[activitypub], openRegistrations=true,
# services.outbound=[atom1.0], usage.users.total=0
5.2 创建管理员与群组(CLI,容器内工具为 mobilizon_ctl)
docker compose exec mobilizon mobilizon_ctl users.new admin@mobilizon.localhost \
--admin --profile-username g25admin --profile-display-name G25Admin \
--password 'YourP@ss1'
docker compose exec mobilizon mobilizon_ctl actors.new --type group \
--username g25group --display-name G25Group --group-admin g25admin
- 两条均实测成功;
users.show回显status: Activated / Role: administrator / @g25admin。 - 坑:
--profile-display-name "带空格 名字"经 mobilizon_ctl 参数转发后被 OptionParser 拆散报 MatchError(实测复现);用无空格名或确保引号穿透到容器内。
5.3 GraphQL 端到端(POST /api)
# 登录取 JWT
curl -s http://127.0.0.1:8160/api -H 'Content-Type: application/json' \
-d '{"query":"mutation{login(email:\"admin@mobilizon.localhost\",password:\"YourP@ss1\"){accessToken refreshToken}}"}'
# → accessToken/refreshToken(JWT sub "User:1")
# 建活动(必须带 organizerActorId,且以群组名义发布需群组权限)
# createEvent 成功返回:id=1, uuid=0e9dfbac-…, status=CONFIRMED, joinOptions=FREE
实测细节:
- 分页字段是
total/elements(不是content/totalElements)。 createEvent用attributedToId指向个人 actor 会被拒("Organizer profile doesn't have permission to create an event on behalf of this group")——先actors.new --type group建组、以组为 attributedTo。- 第二用户
joinEvent(eventId:1, actorId:<id>)实测返回role:PARTICIPANT;管理侧查询event{participants{total}}→ total=2(CREATOR+PARTICIPANT),ParticipantStats含 going/participant/notConfirmed/rejected 等计数。
5.4 联邦对象验证
curl -s "http://127.0.0.1:8160/.well-known/webfinger?resource=acct:g25admin@mobilizon.localhost"
# → subject/aliases/self + ostatus subscribe template /interact?uri={uri}
curl -s -H 'Accept: application/activity+json' http://127.0.0.1:8160/events/<uuid>
# → 标准 AS Event 对象,attributedTo=https://mobilizon.localhost/@g25group
curl -s -H 'Accept: application/activity+json' http://127.0.0.1:8160/@g25admin
# → Person actor(publicKey/outbox);outbox @context 含 schema.org、mz:、joinpeertube.org/ns
六、升级、备份与日常运维
| 操作 | 命令 | 实测/出处 |
|---|---|---|
| 升级 | docker compose pull && docker compose up -d | 官方 Docker 页;entrypoint 自动跑新迁移 |
| 备份 | docker compose exec db pg_dump -U mobilizon mobilizon > mz.sql + 打包 config.exs + uploads 卷 | FAQ 三件套 |
| PG 大版本升级 | 必须 pg_dump → 换镜像 → pg_restore(不能只改 tag) | FAQ 明确 |
| 手工迁移/回滚 | docker compose exec mobilizon mobilizon_ctl migrate / rollback | help 实测 |
| 孤儿媒体清理 | docker compose exec mobilizon mobilizon_ctl media.cleanorphan | 5.2.4 实测子命令(文档写 clean_orphan,以实测为准) |
| 进容器调试 | docker compose exec mobilizon bash | — |
| 日志 | docker compose logs -f mobilizon;级别由 MOBILIZON_LOGLEVEL 控制 | — |
高级配置(超出 .env 的参数,如 Oban 队列、CSP、反垃圾):官方路径是 docker cp mobilizon-mobilizon-1:/etc/mobilizon/config.exs ./config.exs,编辑后以卷挂载覆盖同路径并 docker compose up -d 重启。
七、FAQ / 踩坑清单
- 拉不到
framasoft/mobilizon:正常,该镜像已废弃;用kaihuri/mobilizon(见文首纠偏)。 - 要不要装 Redis:不要。依赖只有 PostgreSQL+PostGIS;网上旧教程里的 redis 服务段可删。
- 首启卡在迁移/连库:entrypoint 会等
pg_isready;若 db 容器健康但应用仍报错,检查.env的MOBILIZON_DATABASE_HOST是否为 compose 服务名(本例db)。 mobilizon_ctl参数含空格报错 MatchError:见 5.2 坑注。- 页面能开但地图搜索无结果:
MOBILIZON_GEOSPATIAL_SERVICE默认 Nominatim 需外网可达nominatim.openstreetmap.org;离线环境换自建 Photon/Addok 端点。 - 邮件不发:默认 SMTP
localhost:25是占位,必须配真实MOBILIZON_SMTP_*;可用mobilizon_ctl maintenance.testemails实测发送(该子命令实测存在但文档 CLI 页未列)。 - 端口冲突:容器内固定 4000,宿主映射自选;多实例机器先查
ss -ltnp。
取证说明:本文所有命令、日志行、回显与资源数字来自 2026-11-02 实测部署(kaihuri/mobilizon:5.2.4-amd64 + postgis/postgis:15-3.4,宿主端口 8160);批次结束后测试容器/卷/镜像已清理。配置参数详解与 CLI 详解见同专栏另外两篇。