自托管RSS阅读器FreshRSS:用Docker十分钟搭建独立信息入口

发布时间:2026/9/24 9:56:44
自托管RSS阅读器FreshRSS:用Docker十分钟搭建独立信息入口 这几年的资讯消费说实话是越来越让人心累了。不管打开哪个App都是算法替你决定今天该看什么。刷了一小时回头一看全是同类内容真正想看的反而被淹没在信息洪流里。我也是在某个深夜翻完一个又一个信息流之后萌生了一个念头把阅读这件事重新握在自己手里。于是就有了今天要聊的主角——FreshRSS一个可以自托管、数据完全属于你自己的RSS阅读器。这篇博文我会从部署思路、Docker实践、日常使用到问题排查完整走一遍保证你跟着操作十分钟内也能拥有一个独立的阅读入口彻底告别“推荐什么看什么”的被动状态。我最早用RSS还是Google Reader的年代那时候订阅、分类、标记已读清爽得不行。后来Google Reader关停整个RSS生态经历了一段沉寂期很多朋友转向了今日头条、微信公众号这类内容聚合平台我也一样。但时间长了你会发现算法推荐本质上是在“驯化”你的注意力它不断猜测你喜欢什么然后只让你看到那些内容。这种模式不是不好而是不适合所有人。如果你是想认真做信息输入的创作者、研究者、或者纯粹想保持独立思考的读者RSS的价值会重新浮现。FreshRSS就是其中一个特别适合自部署的项目。它本身是PHP写的开源、轻量、支持多用户底层可以跑SQLite也可以跑MySQL。界面干净功能不花哨但够用最关键的是它提供一个兼容Google Reader的API手机上可以搭配Reeder、FeedMe这类第三方客户端使用。换句话说搭建好之后你不仅在电脑上能看手机上也能随时同步体验完全不输给商业服务。这次我用的部署方案是Docker Compose这也是目前自托管服务最主流的玩法。后面我会把整套配置、步骤、踩过的坑全部展开说清楚包括那些官方文档里没写明白的细节。不管你是第一次接触Docker还是已经玩过一段时间看完这篇都能顺利跑起来。1. 为什么自建RSS阅读器FreshRSS在众多方案里的定位1.1 算法推荐与RSS的本质差异先聊一个核心问题算法推荐和RSS到底差在哪算法推荐的工作方式是“收集你的行为数据→建立用户画像→筛选内容→推送给用户”。它的目标不是让你看到更全面的世界而是让你在App里待得更久。所以你刷到的内容高度同质化观点越来越趋同你以为自己在“获取信息”其实是在不断验证自己已有的认知。时间久了人会变得信息偏食。RSS的机制完全反过来你主动选择信息源你决定订阅谁、不订阅谁阅读器只负责把订阅源的新内容抓取回来按时间顺序排好。没有猜你喜欢没有千人千面没有“划掉一个又出现一个”的推荐流。你要做的只是定期打开阅读器把没看的内容看完或者直接标记全部已读。这种阅读方式天然就是对抗算法焦虑的。当然RSS不适合所有人。它要求你有一点信息来源管理的意识需要你偶尔花点时间维护订阅列表。但对于写博客的人、靠信息吃饭的运营、做技术调研的工程师或者单纯厌倦推荐流的普通读者RSS依然是目前最可靠的信息获取方案。FreshRSS的意义就是把这个方案变成一个完全由你掌控的私有服务。1.2 FreshRSS与主流自托管阅读器对比自托管RSS阅读器其实有好几个选择除了FreshRSS常见的还有Miniflux、Tiny Tiny RSSTT-RSS、Stringer等。我最终选了FreshRSS原因可以看下面这张表格对比维度FreshRSSMinifluxTiny Tiny RSS开发语言PHPGoPHP安装复杂度低容器镜像完善低单二进制文件中依赖较多资源占用较低需PHP-FPM极低中等多用户支持支持不支持支持API兼容Google Reader APIFever API自己的API移动端生态Reeder、FeedMe等官方客户端、Reeder官方App为主UI风格传统但可定制极简极客风传统扩展机制有官方扩展仓库少量内置扩展有插件机制Miniflux我很喜欢它的轻量和极简风格但它是单用户设计而且没有官方移动客户端想在手机上看还得通过浏览器或者第三方渠道比较折腾。Tiny Tiny RSS功能确实丰富但历史包袱重、部署依赖多Docker方案也没FreshRSS清爽。FreshRSS是三者里平衡点最好的部署足够简单默认就带API手机客户端直接支持扩展机制也成熟可以作为长期主力方案。另外一个选FreshRSS的原因是它的社区非常活跃。项目在GitHub上常年保持更新官方镜像发布频率也很高安全漏洞修复及时。自托管服务最怕作者弃坑FreshRSS在这一点上让人放心。2. 部署前的准备方案选型与运行环境2.1 为什么选择Docker Compose部署FreshRSS的官方文档提供了两种主要安装方式传统方式需要LNMP环境Linux Nginx MySQL/MariaDB PHP得手动装PHP扩展、配Nginx站点、创建数据库流程繁琐还容易踩坑Docker方式把整个运行环境打包进镜像拉下来就能跑省去了大量环境配置的重复劳动。我强烈建议走Docker Compose这条路。原因有三点第一可复现性。你把docker-compose.yml文件保存好以后无论换服务器还是重新部署只要一份配置文件就能恢复整套服务不用再去回忆当初“那个PHP扩展到底装没装”。第二隔离干净。FreshRSS跑在容器里不会污染宿主机的系统环境。你今天部署FreshRSS明天再跑一个Nginx或数据库互不干扰。第三升级方便。镜像发布新版本后docker compose pull docker compose up -d两条命令完成升级。如果是传统环境你得手动备份、替换文件、检查扩展兼容性费时费力。不过Docker部署也有个容易被忽视的前提你的服务器最好有1GB以上内存512MB以下内存跑FreshRSS会有点吃力尤其是PHP-FPM进程同时处理抓取任务时。1核2G的入门级云主机跑FreshRSS完全够用我自己的部署环境就是这样一个“小水管”配置跑得很稳。2.2 需要准备哪些基础环境开始之前确认这几样东西你已经准备好一台可公网访问的服务器操作系统不限Ubuntu 22.04/Debian 12/CentOS 7都可以一个域名非必需但强烈建议后续配HTTPS、手机客户端连接都需要用IP也能跑但体验和安全性差一截Docker Engine和Docker Compose插件这是最核心的依赖一个趁手的终端工具SSH连上服务器用服务器购买这里不多展开选一个你用得顺手的云厂商就好。域名的话建议用便宜一点的.xyz或.top后缀反正只是自用没必要花大价钱。Docker的安装可以参考官方脚本也可以直接用发行版自带的软件源安装。装完之后记得验证一下版本docker --version docker compose version如果docker compose提示命令不存在可能是Docker版本较旧需要安装docker-compose-plugin或者直接用老款的docker-compose命令。后面我提供的配置都以新版docker compose中间带空格为准。2.3 数据存储选SQLite还是MySQLFreshRSS支持两种数据库SQLite和MySQL/MariaDB。不少新手在这一步犹豫我直接给结论单用户或者两三个人自用选SQLite就够了需要多人同时使用、追求高并发写入性能才考虑MySQL。SQLite的好处是零配置数据就是一个文件备份直接复制文件就行。FreshRSS官方镜像默认就用SQLite存数据启动后自动初始化不需要额外起一个数据库容器。缺点是不适合高频并发写入但个人使用场景根本到不了那个量级。MySQL的好处是性能上限高、支持网络访问、便于搭建主从备份但你必须单独跑一个MySQL容器还要处理初始化账号、库、权限复杂度明显上升。我后面会给出SQLite“极简版”和MySQL“完整版”两套dokcer-compose配置你按需选择。3. FreshRSS一键部署实操从拉镜像到初始化完成3.1 极简版SQLite FreshRSS单容器先上一个最简配置适合想快速体验的朋友。在服务器上创建一个目录比如/opt/freshrss然后新建docker-compose.yml文件内容如下services: freshrss: image: freshrss/freshrss:latest container_name: freshrss hostname: freshrss restart: unless-stopped ports: - 8080:80 environment: TZ: Asia/Shanghai CRON_MIN: */15 * * * * TRUSTED_PROXY: 127.0.0.1 volumes: - ./data:/var/www/FreshRSS/data - ./extensions:/var/www/FreshRSS/extensions说几个关键配置项image我直接用latest标签这是官方推荐的日常使用标签会跟随最新稳定版更新。如果你追求极致稳定也可以固定一个具体版本号比如freshrss/freshrss:1.24.1但升级时需要手动改标签稍微麻烦一点。CRON_MIN这个环境变量很关键。FreshRSS需要定时去订阅源抓取新内容容器内置了一个cron调度器*/15 * * * *表示每15分钟刷新一次订阅。如果你订阅的源更新频繁可以改成*/5 * * * *但别太频繁否则容易触发部分网站的抓取频率限制。/var/www/FreshRSS/data和/var/www/FreshRSS/extensions是数据目录和扩展目录必须挂载到宿主机。这是整个部署中最核心的部分以后你的订阅数据、数据库文件、用户配置都在宿主机./data下容器挂了数据也不会丢。启动之前先把宿主机目录权限准备好mkdir -p data extensions chmod -R 777 data extensionsFreshRSS容器内的PHP-FPM进程默认以www-data用户运行UID是33和宿主机用户的UID大概率不一致。如果不给data和extensions目录足够权限容器创建数据库时会报权限错误。用777权限是偷懒做法但胜在省事个人服务器上问题不大。更优雅的方案是chown -R 33:33 data extensions但不同环境下UID可能不同所以我更推荐用777实测稳定。一切都准备好之后直接docker compose up -d docker compose logs -f看到日志里出现类似Finished startup或者Nginx/PHP-FPM相关的启动信息说明容器已经起来了。这时候浏览器访问http://你的服务器IP:8080就能看到FreshRSS的安装页面。3.2 初始化向导的完整流程看到FreshRSS安装界面后流程非常简单但有几个细节值得注意。第一步是选择语言默认有中文界面。第二步是设置管理员账号用户名、密码、邮箱途中会提示你选择数据库类型如果你用的是3.1的极简配置选SQLite就行。有个容易让新手困惑的地方是“FreshRSS的URL地址”这一栏。如果是用IP端口访问就填http://你的IP:8080如果已经配了域名就填https://你的域名。这个地址会写入配置文件中影响后续生成订阅链接和移动端连接最好一次性填对反悔了就得改数据库或者重新安装。点击“安装”之后FreshRSS会初始化数据库、创建管理员账号、写入配置文件全程大概几秒钟。完成后会自动跳转到登录页用刚创建的管理员账号登录就能看到FreshRSS的主界面了。第一次进入会有一个欢迎引导提示你添加订阅。你可以先随便添加一个博客或新闻站的RSS源试试抓取效果。比如很多技术博客都默认输出RSS直接在“订阅管理”里输入站点地址FreshRSS会自动尝试发现RSS地址也可以直接粘贴完整的feed URL。到这一步一个能用的FreshRSS已经跑起来了。但离“手机上也顺滑阅读”还有一段距离下面要解决访问入口和客户端连接的问题。3.3 完整版配合Nginx反向代理与HTTPS直接用IP端口访问虽然能用但有几个问题一是端口号不好记二是没有HTTPS加密三是部分RSS源对非标准端口的抓取请求不友好。所以只要你有域名我都建议配一层反向代理。反向代理的方式很多Caddy是其中最省心的一个——它支持自动申请和续期HTTPS证书配置文件也短。不过Nginx用户群体更大网上资料丰富这里我主要讲Nginx方案。首先改docker-compose把FreshRSS的端口从映射到宿主机改为仅内部访问避免绕过Nginx直接暴露到公网。修改后的配置services: freshrss: image: freshrss/freshrss:latest container_name: freshrss hostname: freshrss restart: unless-stopped expose: - 80 environment: TZ: Asia/Shanghai CRON_MIN: */15 * * * * TRUSTED_PROXY: 127.0.0.1 volumes: - ./data:/var/www/FreshRSS/data - ./extensions:/var/www/FreshRSS/extensions注意这里的expose只对Docker内部网络开放宿主机直接访问不到8080端口了需要由Nginx转发到容器。同时把TRUSTED_PROXY改为127.0.0.1告诉FreshRSS“信任来自反向代理的请求”这样它才能正确判断用户真实IP日志和会话管理都更准确。然后宿主机安装Nginx配置一个站点server { listen 80; server_name rss.example.com; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name rss.example.com; ssl_certificate /etc/nginx/cert/rss.example.com.pem; ssl_certificate_key /etc/nginx/cert/rss.example.com.key; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }证书可以用Lets Encrypt自动申请也可以用你云厂商的免费证书正规流程是先用certbot生成证书再指向对应路径。这里我不展开证书签发细节只提醒一句证书到期前一定要设置自动续期手动续期你迟早会忘。配好Nginx之后重启服务docker compose down docker compose up -d nginx -t systemctl reload nginx然后访问https://rss.example.com如果一切正常你会看到FreshRSS的登录界面地址栏有小锁标志说明HTTPS已经生效。3.4 移动端连接用Reeder和FeedMe实现随时随地阅读电脑上阅读只是RSS的一半体验手机上刷订阅才是真正的日常。FreshRSS的杀手锏就是兼容Google Reader API这意味着大量第三方阅读器可以直接连过来。iOS上我推荐Reeder 4这算是RSS阅读器的老牌选手界面简洁、手势操作顺手支持FreshRSS的API接入。安卓上推荐FeedMe免费开源功能很全尤其对FreshRSS的兼容性调教得很用心。连接方法大同小异以FeedMe为例打开应用选择“FreshRSS”类型填写你的FreshRSS地址比如https://rss.example.com/api/再填用户名和密码测试连接通过后就能同步所有订阅和未读状态。Reeder的添加方式稍微不同需要手动选择“Google Reader API”类型但原理一样。这里有一个坑如果你用的是IP访问移动端连接大概率会失败因为部分客户端强制要求HTTPS。这也是我反复强调“配置域名和HTTPS”的原因之一。另外FeedMe里如果要推送通知需要额外配置FreshRSS的扩展这个后面再说。4. 日常使用与进阶玩法让FreshRSS真正成为阅读中枢4.1 订阅源管理导入、分类与统一管理初始化完成之后第一件事不是急着订阅而是把已有的RSS源迁移过来。如果你之前用Feedly或者Inoreader这类在线服务可以直接从那边导出OPML文件然后在FreshRSS里导入。OPML是RSS阅读器的通用交换格式一串XML记录了你的订阅列表和分类结构。导入路径在FreshRSS后台“订阅管理”里上传OPML之后分类和订阅会原样保留。我当初从Feedly导出再导入FreshRSS整个过程不到一分钟几百个订阅一个没漏。这个功能对迁移用户特别友好基本没有迁移成本。分类这件事我建议做得克制一点。我见过有人建二十多个分类最后阅读效率反而低。合理的分类策略是按“用途”切分而非“领域”切分。比如“每日必读”高频重点源、“普读”可看可不看、“稍后读”囤着不动、“行业监控”竞品动态。这样打开阅读器先看“每日必读”看完再扫其他哪怕几百上千个订阅也能轻松消化。FreshRSS的标签tag系统也值得利用一条订阅可以同时属于多个标签比单一分类灵活。比如一个技术博客既打上“前端”标签又打上“每日必读”在标签视图里就能交叉筛选。不过标签功能在移动端支持一般主力还是分类。4.2 提升抓取质量全文抓取与内容清洗RSS有一个让人头疼的历史问题很多源默认只输出摘要不输出全文。你明明订阅了却只能在阅读器里看到开头两行话想读全文还得点进原网站。在电脑端还行手机上就非常难受了。FreshRSS对这个问题有解决方案。官方扩展仓库里有一个扩展叫“全文抓取”真正名称是freshrss_extensions里的Full text feed也有其他第三方开源方案安装后可以对所有订阅源启用全文抓取。原理是拿到摘要后自动请求原文页面再把正文内容提取出来显示在阅读器里。启用方法把扩展代码放进extensions目录然后在后台“扩展管理”里启用即可。注意全文抓取会显著增加服务器负载和带宽消耗因为每次刷新都要额外抓取原文页面。如果你的VPS配置不高建议只对真正只出摘要的源启用别搞全局。另外一个问题是噪音条目。有些源会定期发一些无关广告或者凑数内容FreshRSS的过滤规则即filter可以自动把符合关键词的文章标记已读或隐藏。比如你在筛选规则里设置“标题包含‘促销’则标记已读”以后这类内容就不会打扰你了。4.3 数据备份与迁移数据是你的核心资产自托管服务最重要的就是数据安全RSS阅读器的数据主要指你的订阅列表、已读状态和用户配置。FreshRSS的数据都存在data目录里SQLite模式下就是一个db.sqlite文件。备份这个文件就够了。我写了个简单的cron任务每天凌晨打包整个data目录并上传到对象存储万一服务器挂了新机器上一恢复订阅和已读状态全都在。脚本不复杂核心命令就一行tar czf freshrss-backup-$(date %F).tar.gz -C /opt/freshrss data extensions恢复时把压缩包解压回原目录再docker compose up -d完事儿。这里强调一点不要只备份db.sqliteextensions目录里可能有你装的扩展配置data目录下还有其他配置文件整个目录一起打包最稳妥。4.4 结合AI大模型给RSS加一个“摘要大脑”最近大家都在聊本地部署大模型其实RSS阅读器和大模型的结合点很多。FreshRSS虽然没有内置AI功能但它的API和扩展机制允许你接入外部服务。比如可以用FreshRSS的过滤钩子把每篇文章的标题和摘要发到本地部署的大模型接口让模型生成一句话精华再输出回阅读器。我试过最简单的实现写一个Python脚本定时从FreshRSS API拉取未读文章调用本地Ollama跑一个轻量模型做摘要再把摘要存到一个单独的订阅源里。这样打开阅读器先扫一眼AI摘要觉得值得细读再点开全文。相当于给信息流加了一层“人工预筛”。如果你也用Ollama这类本地推理工具可以顺手研究下。不需要把摘要写回FreshRSS数据库建一个子账号挂个自定义RSS源也能实现类似效果。思路不难就是脚本做中转关键是别把抓取频率设太高免得把API打挂。5. 常见问题与排查技巧实录5.1 容器权限导致无法初始化数据库这是我在各种部署群里看到最多的问题。现象是访问安装页面之后点安装提示无法写入数据库或者报Failed to create database。绝大多数是因为data目录的权限不对。FreshRSS容器里的PHP进程以www-data用户运行UID为33而宿主机新建的目录默认归root所有。解决方法是把data和extensions目录的所有者改为33或者直接777chmod -R 777 /opt/freshrss/data /opt/freshrss/extensions改完再重新执行一次docker compose up -d然后刷新页面重试。注意如果数据目录里已经存在损坏的初始化文件建议清空data目录再装否则可能会复现同样的问题。5.2 定时刷新不生效FreshRSS的CRON_MIN环境变量只设置了一个刷新周期但容器里的cron服务默认被某些镜像精简了。如果你发现新文章很久都不出现先看一下容器日志docker logs freshrss | grep cron如果没有任何cron输出说明定时任务没有跑起来。这时候可以试试在老版本镜像上改用宿主机cron的方式定期执行docker exec freshrss php /var/www/FreshRSS/cli/actualize-user.php --user 你的用户名手动触发抓取。不过新版本镜像一般没这个问题我自己用latest标签一年多刷新一直很准时。5.3 部分订阅源抓取不到内容遇到这种情况第一步先确认源本身是否还能访问。RSS源“失联”是常态好端端的站点可能突然关闭了输出或者调整了路径。可以在FreshRSS后台“订阅管理”里更新该源看返回的状态码是200还是404。如果源输出正常但还是抓不到可能是源服务器的TLS版本太老而FreshRSS容器内的PHP默认要求较高的加密协议。这种情况很少见但出现过。另一个常见原因是源站有反爬策略对非浏览器UA请求直接拒绝。FreshRSS允许为每个订阅源自定义User-Agent和HTTP头可以在源设置里伪装成浏览器的UA试试比如Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36如果还是不行就考虑用全文抓取或者RSSHub这类中间服务来生成可用的RSS源。5.4 迁移服务器后登录态失效FreshRSS的会话数据默认也存放在data目录里迁移时如果不完整复制整个data目录登录状态、API Token都会丢失。所以备份必须包含整个data目录别只想着数据库文件。迁移后如果登录提示“会话无效”先确认备份完整必要时在后台重新生成用户API Token手机会自动重新要求授权。6. 写在最后的一点经验FreshRSS我用了快两年最直观的感受是阅读习惯真的会被重建。刚开始从推荐流切回RSS时总担心错过什么。后来发现真正重要的信息基本不会因为你晚看到24小时就失去价值反而是被算法推着走的日子每天都处在一种“不看就落后”的错觉里。搭建FreshRSS本身不是难事难的是你愿意为“自己掌控信息输入”这件事花一点时间。现在部署方案已经做到了十分钟级别移动端有成熟的客户端数据完全握在自己手里没有广告没有隐私采集没有任何人猜你想看什么。这种感觉用过的人都会懂。如果你手头正好有一台闲置的服务器或者正准备尝试自托管FreshRSS是一个绝佳的入手项目——轻量、实用、一次搭好能用很多年。