FriRSS

FriRSS

Docker app from Fripix's Repository

Overview

FriRSS is a modern, self-hosted, customizable web frontend for FreshRSS. It provides a desktop interface and mobile PWA, full-text extraction, offline reading, multiple languages, themes and extensive interface customization.

FriRSS

Your FriRSS, your rules.

A self-hosted, customizable web frontend for FreshRSS.

Français  English  Deutsch  Español  Italiano  Português  Nederlands  Polski  Українська  简体中文

Release CI Security License

GHCR Docker pulls Unraid Community Apps amd64 and arm64

Preview

FriRSS on desktop
FriRSS as a PWA on a phone    FriRSS on a tablet

Live demo

frirss.fripix.ch

Username fridemo  ·  Password 6wVQj3Q@2Qz@

  • Runs on a small server with no Redis cache.
  • Feeds cannot be added or removed, and no FreshRSS server can be connected.
  • Preferences reset every 30 minutes, so themes and layout return to their defaults.
  • Read state and favorites are shared with everyone else visiting.

The demo is there to try the interface.

About

FreshRSS has an excellent engine: solid, self-hosted, great at fetching and storing feeds. Its built-in interface just wasn't for me, and none of the alternatives fit what I wanted — so I built my own.

FriRSS sits on top of FreshRSS through its Google Reader API and replaces only the interface.

A note in the interest of honesty: this is a personal project and I'm not a developer. FriRSS was built largely by "vibe coding" — describing what I wanted to an AI assistant and iterating. Use it in that spirit.

Features

Reading

  • Three panes on desktop, and an installable PWA on mobile with swipe navigation, swipe actions and pull-to-refresh.
  • Full-text extraction (Readability) when a feed only ships a summary — cached, so re-reads are instant. On a feed set to auto-extract, the next articles are warmed ahead of you, images included, so swiping lands on a page that is already whole.
  • Offline reading. Articles stay readable without a connection, images included. Favorites and read-later are kept automatically; a one-tap Prepare offline sweep covers the last 30 days across every feed.
  • Favorites, read-later, read/unread, with all / unread / favorites filters. The unread-only choice is remembered per feed — or, if you prefer, one choice applies to every feed (Preferences → General → Unread filter).
  • Ticking an article read clears it from the unread list — instantly, in every layout including the compact one. Opening an article leaves its row where it is, so you keep your place; only a deliberate tick removes one. If the server refuses the write, the row comes back and says so.
  • Open an article at its source — an icon on each row opens the origin site in a new tab and selects the article, so it is marked read and keeps its place in the list. For the feeds you would rather read on the site itself.
  • Right-click an article — or long-press it on touch, or press the keyboard Menu key — for a menu: open at source, mark read or unread, favorite, read later, copy link. On a phone it opens as a bottom sheet; a middle click opens the article at its source directly.
  • Search and infinite scroll, scoped to the view you are actually in — and the last five queries are offered back, per server.
  • Mark as read while scrolling, optionally: an article is marked once it has left the top of the list. Off by default, never during a search.
  • Share an article or copy its link from the reading pane — the system share sheet on mobile, the clipboard elsewhere.
  • Resume where you left off — your last feed and filter come back on reopen, with an unobtrusive offline / back-online indicator.

Keyboard and reach

  • A command palette on ⌘K / Ctrl+K: jump to a feed, a category, a label, a view, switch FreshRSS server, or run an action. Accents are ignored, so securite finds Sécurité.
  • Ten reassignable shortcuts, and ? opens a sheet listing them with the keys you actually configured.
  • Menus by long press on touch — a feed in the sidebar opens its menu (rename, open site, auto-extract, unsubscribe), an article opens its own, with 44pt rows.
  • Built to be used without a mouse — a visible focus ring throughout, 44pt touch targets, screen-reader names on icon-only buttons, and reduced motion honored everywhere.

Make it yours

