Pleroma 2.11.0 Docker Compose 部署指南

Ubuntu 24.04 用 Docker Compose 部署 Pleroma 2.11.0 全纪实:官方 registry 制品路线、两服务栈编排、入口脚本迁移实录、Web/Nodeinfo/OAuth 发贴闭环验证,含 Docker Hub 鉴权墙、redirect_uris 复数参数、HTTPS 出厂假设等 7 条排障实录,全部回显 2026-11-02 实测。

PleromaPleromaFediverseElixirDockerDocker ComposePostgreSQL2026/11/02

Pleroma 2.11.0 Docker Compose 部署指南

本文在 Ubuntu 24.04.5 上用 Docker Compose 以官方镜像路线部署 Pleroma 2.11.0(两服务栈:PostgreSQL 15 + Pleroma),从制品获取、栈编排、启动迁移、Web 验证到管理员账号与 Mastodon API 发贴闭环,全部命令与回显为 2026-11-02 实测,含 7 条排障实录。

一、环境与制品信息

1.1 实验环境

项目值
主机 OSUbuntu 24.04.5 LTS(内核 7.0.0-38-generic,x86_64)
Docker29.9.2, build a82e528
Docker Composev5.6.1
组件版本Pleroma 2.11.0(镜像内版本号 2.11.0-develop,stable 分支 2026-10-24 构建)
依赖数据库PostgreSQL 15(官方镜像路线出厂默认对接)
Web 端口127.0.0.1:8148 → 应用内 4000
实测日期2026-11-02

1.2 官方制品路线(先厘清一个常见误区)

Pleroma 官方仓库 README 写着 "While we don't provide docker files",该说法已滞后:官方仓库现有 Dockerfile 与 Woodpecker CI(.woodpecker/docker.yml),每次 stable 分支推送/打 tag 由 kaniko 构建并推送镜像到 git.pleroma.social/pleroma/pleroma 自有 registry,标签体系为 stable/latest/develop + v<版本>-<arch>(amd64/arm64/armv7),实查标签清单见本专栏选型篇。

⚠️ 排障实录 ①:官方镜像不在 Docker Hub。按惯常思路去拉 Docker Hub 的 pleroma/pleroma 会直接撞鉴权墙:

$ curl -s https://registry-1.docker.io/v2/pleroma/pleroma/tags/list
{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"repository","Class":"","Name":"pleroma/pleroma","Action":"pull"}]}]}

正确制品地址是 git.pleroma.social/pleroma/pleroma(匿名可拉):

$ docker pull git.pleroma.social/pleroma/pleroma:stable
stable: Pulling from pleroma/pleroma
Digest: sha256:d2016be89c0466dde7785ab2e4cc82e671216ef7f439b5d7c64ebc85e7fa9a5e
Status: Image is up to date for git.pleroma.social/pleroma/pleroma:stable
git.pleroma.social/pleroma/pleroma:stable

多架构核对(docker manifest inspect 实测,amd64/arm64/arm 三平台,amd64 摘要 1f1ae185...):

$ docker manifest inspect git.pleroma.social/pleroma/pleroma:stable
{
   "schemaVersion": 2,
   "mediaType": "application/vnd.oci.image.index.v1+json",
   "manifests": [
      {
         "mediaType": "application/vnd.oci.image.manifest.v1+json",
         "size": 1526,
         "digest": "sha256:1f1ae185bca8d46419607ae32aa9ba6f71bdb8e61645ccf0402153f3ffe0aec0",
         "platform": {
            "architecture": "amd64",
            "os": "linux"
         }
      },
      ...(arm64 摘要 e0a4c17e...、arm/v7 摘要 5efcc312... 略)
   ]
}

1.3 镜像内部结构(决定后续所有路径与端口)

从镜像 config 与 Dockerfile 实查(docker inspect + 官方仓库 Dockerfile):

  • 运行期系统包仅 6 个:exiftool ffmpeg vips libmagic ncurses postgresql-client——没有 Redis、没有 Elasticsearch,联邦社交依赖链就 PostgreSQL 一环;
  • 发布制品在 /opt/pleroma(HOME),数据目录 /var/lib/pleroma,内置配置 /etc/pleroma/config.exs(即仓库 config/docker.exs);
  • 入口脚本 /opt/pleroma/docker-entrypoint.sh:等库 → pleroma_ctl migrate → exec bin/pleroma start;
  • EXPOSE 4000,出厂监听 0.0.0.0:4000;
  • 出厂 docker.exs 按环境变量组装配置:DOMAIN/INSTANCE_NAME/ADMIN_EMAIL/NOTIFY_EMAIL/DB_HOST/DB_PORT/DB_NAME/DB_USER/DB_PASS,并自动生成 /var/lib/pleroma/secret.exs(Cookie 签名密钥与 VAPID 推送密钥对)。

