PieFed 1.8.10 Docker Compose 部署指南

PieFed 1.8.10 官方 compose 源码构建部署全实测:clone/checkout、.env.docker、数据目录权限、六服务编排、init-db 建管理员、nodeinfo/Alpha API 验证、PostgreSQL/Redis 依赖链,含 entrypoint 重启循环与 TTY 交互初始化等排障实录。

PieFedPieFedFediverse论坛PythonDocker自托管2026/11/02

PieFed 1.8.10 Docker Compose 部署指南

目标:用 PieFed 官方源码仓库 rimu/pyfedi(v1.8.10)自带的 compose 编排,从源码构建并部署一套可登录、可发帖、可联邦的 PieFed 论坛,完整覆盖 PostgreSQL 17 + Redis 6.2 依赖链、数据目录初始化、数据库初始化与管理员创建,并沉淀 entrypoint 重启循环、交互式初始化 TTY 依赖等排障实录。已在 Ubuntu 24.04.5 LTS + Docker 29.9.2 + Compose v5.6.1 环境全量实测通过,文中所有命令与回显均为实测原文(演示账号 admin / q1w2e3r4)。

一、环境信息

本文所有命令与输出均在以下环境实测采集(2026-11-02):

  • 操作系统:Ubuntu 24.04.5 LTS(x86_64,kernel 7.0.0-38-generic)
  • Docker:29.9.2(含 Docker Compose v5.6.1 插件)
  • PieFed:v1.8.10(Codeberg 仓库 tag,2026-10-31 打点;app/constants.py 中 VERSION = '1.8.10')
  • 应用运行时:Python 3.13-slim 基础构建,Flask + Gunicorn 26.2.0(gthread worker)
  • 数据库:PostgreSQL 17(官方镜像 postgres:17,compose 内置调优参数)
  • 缓存/队列:Redis 6.2(官方镜像 redis:6.2;DB0 缓存、DB1 Celery broker)
  • 实时通知:piefed-notifs(同仓库 fastapi_server.py,FastAPI + uvicorn)
  • 可选面板:Adminer(官方镜像 adminer)
  • 部署目录:/opt/data/quzuo/tmp/piefed/pyfedi(生产建议 /opt/piefed/pyfedi 等等价位置,下文命令在仓库根目录执行)
  • 对外端口:Web 8129、实时通知 8130、Adminer 仅 127.0.0.1:8888;自定义网络 10.89.56.0/24

站内配套:《PieFed 1.8.10 介绍与选型指南》、《PieFed 1.8.10 配置参数详解》、《PieFed 1.8.10 常用 CLI 命令与工具详解》。同类联邦论坛部署可对照 《Lemmy 0.19.21 Docker Compose 部署指南》。

二、制品选型说明(实查结论)

PieFed 官方不发布预构建应用制品。2026-11-02 实查:

  • ghcr.io/rimu/pyfedi 匿名 manifest 请求返回 401(无公开仓库);
  • Docker Hub 检索无 piefed/pyfedi 官方仓库;
  • 官方 compose.yaml 中 web/celery/piefednotifs 三个服务全部声明 build: context: . / target: runtime,即官方生产形态就是从源码仓库根目录构建。

因此本篇不走"拉现成制品改 tag"路线,而是严格按官方 INSTALL-docker.md 的 clone → checkout → build 路径执行。仅依赖的三个公共基础制品按官方钉定版本拉取:postgres:17、redis:6.2、adminer。

三、前置条件

  1. 一台 2C4G 起步的 Linux 服务器(官方最低口径),磁盘 ≥40 GB 或预留对象存储。
  2. Docker 29.x 与 Compose v2+ 插件;docker compose version 回显 Docker Compose version v5.6.1。
  3. 出站网络:构建需访问 PyPI 与 Debian 源;初始化会访问 data.lemmyverse.net(社区数据集)与 raw.githubusercontent.com(一次性邮箱域名表,可缺省,见排障 11.1)。
  4. 生产部署需注册域名并准备 SSL 反向代理(Nginx/Caddy/Cloudflare Tunnel);测试环境可用 127.0.0.1:端口,但官方提醒:要联邦必须有域名,且 SERVER_NAME 事后变更等于清库重来。

