同一天打开十几个网站,很多次只是想确认“有没有更新”。RSS 能把更新送到一个地方,但阅读器如果塞满分类、推荐和不停出现的按钮,也会变成另一种信息负担。Miniflux 的界面偏简洁,适合只想订阅、阅读、保留几篇文章的人。
这篇部署一个个人阅读器,用 PostgreSQL 保存账号、订阅和阅读状态。先导入少量来源,确认抓取与手机访问,再迁入旧阅读器的 OPML。OPML 主要保存订阅关系,不应当作完整数据库备份;已经读过什么、哪些文章被收藏,需要另外检查。
先准备三类不同的订阅
选择一个普通博客、一份更新较少的技术公告,再加一个自己经常看的来源。观察它们提供全文还是摘要、更新间隔怎样、是否经常出现重复条目。RSS 内容由来源决定,阅读器不能保证每个网站都提供完整正文。
来源没有公开 feed 时,不急着用脚本猜几十个地址。先找网站给出的 RSS 或 Atom 入口,核对服务器能请求到 XML。浏览器访问网站首页正常,不代表 VPS 能抓取它的订阅;目标限流、认证或网络路径都可能影响更新。
服务器已安装 Docker Engine、Compose 插件,当前账号能执行 Docker。Caddy 在宿主机运行,域名 rss.example.com 指向 VPS。数据库不公开端口,应用只在回环地址提供上游服务。
mkdir -p /opt/miniflux/db
cd /opt/miniflux
umask 077
openssl rand -hex 32
openssl rand -hex 32
创建 .env,分别填写数据库密码和管理员密码。数据库连接 URL 中本例使用十六进制随机值,避免未编码的特殊字符影响解析。用户名可以改成自己喜欢的名字,不必沿用 admin。
MINIFLUX_DB_PASSWORD=your-generated-database-password
MINIFLUX_ADMIN_USERNAME=reader
MINIFLUX_ADMIN_PASSWORD=your-generated-login-password
chmod 600 .env
首次初始化把账号建立出来
保存以下内容为 compose.yaml。官方当前示例使用 PostgreSQL 18,挂载根目录为 /var/lib/postgresql;17 及更早版本的数据目录不同。只把它用于新实例,不覆盖已有数据库的大版本升级流程。
services:
miniflux:
image: miniflux/miniflux:latest
restart: unless-stopped
ports:
- "127.0.0.1:8087:8080"
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgres://miniflux:${MINIFLUX_DB_PASSWORD:?set database password}@db/miniflux?sslmode=disable
RUN_MIGRATIONS: "1"
CREATE_ADMIN: "1"
ADMIN_USERNAME: ${MINIFLUX_ADMIN_USERNAME:?set admin username}
ADMIN_PASSWORD: ${MINIFLUX_ADMIN_PASSWORD:?set admin password}
healthcheck:
test: ["CMD", "/usr/bin/miniflux", "-healthcheck", "auto"]
interval: 30s
timeout: 5s
retries: 3
db:
image: postgres:18
restart: unless-stopped
environment:
POSTGRES_USER: miniflux
POSTGRES_DB: miniflux
POSTGRES_PASSWORD: ${MINIFLUX_DB_PASSWORD:?set database password}
volumes:
- ./db:/var/lib/postgresql
healthcheck:
test: ["CMD", "pg_isready", "-U", "miniflux", "-d", "miniflux"]
interval: 10s
start_period: 30s
RUN_MIGRATIONS 安排数据库结构迁移,CREATE_ADMIN 和账号参数用于首次建管理员。首次成功登录以后,可以移除建管理员的三个环境字段,以及不再需要的管理员密码配置,重新创建应用容器。更改这些初始化字段不是修改现有用户密码的方法。
首次拉取当前镜像后,固定正式版本或摘要,后续升级先看发布说明。数据库镜像也要记录实际版本,不让自动更新器替你跨大版本迁移。
docker compose config --quiet
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=100 miniflux db
应用一直等数据库时检查数据库日志、账号和网络。初始化过的 PostgreSQL 不会因为 .env 密码改了就自动改库内密码;需要正式修改数据库用户,再同步应用配置。不要为了解决连接失败删除 db。
阅读器地址保持一个固定入口
可以先通过 SSH 转发登录,打开本机 18087 端口确认管理员与数据初始化正常。
ssh -N -L 18087:127.0.0.1:8087 [email protected]
宿主机 Caddy 添加如下站点段,校验配置后重载,手机与电脑使用正式 HTTPS 域名访问。若代理在容器里,改用共享 Docker 网络上的服务名。
rss.example.com {
reverse_proxy 127.0.0.1:8087
}
正式入口用未登录窗口核对权限,再登录添加一条 feed。家庭成员需要使用时单独建账号,订阅和阅读状态分别管理。给客户端或自动化创建独立凭据,不要把管理员密码复制到每一台设备。
只给自己用时,也可以让域名在 VPN 内解析并限制访问,不必公开整个入口。迁移后保持同一地址能减少客户端修改,但仍要检查证书、账号以及新增条目是否进入新实例。
OPML 导入以后先清理重复来源
从旧阅读器导出 OPML,保存原文件,再导入 Miniflux。核对几个分类、来源地址和条目数量,不把“导入成功”理解成历史文章与未读状态全部迁移完成。某些来源可能更换过 feed,旧地址还会存在,应查明后再整理。
分类少一点更容易维护。可以先分“常看”“公告”“偶尔查”,不要复制旧目录里几十个几乎没有内容的分类。更新很频繁的来源单独放一组,阅读时能主动选择要不要打开,避免它把其他来源挤到列表后面。
先读完一条文章,刷新后检查已读状态,再用手机登录确认同步。收藏一篇文章,从另一端查看收藏列表。客户端只是显示同样的来源,还不能证明阅读与收藏状态对上了。
对全文较短的 feed,按当前阅读界面和来源设置尝试抓取原文,逐项核对结果。登录墙、复杂脚本和反爬页面可能让抓取失败。保留打开原网站的方式,不把不完整结果当作网站文章的完整档案。
抓取失败要分清是哪一步
单个来源报错时,查看状态码与错误消息,核对 feed URL、认证要求和服务器网络。所有来源同时停止更新,检查应用是否运行、数据库是否可写,再看任务与资源。不同范围的失败不适合用同一个办法处理。
来源返回重复条目,有时与它的 GUID、链接或发布时间变化有关。删除订阅重加可能让旧内容又全部出现,先记录样本和变化,再决定处理方式。不要为了让未读数字变小就把所有异常来源删掉。
高频抓取可能触发网站限流。先按来源更新频率安排,不必给每个几天才更新一次的博客都设置很短间隔。错误重试也会消耗请求,排障时关注目标响应而不是盲目加快刷新。
文章乱码、图片丢失或正文缺段时,先对照原始 feed 和网站内容。错误可能在来源、提取或客户端展示,数据库中有记录不代表最终阅读结果正常。批量修改前用少量样本验证,避免把整个库的抓取设置一起改坏。
备份数据库,OPML 留作额外出口
完整数据库备份保留用户、订阅、文章和状态。OPML 适合把订阅交给另一个阅读器,定期导出也有价值,但不能替代数据库。备份前短暂停止应用,让本批数据保持一致,再通过仍在运行的 PostgreSQL 导出。
cd /opt/miniflux
mkdir -p /opt/backups/miniflux
docker compose stop miniflux
docker compose exec -T db pg_dump -U miniflux -d miniflux -Fc > /opt/backups/miniflux/database.dump
tar -czf /opt/backups/miniflux/config.tgz compose.yaml .env
docker compose start miniflux
这是一次备份的文件名示例,长期使用为每次建立日期目录,成功以后再清理过期副本。导出失败也要恢复应用,并查明错误;一个空 dump 文件没有恢复价值。副本复制到另一处存储,保护里面的私人订阅、账号和配置密钥。
恢复时创建独立数据库,再用对应 PostgreSQL 工具导入,应用使用备份对应版本,临时端口保持私人。检查分类、收藏、已读状态和新抓取的一条文章。恢复出的数据库不能继续和正式实例共用,否则两边会互相写入。
使用一段时间以后,把长期不看的来源移出常用分类。RSS 阅读器没有必要让未读数永久归零;留下真正需要的更新,并让来源和数据能够迁移,才容易持续使用。