二、Compose 栈编排

2.1 完整 docker-compose.yml(实测在跑版本)

name: pleroma-2.11.0-stack
services:
  db:
    image: postgres:15
    container_name: pleroma-2.11.0-db
    restart: unless-stopped
    environment:
      POSTGRES_DB: pleroma
      POSTGRES_USER: pleroma
      POSTGRES_PASSWORD: q1w2e3r4
    volumes:
      - ./pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U pleroma -d pleroma"]
      interval: 5s
      timeout: 3s
      retries: 20
    networks: [pleroma-net]

  pleroma:
    image: git.pleroma.social/pleroma/pleroma:stable
    container_name: pleroma-2.11.0-app
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      DOMAIN: localhost
      INSTANCE_NAME: "Pleroma Demo"
      ADMIN_EMAIL: admin@example.com
      NOTIFY_EMAIL: notifications@example.com
      DB_HOST: db
      DB_PORT: "5432"
      DB_NAME: pleroma
      DB_USER: pleroma
      DB_PASS: q1w2e3r4
    ports:
      - "127.0.0.1:8148:4000"
    volumes:
      - ./data:/var/lib/pleroma
    networks: [pleroma-net]

networks:
  pleroma-net:
    driver: bridge
    ipam:
      config:
        - subnet: 10.89.72.0/24

2.2 编排要点解读

  1. name: 显式项目名:多组件共用宿主机时防止 docker compose down 互删;
  2. depends_on + healthcheck:pg_isready 探活 + condition: service_healthy,配合入口脚本自身的 -- Waiting for database... 双保险;
  3. DOMAIN=localhost + 端口回环绑定:实验环境无域名无 TLS,用 127.0.0.1:8148:4000 最小暴露面;生产必须换成真实域名并前置反代(见 2.3);
  4. ./data:/var/lib/pleroma 必须持久化:secret.exs(Cookie 签名密钥)与 config.exs(用户配置)都落在这里,丢了等于实例身份丢失、所有会话失效;
  5. 网络显式 subnet:10.89.72.0/24 为实验环境统一规划槽位,生产可删。

2.3 出厂 HTTPS 假设与本地 HTTP 适配

