Self-hosted service
stringer-rss/stringer avatar
stringer-rss/stringer

Stringer: a self-hosted RSS reader with no recommendations and no social layer

A self-hosted, anti-social RSS reader.

4,133 stars390 forksRubyMIT

At a glance

What is it?
Stringer is a Rails and PostgreSQL feed reader that runs on your own server and implements a Fever API clone for mobile clients. Its appeal is what it refuses to do: no machine learning, no sharing, no external dependencies beyond the database.
Who is it for?
Adopt Stringer if you want a small Rails feed reader you control, you already run PostgreSQL, and you care more about keyboard shortcuts than recommendation feeds. Do not adopt it if you want a hosted reader with no server to maintain, or if you expect a broad plugin ecosystem.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 27, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What Stringer is for, and who should care

Stringer describes itself as a self-hosted, anti-social RSS reader. The phrase is doing real work. The README states plainly that the project has no external dependencies, no social recommendations or sharing, and no machine learning algorithms. What it does have, per the same document, is keyboard shortcuts.

That positions it against readers that treat your subscriptions as a signal to be mined. If you want an unread list that stays an unread list, the design goal and your goal line up. If you want article suggestions based on what your friends read, this project has deliberately declined to build that.

The audience is narrow but identifiable: people who already run a server, are comfortable with a Ruby application and PostgreSQL, and want their reading list off someone else's infrastructure. The repository layout backs this up. There is a Dockerfile, a docker-compose.yml, a Procfile, and a docs/ directory with separate files for Heroku, VPS, Docker and OpenShift deployment. Four deployment targets is a lot of surface area for a personal reader, and it tells you the maintainers expect you to pick your own host rather than use theirs.

The stack: Rails, PostgreSQL, Hotwire and GoodJob

The README names the components: a Ruby app based on Rails, PostgreSQL, Hotwire (Turbo and Stimulus) and GoodJob. Feed parsing is credited to feedjira, and feed discovery to feedbag. Those two libraries do what the project calls the heavy lifting, which means Stringer itself is mostly the web layer, the storage model and the background job wiring rather than a parser written from scratch.

The front end is not a single-page application. package.json lists @hotwired/turbo and @hotwired/stimulus alongside backbone, underscore, mousetrap and bootstrap. Backbone and Underscore are older choices, and their presence next to Turbo suggests the interface was built over time rather than designed once. mousetrap is the keyboard shortcut library, which is consistent with the shortcuts being a headline feature rather than an afterthought.

GoodJob handles background work, which is where feed fetching and the periodic story cleanup live. The Dockerfile installs supercronic and runs it under supervisord, so scheduled tasks have a home in the container even though the application itself is a Rails process. The container sets PORT=8080 and RAILS_ENV=production, and the compose file maps host port 80 to that 8080.

One consequence worth stating: PostgreSQL is not optional. The compose file defines a dedicated stringer-postgres service on postgres:16-alpine with a named volume. You are not getting an embedded database or a single-binary deployment.

Installing Stringer with Docker Compose

The compose file defines three services: stringer-setup, stringer-postgres and stringer. The setup service runs an entrypoint script that initializes or updates the environment file, and the other two wait on it completing successfully. That ordering matters, because the application and the database both read the same .env file.

Start by creating that file. The compose file mounts ./.env into the containers at /app/.env, and the setup service is what populates it, so you need the file to exist before the stack comes up.

bash
touch .env
docker compose up -d

The stack brings up PostgreSQL 16 on an internal network, runs the setup step, then starts Stringer. The application service publishes port 80 on the host, mapped to 8080 inside the container, so the reader should be reachable at http://localhost once the containers report healthy. The database data lands in /srv/stringer/data on the host through the volume definition, which is the path you would back up.

If you prefer to run from source rather than the published image, the README points at the toolchain versions pinned in .tool-versions, covering Ruby, Node, PostgreSQL and pnpm. With those installed, the development entry point is a single script:

bash
bin/setup

That installs dependencies, prepares the database and launches the development server on port 3000. Subsequent runs use bin/dev, which boots Puma alongside esbuild watchers for JavaScript and CSS. An interactive console is available through rake console.

Connecting a mobile client through the Fever API

The feature that most changes how you use Stringer is the Fever API clone. The README says Stringer implements a clone of Fever's API so it can be used with any mobile client that supports Fever, and it gives the connection settings directly: the server is your Stringer path followed by /fever, the email field is the literal string stringer and is case-sensitive, and the password is your Stringer password.

text
Server: http://reader.example.com/fever
Email: stringer
Password: {your-stringer-password}

That email convention is the kind of detail that costs an hour if you do not know it. A mobile client that validates the email field as an address may reject the bare word stringer, and the README does not address that case. It also does not document which Fever endpoints are implemented, so compatibility with any specific client is something you verify by trying it rather than by reading a support matrix.

If you are running on Heroku and want a custom hostname, the README gives the sequence: run heroku domains:add reader.yourdomain.com, then add a CNAME at your registrar with the name reader pointing at your Heroku instance hostname, and wait for propagation. The same section notes the app runs fine on the Eco and Basic Heroku plans, which is a useful cost signal for a personal reader.