Almost everything is yours to tweak:

  • Six themes ship with it — Default, Riso, Paper and High Contrast in light, Night and Desk in dark — and FriRSS can follow your system's light/dark setting, switching between the two you pick.
  • Themes are yours to make — full control over all 36 colors and 7 font sizes; create, save, export, import and share your own.
  • Element colors — recolor the sidebar, accents, panels, links, article text and more, individually.
  • Font sizes — independent sizes for article titles, summaries, source names and the reading body.
  • Layout — resizable columns, density and spacing, date separators, feed icons, toggles for the source label and top bar, and a desktop/mobile switch on tablets.
  • Row action icons — show or hide each of the four icons on an article row (favorite, read later, open at source, mark read), one by one, from the new Layout section.
  • A sidebar that remembers — collapsed sections stay collapsed, and feeds with nothing unread can be hidden to declutter long lists.
  • Categories & feeds — rename or delete a category and move feeds between categories, right from the preferences.
  • Labels & sub-labels — a nestable tagging system: create, rename, color, drag to organize, group under parents, with per-label article counts.
  • Branding — set your own app name and logo.
  • 10 languages — the interface follows your browser language on first run.

Accounts, SSO & multi-server

  • Multi-user with admin/user roles — each person keeps their own feeds and settings.
  • Single sign-on via OIDC (tested with Authentik); existing accounts are linked by email, and passkeys work through your provider.
  • Multi-server — connect several FreshRSS instances and switch between them, from the top bar or the command palette.

Self-hosting

  • Optional Redis cache (stale-while-revalidate) and a background pre-fetch worker for instant loads.
  • Encrypted backup and restore — one passphrase-protected file rebuilds a whole instance, including on a brand-new one.
  • FreshRSS tokens encrypted at rest; same-origin proxy (no CORS) with an anti-SSRF guard.
  • Ships as a single Docker image (nginx + Node) with SQLite storage, built for amd64 and arm64 (Raspberry Pi, 64-bit OS).

Installation

FriRSS is a frontend for FreshRSS, so you need a running FreshRSS instance with the Google Reader API enabled (Settings → Authentication → Allow API access).

mkdir -p frirss-data
docker run -d --name frirss \
  -p 8080:80 \
  -v "$PWD/frirss-data:/app/data" \
  -e TZ=Europe/Zurich \
  ghcr.io/fripix/frirss:latest

The database lands in a frirss-data folder right where you ran the command. The same image is on Docker Hub as fripix/frirss:latest if you prefer it, and a ready-to-edit docker-compose.yml is included.

Then open http://localhost:8080 and create the first account — it becomes the administrator. Registration then closes: new instances refuse sign-ups by default, and you open them again from Preferences → Administration when you want to invite someone. Connect your FreshRSS server next: its URL, your FreshRSS username, and the API password from FreshRSS → Settings → Profile.

Is your FreshRSS on a private address? Most self-hosted instances are — http://192.168.1.20:8080, http://freshrss on a Docker network, anything your router alone can reach. The backend refuses private targets by default, so a logged-in user cannot aim it at your NAS, your database, or a cloud metadata endpoint. Trust your FreshRSS host explicitly and the block lifts for that one address:

-e PROXY_INTERNAL_HOSTS=192.168.1.20

Skip it and connecting fails while the container log shows POST /api/proxy 403. Public FreshRSS URLs need nothing.

The first launch generates the JWT secret and the token-encryption key and stores them in the database, so backing up the data folder backs up everything — see Backups.

Configuration