⚠️ 排障实录 ②:出厂 docker.exs 写死 url: [host: DOMAIN, scheme: "https", port: 443]——官方假设你必然在反代后面。本实验直接 HTTP 端口转发访问时,实例自报的 actor/对象 URL 会全错(https://localhost/...)。适配方法:在持久化的 /var/lib/pleroma/config.exs(出厂自动生成,晚于 docker.exs 导入、优先级更高)里覆盖 Endpoint url:

$ docker exec pleroma-2.11.0-app cat /var/lib/pleroma/config.exs
import Config

# For additional configuration outside of environmental variables
config :pleroma, Pleroma.Web.Endpoint,
  url: [host: "localhost", scheme: "http", port: 8148]

改完重启应用服务生效:docker compose restart pleroma。日志横幅随即变为 Access Pleroma.Web.Endpoint at http://localhost:8148。

三、部署步骤实测

3.1 起栈

$ docker compose up -d
 Network pleroma-2110-stack_pleroma-net Creating
 Network pleroma-2110-stack_pleroma-net Created
 Container pleroma-2.11.0-db Creating
 Container pleroma-2.11.0-db Created
 Container pleroma-2.11.0-app Creating
 Container pleroma-2.11.0-app Created
 Container pleroma-2.11.0-db Starting
 Container pleroma-2.11.0-db Started
 Container pleroma-2.11.0-db Waiting
 Container pleroma-2.11.0-db Healthy
 Container pleroma-2.11.0-app Starting
 Container pleroma-2.11.0-app Started

$ docker compose ps
NAME                 IMAGE                                       COMMAND                  SERVICE   CREATED          STATUS                    PORTS
pleroma-2.11.0-app   git.pleroma.social/pleroma/pleroma:stable   "/opt/pleroma/docker…"   pleroma   26 seconds ago   Up 20 seconds             127.0.0.1:8148->4000/tcp
pleroma-2.11.0-db    postgres:15                                 "docker-entrypoint.s…"   db        26 seconds ago   Up 25 seconds (healthy)   5432/tcp

💡 说明:compose 项目名 pleroma-2.11.0-stack 中的点号会被自动剥离,网络实际叫 pleroma-2110-stack_pleroma-net。

3.2 入口脚本三步曲(首次启动日志实录)

$ docker compose logs pleroma | head -8
pleroma-2.11.0-app  | -- Waiting for database...
pleroma-2.11.0-app  | db:5432 - accepting connections
pleroma-2.11.0-app  | -- Running migrations...
pleroma-2.11.0-app  | warning: missing `:on` in join, defaulting to `on: true`.
pleroma-2.11.0-app  |   opt/pleroma/lib/pleroma-2.11.0-develop/priv/repo/migrations/20191029172832_fix_blocked_follows.exs:108: ...
pleroma-2.11.0-app  | 08:47:39.066 [info] == Running 20170320193800 Pleroma.Repo.Migrations.CreatePleroma.User.change/0 forward
pleroma-2.11.0-app  | 08:47:39.068 [info] create table if not exists users
pleroma-2.11.0-app  | 08:47:39.073 [info] == Migrated 20170320193800 in 0.0s

迁移从 2017 年第一张表一路跑到最新(共 2050+ 行日志,末条迁移 20260725000000),随后进入第三步:

pleroma-2.11.0-app  | 08:47:41.099 [info] == Migrated 20260725000000 in 0.0s
pleroma-2.11.0-app  | -- Starting!
pleroma-2.11.0-app  | 08:47:43.257 [info] PromEx is starting a standalone metrics server on port 4021 over http
pleroma-2.11.0-app  | 08:47:43.276 [info] Running Pleroma.Web.Endpoint with cowboy 2.19.0 at 0.0.0.0:4000 (http)
pleroma-2.11.0-app  | 08:47:43.276 [info] Access Pleroma.Web.Endpoint at http://localhost:8148
pleroma-2.11.0-app  | 08:47:43.277 [info] Gopher server disabled

💡 说明:PromEx ... port 4021 是内置 Prometheus 指标端点(PromEx 库),默认不映射到宿主机,需要监控时在 compose 加 4021 端口即可;紧随其后的 PromEx.LifecycleAnnotator failed to create start annotation in Grafana: :unkown 与 :econnrefused 警告是未配 Grafana 推送的正常噪音,可忽略。

3.3 数据库落表核对

$ docker exec pleroma-2.11.0-db psql -U pleroma -d pleroma -c "select nickname, is_admin, is_active from users order by id;"
    nickname    | is_admin | is_active
----------------+----------+-----------
 internal.fetch | f        | t
 admin          | t        | t
 relay          | f        | t
 tester         | f        | t
(4 rows)

internal.fetch 与 relay 是系统内置账号(联邦拉取代理与中继处理器),无需管理。

四、Web 与 API 闭环验证

4.1 Web 界面

$ curl -s -o /dev/null -w '%{http_code} %{size_download}\n' http://127.0.0.1:8148/
200 14259
$ curl -s http://127.0.0.1:8148/ | head -c 200
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
    <link rel="preload" href="/static/config.json" as="fetch" crossorigin />

官方镜像出厂内置 pleroma-fe(前端静态资源打包在发布制品 priv 目录内,/var/lib/pleroma/static 初始为空也不影响),开箱即有浏览器界面,无需先跑 mix pleroma.frontend install。

4.2 Nodeinfo 与实例信息

$ curl -s http://127.0.0.1:8148/.well-known/nodeinfo
{"links":[{"rel":"http://nodeinfo.diaspora.software/ns/schema/2.0","href":"http://localhost:8148/nodeinfo/2.0.json"},{"rel":"http://nodeinfo.diaspora.software/ns/schema/2.1","href":"http://localhost:8148/nodeinfo/2.1.json"}]}

$ curl -s http://127.0.0.1:8148/nodeinfo/2.0.json | head -c 300
{"version":"2.0","protocols":["activitypub"],"metadata":{"private":false,"features":["pleroma_api","mastodon_api","mastodon_api_grouped_notifications","mastodon_api_streaming","polls","v2_suggestions","pleroma_explicit_addressing","shareable_emoji_packs","multifetch",...

$ curl -s http://127.0.0.1:8148/api/v1/instance | head -c 200
{"version":"2.7.2 (compatible; Pleroma 2.11.0-develop)","description":"Pleroma: An efficient and flexible fediverse server","pleroma":{"metadata":{"features":["pleroma_api","mastodon_api",...

version 字段的 2.7.2 (compatible; ...) 是 Mastodon API 兼容版本号,真实版本看括号内 Pleroma 2.11.0-develop。features 清单里可以看到 chat、pleroma_emoji_reactions、quote_posting、editing 等主线特性。

⚠️ 排障实录 ③:nodeinfo 路径必须带 .json 后缀。/nodeinfo/2.1 返回:

{"error":"Nodeinfo schema version not handled"}

而 /nodeinfo/2.1.json 正常返回 200。.well-known/nodeinfo 给出的 href 才是权威路径。

4.3 管理员账号创建(CLI 路线)

镜像入口只负责起服务,不会自动建管理员(ADMIN_EMAIL 仅写入实例配置)。用发布制品自带的 pleroma_ctl:

⚠️ 排障实录 ④:容器工作目录是 /,直接 docker exec pleroma-2.11.0-app bin/pleroma ... 报 bin/pleroma: not found,必须用绝对路径 /opt/pleroma/bin/pleroma_ctl(或先 cd /opt/pleroma)。

$ docker exec pleroma-2.11.0-app /opt/pleroma/bin/pleroma_ctl user new admin admin@example.com --password q1w2e3r4 --admin -y
A user will be created with the following information:
  - nickname: admin
  - email: admin@example.com
  - password: q1w2e3r4
  - name: admin
  - bio:
  - moderator: false
  - admin: true
...(中间为逐条 SQL debug 日志,略)
User admin created

重复执行同一命令(账号已存在)时,2.11.0 的 user 任务不做友好提示、直接抛异常(实测原样):

⚠️ 排障实录 ⑤:

** (MatchError) no match of right hand side value: {:error, #Ecto.Changeset<action: :insert, ..., errors: [email: {"has already been taken", [constraint: :unique, constraint_name: "users_email_index"]}], ...>}
    (pleroma 2.11.0-develop) lib/mix/tasks/pleroma/user.ex:76: Mix.Tasks.Pleroma.User.run/1

结论:先查重再建号(psql -c "select nickname from users where nickname='admin'"),或按 email 冲突信息判断。异常栈中会打印完整 changeset(含 RSA 私钥与明文口令回显),生产日志注意脱敏。

4.4 OAuth 应用与令牌(Mastodon API 路线)

⚠️ 排障实录 ⑥:POST /api/v1/apps 传旧版单数参数 redirect_uri 被拒(400):

$ curl -s -X POST -d 'client_name=diag1&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scopes=read write follow' http://127.0.0.1:8148/api/v1/apps
{"error":"Missing field: redirect_uris.","errors":[{"message":"Missing field: redirect_uris","title":"Invalid value","source":{"pointer":"/redirect_uris"}}]}

应用日志同步给出严格校验证据:[error] Strict ApiSpec: request denied to /api/v1/apps with params %{"client_name" => "cli-test", "redirect_uri" => ...}。改用复数 redirect_uris 即 200:

$ curl -s -X POST -d 'client_name=diag3&redirect_uris=urn:ietf:wg:oauth:2.0:oob&scopes=read write follow' http://127.0.0.1:8148/api/v1/apps
{"id":"2","name":"diag3","website":null,"redirect_uri":"urn:ietf:wg:oauth:2.0:oob","client_secret":"ZwIzyISUpwffBUrRuaoeIi62Ko9pZR9-eUWw7Ar8TWE","client_id":"ziPjtW7_sGTg_Ed1EH8ggkw2AGmqHB6jQqYFwyuiX9U","vapid_key":"BFKp_ykgdcexcq3Wq-..."}

用 client_credentials 换管理员口令令牌(password grant):

$ curl -s -X POST -u 'ziPjtW7_sGTg_Ed1EH8ggkw2AGmqHB6jQqYFwyuiX9U:ZwIzyISUpwffBUrRuaoeIi62Ko9pZR9-eUWw7Ar8TWE' \
    -d 'grant_type=password&username=admin&password=q1w2e3r4&scope=read write follow' \
    http://127.0.0.1:8148/oauth/token
{"id":2,"scope":"read write follow","me":"http://localhost:8148/users/admin","expires_in":3153600000,"access_token":"vZ7u39IMjbfGN3_R2B_Zx-T5TzlsTDCdwnyAChffh_M","refresh_token":"Sp_fmbreRYdLBZijkfET5BbzCTvLRt8foPnH1Gt8PRI","token_type":"Bearer","created_at":1793621533}

凭据核对与发贴、拉时间线:

$ curl -s -H "Authorization: Bearer vZ7u39IMjbfGN3_R2B_Zx-T5TzlsTDCdwnyAChffh_M" http://127.0.0.1:8148/api/v1/accounts/verify_credentials | head -c 200
{"id":"BC25ecq6F8aSoEYBoe","header":"http://localhost:8148/images/banner.png","pleroma":{"email":"admin@example.com",...,"is_admin":true,...

$ curl -s -X POST -H "Authorization: Bearer vZ7u39IMjbfGN3_R2B_Zx-T5TzlsTDCdwnyAChffh_M" \
    -d 'status=S8 batch takeover check from Pleroma 2.11.0 compose stack' \
    http://127.0.0.1:8148/api/v1/statuses | head -c 300
{"id":"BC2NPYWLocV0FMHEcS","sensitive":false,"application":null,"pleroma":{"local":true,"context":"http://localhost:8148/contexts/76331ed5-2f59-4e64-b529-bd4f43a4a628",...

$ curl -s -H "Authorization: Bearer vZ7u39IMjbfGN3_R2B_Zx-T5TzlsTDCdwnyAChffh_M" http://127.0.0.1:8148/api/v1/timelines/public | head -c 200
[{"id":"BC2NPYWLocV0FMHEcS","sensitive":false,"application":{"name":"cli-test","website":null},"pleroma":{"local":true,...

发贴 200、公开时间线能查到该贴,部署闭环完成。

⚠️ 排障实录 ⑦:直连端口(无反代)时每个 API 请求都会刷一条警告:

[warning] Rate limiter disabled due to forwarded IP not being found. Please ensure your reverse proxy is providing the X-Forwarded-For header or disable the RemoteIP plug/rate limiter.

原因是限流器依赖 X-Forwarded-For 识别来源 IP。生产按官方 nginx 模板配好 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 即消失;实验环境可无视。

五、生产化差异清单

本实验为「无域名直连」形态,上生产需按下表改造(细节见配置篇):

项实验形态生产形态
DOMAINlocalhost真实域名(开放联邦后不可更改)
Endpoint urlconfig.exs 手工覆盖 http:8148删除覆盖,恢复出厂 scheme: "https", port: 443
TLS无nginx/caddy 反代终结,转发 X-Forwarded-For
端口暴露127.0.0.1:8148仅反代可达应用 4000,DB 不出网
registrations_open出厂 false(关注册)公共实例显式开或走邀请制
DB 口令compose 明文secret.exs/环境注入 + 强口令
数据备份bind 目录定期 pg_dump + 打包 /var/lib/pleroma(secret.exs 必含)

六、升级与回滚

  • 升级:改 compose 镜像标签(如 v2.11.0-amd64)→ docker compose pull && docker compose up -d,入口脚本自动执行 pleroma_ctl migrate 补跑新迁移;裸 OTP 路线则用 bin/pleroma_ctl update(内置从 git.pleroma.social/api/packages/pleroma/generic 拉 pleroma-otp-<branch>-<flavour> 制品、解包替换的完整逻辑,flavour 自动探测 amd64/arm64/arm/musl)。
  • 回滚:先 pleroma_ctl rollback [VERSION] 回退迁移,再换回旧镜像标签。版本跨度大时先查 release notes 的 Upgrade notes 段。
  • 制品核对提醒:registry 各版本标签架构完备度不一(实测 v2.11.0 有 amd64/arm64/armv7,v2.11.1 当时仅 arm64/armv7),升级前先 docker manifest inspect 确认目标架构标签存在。

七、清理实录

$ docker compose down
 Container pleroma-2.11.0-app Stopping
 Container pleroma-2.11.0-app Stopped
 Container pleroma-2.11.0-app Removing
 Container pleroma-2.11.0-app Removed
 Container pleroma-2.11.0-db Stopping
 Container pleroma-2.11.0-db Stopped
 Container pleroma-2.11.0-db Removing
 Container pleroma-2.11.0-db Removed
 Network pleroma-2110-stack_pleroma-net Removing
 Network pleroma-2110-stack_pleroma-net Removed

$ docker volume ls | grep -i pleroma   # 本栈数据全为 bind 挂载,卷列表零残留
$ rm -rf ./data ./pgdata               # 如需彻底清数据

八、站内阅读


实测说明:本文回显均产生于 2026-11-02 的 Ubuntu 24.04.5 + Docker 29.9.2 环境;长 SQL debug 日志与异常栈中的密钥内容已按行截断/脱敏,其余逐行照抄。

Pleroma 2.11.0 Docker Compose 部署指南 - Quzuo