Where Stringer gets awkward

The clearest limitation is operational: this is a server application with a required PostgreSQL instance, and the README does not describe a backup, migration or rollback procedure for the database. The compose file gives you a volume path, and that is the extent of the guidance. If you are not already comfortable restoring a Postgres dump, the deployment docs under docs/ are where you would need to look before trusting it with years of reading history.

Second, the reader accumulates. The README acknowledges this with a cleanup task, rake cleanup_old_stories, which by default removes read stories older than 30 days that are not starred. That default is a policy decision baked into a command. If you read slowly, or you treat your reader as an archive, the default will delete things you meant to keep unless you star them or run the task differently. The README does not document the flags for changing the window.

Third, translations are handled through LocaleApp, and the language is set with the LOCALE environment variable. The README gives the Heroku form, heroku config:set LOCALE=en, and points at config/locales for the list of available languages. If your language is not in that directory, the README directs contributors to LocaleApp rather than describing a local translation workflow. That is a real barrier for anyone who wants to fix a translation without signing up for a third-party service.

Finally, the project is a single-maintainer effort according to the README's maintainers section, with the creator and another contributor listed as alumni. The last push to the repository was on 2026-09-23.

How Stringer differs from Miniflux and FreshRSS

The obvious alternatives for a self-hosted reader are Miniflux and FreshRSS, and the difference is mostly in what you are willing to run. Miniflux is a Go application that the project distributes as a single binary with PostgreSQL, which means no Ruby toolchain, no Node, no pnpm and no asset build step. FreshRSS is PHP and works against MySQL, PostgreSQL or SQLite, so it can run on cheap shared hosting where a Rails app cannot.

Stringer sits at the heavier end of that range. You need Ruby, Node and pnpm if you run from source, or you accept the published container image. In exchange you get a Rails application that is straightforward to modify if you already write Ruby, a Hotwire front end that behaves like a normal server-rendered app, and the Fever API for mobile clients. FreshRSS has its own API for clients, and Miniflux has a documented REST API, so the Fever compatibility is not unique, but it is the specific bridge Stringer chose and it works with the clients that speak it.

The honest framing: if your priority is the smallest possible footprint, Stringer is the wrong pick. If your priority is a Ruby codebase you can read and change, the other two are the wrong pick.

Licence and maintenance cost

Stringer is MIT licensed, which is permissive and places few obligations on you beyond retaining the copyright and permission notice. That matters less for a self-hosted reader than for something you redistribute, but it does mean you can fork and modify without a copyleft question. The repository also bundles fonts under the SIL Open Font License 1.1: ReenieBeanie and Lato, both noted in the README. Those are separate licences from the application code, and if you strip or replace the fonts you should check what the OFL requires rather than assuming the MIT terms cover them. This is not legal advice, and the LICENSE file in the repository is the authoritative text.

The upgrade cost is tied to the deployment path you choose. The Docker route is the least painful, because the compose file already encodes the setup step that initializes or updates the environment file, and pulling a new image plus rerunning the stack is the whole procedure. The Heroku route is similarly managed. A manual VPS install is where the cost concentrates, since you own the Ruby version, the Node version, the PostgreSQL instance and the asset build. The README pins the toolchain in .tool-versions, which helps, but nothing in the documentation describes what happens to your data during a schema change.

Editorial conclusion

Adopt Stringer if you want a small Rails feed reader you control, you already run PostgreSQL, and you care more about keyboard shortcuts than recommendation feeds. Do not adopt it if you want a hosted reader with no server to maintain, or if you expect a broad plugin ecosystem. Before committing, verify the Fever API path against your mobile client, check that your chosen deploy target matches the docs under docs/, and confirm the LOCALE value you need exists under config/locales.

Frequently asked questions

What is Stringer?

Stringer is a self-hosted RSS reader built as a Ruby on Rails application with PostgreSQL, Hotwire and GoodJob. The README describes it as anti-social, meaning it has no social recommendations or sharing and no machine learning.

How do I install Stringer on my own server?

The README provides instructions for deploying to Heroku manually, to a Linux-based VPS, to Docker and to OpenShift, with a separate document under docs/ for each. The Docker path uses the docker-compose.yml in the repository, which defines setup, PostgreSQL and application services.

Can I use Stringer with a mobile RSS client?

Yes, if the client supports the Fever API. Stringer implements a clone of Fever's API, and the README gives the connection settings: the server path ends in /fever, the email is the case-sensitive string stringer, and the password is your Stringer password.

How do I delete old read articles in Stringer?

Run rake cleanup_old_stories. By default it removes read stories more than 30 days old that are not starred, and the README says you can run it manually or add it as a scheduled task.

What language does the Stringer interface use?

The language is set with the LOCALE environment variable, and the available translations live in config/locales. On Heroku the README gives the command heroku config:set LOCALE=en.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. stringer-rss/stringer on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/stringer-rss-stringer.svg)](https://hysenlabs.com/projects/stringer-rss-stringer)