四、部署步骤

4.1 获取官方部署包并检出稳定版

PieFed 的部署包就是源码仓库本体(含官方 compose.yaml),按官方指南 clone 后检出最新稳定 tag:

git clone https://codeberg.org/rimu/pyfedi.git
cd pyfedi
git checkout v1.8.10

实测回显(tag 日期与版本号交叉验证):

$ git fetch --depth 1 origin refs/tags/v1.8.10:refs/tags/v1.8.10
 * [new tag]         v1.8.10    -> v1.8.10
$ git show v1.8.10 --no-patch --format='%ci %s'
2026-10-31 12:00:01 +1300 v1.8.10

仓库根目录关键文件:compose.yaml(编排)、env.docker.sample(环境变量模板)、Dockerfile(两阶段构建,builder 装编译依赖、runtime 产出运行环境)、docker-dirs.sh(数据目录脚本)、INSTALL-docker.md(官方指南)、entrypoint.sh / entrypoint_celery.sh / entrypoint_async.sh(三类进程入口)、docker.cron + supercronic(内置定时任务)。

4.2 准备环境变量文件 .env.docker

官方步骤是复制模板并编辑 SECRET_KEY 与 SERVER_NAME:

cp env.docker.sample .env.docker

实测写入的最小集(演示口令 q1w2e3r4;SECRET_KEY 用 32 字节随机十六进制):

SECRET_KEY='c0d5…(64位随机)'
SERVER_NAME='127.0.0.1:8129'

POSTGRES_USER='piefed'
POSTGRES_PASSWORD='q1w2e3r4'
POSTGRES_DB='piefed'

DATABASE_URL=postgresql+psycopg2://piefed:q1w2e3r4@db/piefed

CACHE_TYPE='RedisCache'
CACHE_REDIS_DB=1
CACHE_REDIS_URL='redis://redis:6379/0'
CELERY_BROKER_URL='redis://redis:6379/1'

ENABLE_ALPHA_API = 'true'

注意 DATABASE_URL 的主机名 db 就是 compose 中 PostgreSQL 服务名,Redis 同理用 redis——这是官方模板的写死约定,改服务名必须同步改 URL。

4.3 compose 定制(显式项目名 + 端口 + 网络)

官方指南明确:定制 compose.yaml 的推荐方式是另建 compose.override.yaml 合并覆盖,并 git update-index --assume-unchanged 让 git 忽略本地改动。本篇实测做了三处定制(直接改 compose.yaml 亦可,此处为记录方便列出 diff 要点):

# 1) 显式项目名(多实例并存时防止 down 互删)
name: piefed-v1.8.10-stack

# 2) web 端口映射改为宿主 8129(官方默认 8030:5000)
  web:
    ports:
      - '8129:5000'

# 3) 实时通知端口(官方默认 8040:8000)
  piefednotifs:
    ports:
      - "8130:8000"

# 4) 自定义网络固定网段
networks:
  pf_network:
    name: piefed-v1.8.10-net
    external: false
    ipam:
      config:
        - subnet: 10.89.56.0/24

官方编排的服务全貌(6 服务):db(postgres:17,user: "1000:1000",shm_size 2GB,内置 shared_buffers=1GB 等调优命令行参数)、redis(redis:6.2)、celery(构建,container_name piefed_celery1)、web(构建,container_name piefed_app1)、piefednotifs(构建,container_name piefed_notifs)、adminer(127.0.0.1:8888)。

4.4 创建数据目录(勿跳过)

官方指南强调 ./docker-dirs.sh 不能跳过,它会创建四个 bind 目录并授权:

./docker-dirs.sh   # 创建 pgdata/ media/ logs/ tmp/

实测等价手工执行与关键权限修正——compose 里 db 服务以 user: "1000:1000" 运行,而 docker-dirs.sh 把目录 chown 给当前登录用户;root 下执行必须补一刀,否则 PostgreSQL 拒绝启动:

mkdir -p pgdata media logs tmp
chown -R 1000:1000 pgdata media logs tmp
$ ls -ld pgdata media logs tmp
drwxr-xr-x 2 yahui yahui 4096 Nov  2 16:06 logs
drwxr-xr-x 2 yahui yahui 4096 Nov  2 16:06 media
drwxr-xr-x 2 yahui yahui 4096 Nov  2 16:06 pgdata
drwxr-xr-x 2 yahui yahui 4096 Nov  2 16:06 tmp

