PieFed 1.8.10 Docker Compose 部署指南
PieFed 1.8.10 官方 compose 源码构建部署全实测:clone/checkout、.env.docker、数据目录权限、六服务编排、init-db 建管理员、nodeinfo/Alpha API 验证、PostgreSQL/Redis 依赖链,含 entrypoint 重启循环与 TTY 交互初始化等排障实录。
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。
三、前置条件
- 一台 2C4G 起步的 Linux 服务器(官方最低口径),磁盘 ≥40 GB 或预留对象存储。
- Docker 29.x 与 Compose v2+ 插件;
docker compose version回显Docker Compose version v5.6.1。 - 出站网络:构建需访问 PyPI 与 Debian 源;初始化会访问
data.lemmyverse.net(社区数据集)与raw.githubusercontent.com(一次性邮箱域名表,可缺省,见排障 11.1)。 - 生产部署需注册域名并准备 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 实测通过。
六、数据持久化说明
| 路径 | 挂载点 | 内容 | 备份优先级 |
|---|---|---|---|
./pgdata | db 服务 /var/lib/postgresql/data | 全部业务数据 | ★★★(唯一不可再生) |
./media | web/celery /app/app/static/media/ | 用户上传与原图/缩略图 | ★★★(可配 S3 外置) |
./tmp | web/celery /app/app/static/tmp/ | 社区数据集、一次性邮箱表、临时件 | ★(可再生) |
./logs | web/celery /app/logs/ | 应用日志 | ★ |
卷 redis_data | redis /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,官方给两种接线方式:
- 内置 supercronic:compose 中 web 服务加环境变量
CRON=true,docker.cron即生效(每日维护 02:05、send-queue 每分钟、邮件通知每 30 分、孤儿文件每周一 04:05)。 - 宿主 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)。
修复:两条路,都实测可用——
- 脚本分配伪终端驱动交互(
pty.openpty+docker exec -it,按提示串逐个回写答案),本次部署即以此完成管理员创建; - 纯自动化用
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 迁移链随即打通。
十一、参考链接
- 官方 Docker 安装指南:https://codeberg.org/rimu/pyfedi/src/branch/main/INSTALL-docker.md
- 裸机安装指南:https://codeberg.org/rimu/pyfedi/src/branch/main/INSTALL.md
- 官方安装入口文档:https://join.piefed.social/docs/installation/
- 源码仓库:https://codeberg.org/rimu/pyfedi (v1.8.10,AGPL-3.0-or-later)
- 低内存调优讨论:https://piefed.social/c/piefed_help/p/1867393
- 站内配套:《PieFed 1.8.10 配置参数详解》、《PieFed 1.8.10 常用 CLI 命令与工具详解》、《Lemmy 0.19.21 Docker Compose 部署指南》