Variable Description Default
PUID User id the Node process runs as (the data directory is adopted on start) 1000
PGID Group id for the same process 1000
FRIRSS_BASE_URL Public base URL — recommended behind a reverse proxy; fixes OIDC redirect URIs derived
FRIRSS_DATA_DIR SQLite database directory /app/data
PROXY_REWRITES public→internal URL rewrites for the backend proxy (from=to, comma-separated)
PROXY_INTERNAL_HOSTS Anti-SSRF allowlist: private hosts the proxy may reach directly, comma-separated. Needed when FreshRSS sits on a LAN or Docker address
REDIS_URL Enables the read cache (stale-while-revalidate); empty disables it
CACHE_ARTICLES_PER_FEED Articles kept per feed in the cache 50
CACHE_TTL Cache key expiry, in seconds 86400
CACHE_SYNC_INTERVAL Background pre-fetch interval in minutes (0 disables; needs REDIS_URL) 0
CACHE_SYNC_ACTIVE_DAYS Only pre-fetch for users seen in the last N days 7
CACHE_SYNC_PARALLEL_USERS Users pre-fetched in parallel 3
FRIRSS_REFRESH_MAX_FEEDS Number of feeds to refresh per button press (non-integer or < 1 → default) 1000
FRIRSS_PROXY_RATE_LIMIT Proxied and article-extraction requests allowed per user per minute — one shared budget (0 disables; non-integer or negative → default) 600
CORS_ORIGIN Allowed CORS origin(s) — only for split front/back deployments

Since 1.4.10 the server extracts article text: the browser asks GET /api/extract first, and falls back to extracting the page itself only when that route is missing (an older backend), refuses the page, is busy, or cannot reach it. With REDIS_URL set the result is cached by URL rather than by account, so a page is extracted once for the whole instance instead of once per device: a second device, or another user reading the same feed, gets it instantly. Devices asking for the same cold page at the same moment are coalesced onto a single extraction, so the origin site sees one request rather than one per reader. Without Redis the route still runs — it simply keeps nothing, so the next device pays for the extraction again. Either way the work now happens on the server rather than on every phone, so budget for it: parsing a page blocks the single Node process that serves the whole instance for tens of milliseconds up to about a second on the largest pages. That work is bounded — one page is parsed at a time and at most five requests may be in flight or waiting, beyond which the server says so and the browser extracts locally — but the bound is a queue, not extra capacity: a busy instance is one where phones do the parsing again.

Single sign-on is configured at runtime in Preferences → Administration (issuer, client ID, client secret).

Backups

Two things are worth backing up, and they are not the same thing.

The encrypted export

Preferences → Administration → Backup produces a single file holding everything FriRSS knows about itself: accounts and their password hashes, the configured FreshRSS servers, their tokens and the key that decrypts them, preferences, and instance settings. It does not hold your articles — those live in FreshRSS.

That file is therefore enough to impersonate every account on the instance, which is why a passphrase is mandatory (12 characters minimum). Lose the passphrase and the file is permanently unusable. There is no recovery path, by design; keep it somewhere other than next to the file.

Restore it from the same screen, or from the first-run screen of a fresh instance — which makes it a migration tool as much as a backup. Either way you see what the file contains before committing: when it was made, which version produced it, how many accounts and servers. Restoring replaces the instance's contents entirely and signs everyone out.

Environment variables are recorded in the file and shown at restore time, but never applied: they belong to the deployment, not to the backup. Copy them into your compose file yourself.

The data directory

/app/data holds frirss.db and its write-ahead log — the JWT secret and the token-encryption key included. Backing up that volume backs up the instance.

To take a copy out of a running container, use the snapshot script rather than cp:

docker exec frirss node scripts/backup-db.js /app/data/backups

It goes through SQLite's .backup() API: atomic, and safe while the server is writing. Copying frirss.db on its own is not a valid backup — the most recent writes live in the -wal file beside it.

Locked out of every admin account? docker exec -it frirss node scripts/reset-password.js sets a new password from the terminal.

Security