四个目录用途:pgdata/ → 数据库数据文件;media/ → 用户上传与生成的媒体(挂到应用 app/static/media/);logs/ → 应用日志;tmp/ → 临时件与社区数据集(app/static/tmp/)。

4.5 拉取公共基础制品与源码构建

docker compose pull db redis adminer
export DOCKER_BUILDKIT=1
docker compose build

pull 实测回显尾部:

 Image adminer Pulled

build 为两阶段构建(builder 装 gcc/libpq-dev 编译 Python 依赖 → runtime 装 tini/curl/procps/postgresql-client/tesseract-ocr 并下载 supercronic v0.2.49 校验 sha1,pybabel compile 编译 10 种语言),三个应用服务共享同一 runtime 层。完成后实测:

$ docker images | grep piefed
piefed-v1810-stack-celery:latest      1.06GB
piefed-v1810-stack-web:latest         1.06GB
piefed-v1810-stack-piefednotifs:latest 1.06GB

4.6 启动与数据库初始化

docker compose up -d

实测启动回显:

 Container piefed-v1810-stack-db-1 Started
 Container piefed-v1810-stack-redis-1 Started
 Container piefed-v1810-stack-adminer-1 Started
 Container piefed_celery1 Started
 Container piefed_notifs Started
 Container piefed_app1 Started

web 的入口脚本会自动执行 flask db upgrade(Alembic 迁移)与 flask populate_community_search(拉社区数据集)。迁移完成前访问首页返回 500 是官方文档预告的预期现象("Browser will show 'Internal Server Error' message. Proceed to initialize database"),排障 11.1/11.3 有完整分析。

初始化数据库并创建管理员(官方形态是交互输入用户名/邮箱/口令):

docker exec -it piefed_app1 sh
# 容器内:
export FLASK_APP=pyfedi.py
flask init-db

实测交互回显(非 TTY 环境驱动方式见排障 11.2):

Added banned instance lemmychan.org
Manage banned instances at /admin/federation.
Added 'No-QAnon' blocklist, see https://github.com/rimu/no-qanon.
This can be reversed at /domains/banned using the 'unban all' button.
The admin user created here should be reserved for admin tasks and not used as a primary daily identity (unless this instance will only be for personal use).
Admin user name (ideally not 'admin'): admin
Admin email address: admin@piefed.test
Admin password: ********

init-db 除建管理员外还落库:本地 Instance 记录、Site 记录、Admin 角色及权限、默认版块、29 条起步封禁域名与 No-QAnon 名单、6 项 cron 心跳记录(用于后台"维护任务是否按时运行"告警)。

4.7 上线验证

$ docker compose ps
NAME                           IMAGE                             STATUS                    PORTS
piefed-v1810-stack-adminer-1   adminer                           Up                        127.0.0.1:8888->8080/tcp
piefed-v1810-stack-db-1        postgres:17                       Up                        5432/tcp
piefed-v1810-stack-redis-1     redis:6.2                         Up                        6379/tcp
piefed_app1                    piefed-v1810-stack-web            Up (healthy)              0.0.0.0:8129->5000/tcp
piefed_celery1                 piefed-v1810-stack-celery         Up (healthy)              5000/tcp
piefed_notifs                  piefed-v1810-stack-piefednotifs   Up (healthy)              0.0.0.0:8130->8000/tcp
$ curl -s http://127.0.0.1:8129/nodeinfo/2.0.json | head -c 260
{"openRegistrations":false,"protocols":["activitypub"],"software":{"name":"piefed","version":"1.8.10"},"usage":{"localComments":0,"localPosts":0,"users":{"activeHalfyear":1,"activeMonth":1,"total":1}},"version":"2.0"}

首页跳转与登录态(实测):

$ curl -sL -o /dev/null -w "root_final:%{http_code} url=%{url_effective}\n" http://127.0.0.1:8129/
root_final:200 url=http://127.0.0.1:8129/auth/login?next=/home

Alpha API 登录(演示账号):

$ curl -s -X POST http://127.0.0.1:8129/api/alpha/user/login \
    -H 'Content-Type: application/json' -d '{"username":"admin","password":"q1w2e3r4"}'
{"jwt":"eyJhbGciOiJIUzI1NiIs...(232 字符)","code":200}

建社区、发帖验证(API 路径实测):

$ curl -s -X POST http://127.0.0.1:8129/api/alpha/community -H "Authorization: Bearer $TOKEN" \
    -H 'Content-Type: application/json' -d '{"name":"techops","title":"TechOps","description":"Self-hosting and ops topics"}'
{"community_view":{"community":{"actor_id":"https://127.0.0.1:8129/c/techops","id":1,"local":true,"name":"techops",...}}}

$ curl -s -X POST http://127.0.0.1:8129/api/alpha/post -H "Authorization: Bearer $TOKEN" \
    -H 'Content-Type: application/json' -d '{"title":"PieFed API 首帖","body":"通过 alpha API 发布的测试帖","community_id":1}'
{"post_view":{"community":{"actor_id":"https://127.0.0.1:8129/c/techops",...}}}
$ docker exec piefed-v1810-stack-db-1 psql -U piefed -d piefed -c "select name,post_count from community,post where post.community_id=community.id;"
  name   | post_count
---------+------------
 techops |          1

资源占用实测(docker stats --no-stream,空载):

piefed_app1        0.03%  1.076GiB
piefed_celery1     0.02%  534MiB
piefed-v1810-stack-db-1     0.00%  132.1MiB
piefed_notifs      0.08%  45.87MiB
piefed-v1810-stack-redis-1  0.09%  3.633MiB
piefed-v1810-stack-adminer-1 0.00%  8.531MiB

空载合计约 1.8 GB 内存(web 进程按 CPU 核数/2 起了 4 个 gthread worker,各 worker 常驻约 270 MB),与官方"4GB 起步、10 人以上翻倍"口径吻合。

五、PostgreSQL / Redis 依赖链说明

PieFed 对两个有状态组件的用法(G26 批次注记要求展开):

PostgreSQL 17:唯一主存储,ActivityPub 对象、帖子/评论/投票、联邦队列(send_queue 表)全部入库。官方 compose 直接以命令行参数内置一档调优(shared_buffers=1GB、effective_cache_size=2GB、work_mem=32MB、max_connections=150、max_wal_size=3GB、jit=off 等),并在 shm_size: 2gb 下运行;官方文档明确提醒 4GB 内存以下机器这套默认参数会超用,需下调。init-db 还会强制校验库编码必须 UTF8,否则拒绝初始化(中文/emoji 场景必须满足)。连接池在应用侧由 DB_POOL_SIZE=10 / DB_MAX_OVERFLOW=30 控制。

Redis 6.2:双角色——DB0 做 Flask-Cache(CACHE_TYPE='RedisCache')与联邦暂停状态位(pause_federation 键),DB1 做 Celery broker(CELERY_BROKER_URL)。REDIS_MEMORY_LIMIT(默认 200 MB)是联邦保护水位:超限后 flask send-queue 自动暂停出站投递并置暂停位 600 秒,防止联邦队列把内存打爆。celery 的健康检查用 celery inspect ping 实测通过。

六、数据持久化说明

路径挂载点内容备份优先级
./pgdatadb 服务 /var/lib/postgresql/data全部业务数据★★★(唯一不可再生)
./mediaweb/celery /app/app/static/media/用户上传与原图/缩略图★★★(可配 S3 外置)
./tmpweb/celery /app/app/static/tmp/社区数据集、一次性邮箱表、临时件★(可再生)
./logsweb/celery /app/logs/应用日志★
卷 redis_dataredis /data队列/缓存持久化★(可再生,暂停位丢失无碍)

备份最小集 = pgdata + media 两目录(停写或 pg_dump 逻辑备更稳)。

七、升级与回滚

官方给出两条升级路径:

# 手动:切 tag → 重新构建 → 滚动重启(迁移自动执行)
cd pyfedi
git fetch --tags && git checkout v1.8.10
export DOCKER_BUILDKIT=1
docker compose up -d --build