FriRSS holds the credentials to your FreshRSS server, so a few things are not optional:

  • FreshRSS passwords and tokens are encrypted at rest and never reach the browser — not in a response, not in a URL, not in a log.
  • All FreshRSS traffic goes through an authenticated same-origin proxy with an anti-SSRF guard: it rejects targets that resolve to a private address, and re-checks every redirect hop.
  • The app page and its static assets carry a Content-Security-Policy (script-src 'self') along with X-Frame-Options, X-Content-Type-Options and Referrer-Policy.
  • The backend runs unprivileged in the container (PUID/PGID, 1000 by default): code execution inside Node no longer owns the data directory, where the JWT secret and the token-encryption key live. The data directory is adopted on start, so upgrading needs no action.
  • Registration is closed by default. Only the very first account — the administrator — can be created without someone opening sign-ups in Preferences → Administration.
  • Proxied requests are rate-limited per user (FRIRSS_PROXY_RATE_LIMIT), so an account cannot turn the backend into an open relay.
  • JWT verification is pinned to HS256, and the runtime image ships neither npm nor its dependency tree — nothing there is executed, and it was the source of most reported CVEs.

Dependency and image scans run on every push and can be launched by hand; the current state is in GitHub Actions.

Found a security issue? Please follow SECURITY.md rather than opening a public issue.

Feedback & Contributing

FriRSS is a personal project, but feedback, ideas and contributions are welcome.

  • Found a bug, or have an idea? Open an issue — there are guided forms for both.
  • A question, or something to talk through? Start a discussion
  • Want to contribute code? Pull requests are welcome.
  • Wondering what changed? CHANGELOG.md

Buy Me a Coffee

License

MIT

Install FriRSS on Unraid in a few clicks.

Find FriRSS in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.

Open the Apps tab on your Unraid server Search Community Apps for FriRSS Review the template variables and paths Click Install

Requirements

A running FreshRSS instance is required.

Categories

Download Statistics

1,051
Total Downloads

Related apps

Explore more like this

Explore all

Details

Repository
ghcr.io/fripix/frirss:latest
Last Updated2026-08-22
First Seen2026-08-10

Runtime arguments

Web UI
http://[IP]:[PORT:80]
Network
bridge
Shell
sh
Privileged
false

Template configuration

WebUI PortPorttcp

Port used to access the FriRSS web interface.

Target
80
Default
8787
Value
8787
App DataPathrw

Persistent FriRSS data, including the SQLite database.

Target
/app/data
Default
/mnt/user/appdata/frirss
Value
/mnt/user/appdata/frirss
PUIDVariable

User ID the FriRSS process runs as. Unraid uses 99 (nobody).

Default
99
Value
99
PGIDVariable

Group ID the FriRSS process runs as. Unraid uses 100 (users).

Default
100
Value
100
NODE_ENVVariable

Node.js environment.

Default
production
Value
production
FRIRSS_DATA_DIRVariable

Internal FriRSS data directory.

Default
/app/data
Value
/app/data
TimezoneVariable

Container timezone, for example Europe/Zurich or America/New_York. Leave empty to use UTC.

Target
TZ
Base URLVariable

Public URL of your FriRSS instance, for example https://rss.example.com.

Target
FRIRSS_BASE_URL
Redis URLVariable

Optional Redis connection URL for shared caching.

Target
REDIS_URL
Cache Sync IntervalVariable

Background sync interval in minutes, for pre-filling the cache with no browser open. Requires the Redis URL above: without it, this value does nothing. Set 0 to disable.

Target
CACHE_SYNC_INTERVAL
Default
15
Value
15
FreshRSS Internal HostVariable

Set this if FreshRSS runs on a private address - 192.168.x.x, 10.x.x.x, or a container name on a shared Docker network. FriRSS blocks private targets by default so that a logged-in user cannot aim it at your NAS, your database or a cloud metadata endpoint; listing your FreshRSS host here allows that one address. Comma-separated. Leave empty when your FreshRSS URL is public. Without it, connecting fails and the container log shows: POST /api/proxy 403

Target
PROXY_INTERNAL_HOSTS
Proxy RewritesVariable

Optional. Rewrites a public FreshRSS URL to an internal one, as from=to, comma-separated. Useful when FriRSS and FreshRSS share a Docker network: it cuts the round trip through your reverse proxy. Hosts listed here are trusted automatically.

Target
PROXY_REWRITES