# 官方脚本(实测内容即三行:down → git pull → up -d --build)
./deploy-docker2.sh

deploy-docker2.sh 全文(实测):

#!/bin/bash
export DOCKER_BUILDKIT=1
docker compose down
git pull && docker compose up -d --build

官方提醒:大版本升级可能需要额外步骤,先看 piefed.social/c/piefed_meta 的发布公告。回滚 = 切回旧 tag 重新构建 + 用备份恢复 pgdata(Alembic 无自动降级承诺,跨版本回滚以数据备份为准)。

八、定时任务接线

联邦论坛的出站投递依赖每分钟一次的 send-queue,官方给两种接线方式:

  1. 内置 supercronic:compose 中 web 服务加环境变量 CRON=true,docker.cron 即生效(每日维护 02:05、send-queue 每分钟、邮件通知每 30 分、孤儿文件每周一 04:05)。
  2. 宿主 crontab(官方模板,/etc/cron.d/piefed):
5 2 * * * <USERNAME> docker exec piefed_app1 bash -c "cd /app && ./daily.sh"
*/1 * * * * <USERNAME> docker exec piefed_app1 bash -c "cd /app && ./send_queue.sh"
1 */6 * * * <USERNAME> docker exec piefed_app1 bash -c "cd /app && ./email_notifs.sh"
5 4 * * 1 <USERNAME> docker exec piefed_app1 bash -c "cd /app && ./remove_orphan_files.sh"

命令族详解见 《PieFed 1.8.10 常用 CLI 命令与工具详解》。

九、反向代理要点

生产形态需在前面挂 SSL 代理(Nginx/Caddy/Cloudflare Tunnel),把 443 转发到本实例 5000(本篇实测映射宿主 8129)。官方建议 Nginx 跑在宿主而不是代理组件里,便于把真实客户端 IP 透传给 PieFed(联邦与治理功能依赖客户端 IP 判定)。上线后访问 https://<域名>/test_ip 验证 IP 识别是否正确;配置头需透传 X-Forwarded-For/X-Forwarded-Proto(应用侧 ProxyFix 已内置)。

十、常见问题(排障实录)

10.1 populate_community_search 拉 GitHub raw 超时导致 web 反复重启

现象:up -d 后 web 组件 RestartCount 持续 +1,日志尾部固定出现:

  File "/app/app/cli.py", line 1857, in populate_community_search
    resp = get_request(
        'https://raw.githubusercontent.com/disposable/disposable-email-domains/master/domains.txt')
  File "/app/app/utils.py", line 164, in get_request
    raise httpx.HTTPError(f"HTTPError: {str(e)}") from read_timeout
httpx.HTTPError: HTTPError: The read operation timed out
Running database migrations...

定位:入口脚本 entrypoint.sh 是 set -e,启动时硬跑 flask populate_community_search;该命令依次拉 data.lemmyverse.net(本环境 200 可达)与 raw.githubusercontent.com(本环境直连与常见 CDN 之外全部超时,容器内实测 curl_exit=28)。后者抛异常 → 入口脚本退出 → 迁移白跑一轮又一轮,gunicorn 永远起不来(curl 首页返回 000)。

修复:一次性邮箱域名表只在 FLAG_THROWAWAY_EMAILS=1 时参与业务,且命令里本就容忍非 200 响应,缺它不影响站点运行。按官方推荐的 override 方式(4.3 节)覆盖 web 入口,把该步骤改为可失败继续:

# compose.override.yaml
services:
  web:
    entrypoint:
      - /usr/bin/tini
      - "--"
      - /bin/bash
      - -c
      - |
        set -e
        export FLASK_APP=pyfedi.py
        echo "Running database migrations..."
        flask db upgrade
        flask populate_community_search || echo "WARN: populate_community_search failed (outbound to raw.githubusercontent.com blocked?)"
        if [[ "${CRON:-}" == [YyTt1]* ]]; then
          supercronic -quiet -no-reap /app/docker.cron &
        fi
        echo "Starting Gunicorn..."
        exec gunicorn --config gunicorn.conf.py --preload pyfedi:app
docker compose up -d web

修复后实测:

WARN: populate_community_search failed (outbound to raw.githubusercontent.com blocked?)
Starting Gunicorn...
[2026-11-02 08:23:47 +0000] [7] [INFO] Starting gunicorn 26.2.0
[2026-11-02 08:23:47 +0000] [7] [INFO] Listening at: http://0.0.0.0:5000 (7)
[2026-11-02 08:23:47 +0000] [7] [INFO] Using worker: gthread
[2026-11-02 08:23:47 +0000] [28] [INFO] Booting worker with pid: 28

教训:出站受限网络部署 PieFed 时,先在应用环境内验证两个初始化 URL 的可达性;lemmyverse 数据集缺失只影响"批量引入社区"后台功能,可后补。

10.2 flask init-db 交互口令要 TTY,管道喂输入报 termios 错误

现象:非交互执行 printf 'admin\na@b.c\nq1w2e3r4\n' | docker exec -i piefed_app1 sh -c '... flask init-db' 崩在:

  File "/venv/lib/python3.13/site-packages/pwinput/__init__.py", line 93, in getch
    old_settings = termios.tcgetattr(fd)
termios.error: (25, 'Inappropriate ioctl for device')

定位:用户名/邮箱用 input() 可管道喂,但口令走 pwinput(需要 termios 的 TTY);docker exec -it 又要求宿主 stdin 本身是终端(管道场景报 cannot attach stdin to a TTY-enabled container)。

修复:两条路,都实测可用——

  1. 脚本分配伪终端驱动交互(pty.openpty + docker exec -it,按提示串逐个回写答案),本次部署即以此完成管理员创建;
  2. 纯自动化用 flask init-db --interactive no 完成建库(Site/Instance/角色/封禁表/cron 心跳全部落库,仅跳过管理员),再仿官方逻辑建号:
docker exec -i piefed_app1 sh -c 'export FLASK_APP=pyfedi.py && flask shell' <<'PY'
from app import db
from app.models import User, Role, utcnow
from app.activitypub.signature import RsaKeys
from app.auth.util import random_token
from app.utils import gibberish
from random import randint
u = User(user_name='admin', title='admin', email='admin@piefed.test',
         verification_token=random_token(16), instance_id=1, email_unread_sent=False,
         alt_user_name=gibberish(randint(8, 20)))
pk, pub = RsaKeys.generate_keypair()
u.private_key, u.public_key = pk, pub
u.set_password('q1w2e3r4')
u.roles.append(Role.query.filter_by(name='Admin').first())
u.verified = True
u.last_seen = utcnow()
db.session.add(u); db.session.commit()
print('ADMIN_CREATED id=', u.id)
PY

实测回显:ADMIN_CREATED id= 1,随后首页 302→200 正常。

10.3 初始化前首页 500:AttributeError NoneType.table

现象:迁移完成、init-db 之前,curl http://127.0.0.1:8129/ 返回 500,日志:

  File "/app/pyfedi.py", line 103, in before_request
    site = get_site_as_dict()
...
AttributeError: 'NoneType' object has no attribute '__table__'

定位:每个请求的 before_request 都要读 Site 表首行,空库时返回 None 直接炸。官方 INSTALL-docker.md 已预告此现象("Browser will show 'Internal Server Error' message. Proceed to initialize database")。

处置:非故障,完成 4.6 的 init-db 即恢复。若 init-db 后仍 500,先查 select count(*) from site; 是否为 0——为 0 说明 init-db 事务未提交(多半是 10.2 的 TTY 崩溃导致半途回滚,重跑即可,init-db 幂等前会检查 alembic 表存在性)。

10.4 pgdata 权限不对,db 组件起不来

现象:docker-dirs.sh 在 root 会话跑完后 db 反复退出。

定位:官方 compose 给 db 固定 user: "1000:1000",而脚本把目录 chown 给当前用户;root 部署时 pgdata 属主是 root,PostgreSQL 初始化数据目录失败。官方指南其实也提了补救口径("try repeating the previous step with the chown command using 1000")。

处置:chown -R 1000:1000 pgdata media logs tmp 后 docker compose up -d db,实测进入 Up 且 web 迁移链随即打通。

十一、参考链接

PieFed 1.8.10 Docker Compose 部署指南 - Quzuo