stringer Documentation

A self-hosted, anti-social RSS reader.

4,130 stars Ruby Ruby 1 file ยท ~5,087 tokens #feed-reader#feedjira#rss#rss-reader#ruby
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy stringer on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name stringer -p 80:80 --restart always stringerrss/stringer:latest
Configured Services & Ports
โš™๏ธ stringer-setup
image: stringerrss/stringer:latest
โš™๏ธ stringer-postgres
image: postgres:18-alpine
โš™๏ธ stringer
image: stringerrss/stringer:latest
๐Ÿ”Œ Ports: 80:8080
docker-compose.yml
yaml (docker-compose.yml)
version: '3'

services:
  stringer-setup:
    image: stringerrss/stringer:latest
    container_name: stringer-setup
    restart: no
    env_file: .env
    volumes:
      - ./.env:/app/.env
    entrypoint: ["ruby"]
    command: ["/app/docker/init_or_update_env.rb"]

  stringer-postgres:
    image: postgres:18-alpine
    container_name: stringer-postgres
    restart: always
    depends_on:
      stringer-setup:
        condition: service_completed_successfully
    networks:
      - stringer-network
    volumes:
      - ./.env:/app/.env
      # Postgres 18+ images expect a single mount here and store the database
      # in a version-named subdirectory. See docs/Docker.md for upgrade steps.
      - /srv/stringer/data:/var/lib/postgresql
    env_file: .env

  stringer:
    image: stringerrss/stringer:latest
    container_name: stringer
    build: .
    depends_on:
      stringer-postgres:
        condition: service_started
      stringer-setup:
        condition: service_completed_successfully
    restart: always
    ports:
      - 80:8080
    networks:
      - stringer-network
    volumes:
      - ./.env:/app/.env
    env_file: .env

networks:
  stringer-network:
    external: false
    name: stringer-network

Technical Documentation: stringer-rss/stringer

โ„น๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference

Overview (`README.md`)

Stringer




A self-hosted, anti-social RSS reader.

Stringer has no external dependencies, no social recommendations/sharing, and no fancy machine learning algorithms.

But it does have keyboard shortcuts and was made with love!

Installation

Stringer is a Ruby app based on Rails, PostgreSQL, Hotwire (Turbo and Stimulus) and GoodJob.

Stringer will run just fine on the Eco/Basic Heroku plans.

Instructions are provided for deploying to Heroku manually, to any Ruby
compatible Linux-based VPS, to Docker and to OpenShift.

Niceties

Keyboard Shortcuts

You can access the keyboard shortcuts when using the app by hitting ?.

Using your own domain with Heroku

You can run Stringer at http://reader.yourdomain.com using a CNAME.

If you are on Heroku:

text
heroku domains:add reader.yourdomain.com

Go to your registrar and add a CNAME:

text
Record: CNAME
Name: reader
Target: your-heroku-instance.herokuapp.com

Wait a few minutes for changes to propagate.

Fever API

Stringer implements a clone of Fever's API so it can be used with any mobile client that supports Fever.

Use the following settings:

text
Server: {path-to-stringer}/fever (e.g. http://reader.example.com/fever)

Email: stringer (case-sensitive)
Password: {your-stringer-password}

Translations

Stringer has been translated to several other languages. Your language can be set with the LOCALE environment variable.

To set your locale on Heroku, run heroku config:set LOCALE=en.

If you would like to translate Stringer to your preferred language, please use LocaleApp.

Clean up old read stories on Heroku

You can clean up old stories by running: rake cleanup_old_stories

By default, this removes read stories that are more than 30 days old (that
are not starred). You can either run this manually or add it as a scheduled
task.

Development

Getting Started

You'll need the toolchain versions pinned in .tool-versions (Ruby, Node,
PostgreSQL, and pnpm). With those installed, run:

sh
bin/setup

This installs dependencies, prepares the database, and launches the development
server. The application will be running on port 3000.

On subsequent runs you can start the server directly with bin/dev, which boots
Puma alongside the esbuild watchers for JavaScript and CSS.

You can launch an interactive console (a la rails c) using rake console.

Tests and linting

Run the Ruby tests with bundle exec rspec. Run a single file or example with
bundle exec rspec spec/path/to/file_spec.rb:LINE.

Run the JavaScript tests with pnpm test (Vitest). This also type-checks and
runs ESLint first.

Other checks: bundle exec rubocop (Ruby lint), pnpm eslint (JS lint),
pnpm stylelint (CSS lint), pnpm tscheck (type check), and
bundle exec brakeman (security scan).

Acknowledgments

Most of the heavy-lifting is done by feedjira and feedbag.

General sexiness courtesy of Twitter Bootstrap and Flat UI.

ReenieBeanie Font Copyright © 2010 Typeco ([email protected]). Licensed under SIL Open Font License, 1.1.

Lato Font Copyright © 2010-2011 by tyPoland Lukasz Dziedzic ([email protected]). Licensed under SIL Open Font License, 1.1.

Contact

If you have a question, feature idea, or are running into problems, our preferred method of contact is to open an issue on GitHub. This allows multiple people to weigh in, and we can keep everything in one place. Thanks!

Maintainers

Robert Fletcher boon.gl

Alumni

Matt Swanson (creator), mdswanson.com, @_swanson
Victor Koronen, victor.koronen.se, @victorkoronen


Docker (`docs/Docker.md`)

Stringer on Docker

Production ready setup using docker-compose

Create a local environment file named .env, e.g. via touch .env.

Download docker-compose.yml and run:

sh
touch .env && docker compose up -d

Give it a second and visit localhost!

Production ready manual setup

The following steps can be used to setup Stringer on Docker, using a Postgres database also running on Docker.

  1. Setup a Docker network so the two containers we're going to create can communicate:
sh
docker network create --driver bridge stringer-network
  1. Setup a Postgres Docker container:
sh
docker run --detach \
    --name stringer-postgres \
    --restart always \
    --volume /srv/stringer/data:/var/lib/postgresql \
    --net stringer-network \
    -e POSTGRES_PASSWORD=myPassword \
    -e POSTGRES_DB=stringer \
    postgres:18-alpine
  1. Run the Stringer Docker image:
sh
docker run --detach \
    --name stringer \
    --net stringer-network \
    --restart always \
    -e PORT=8080 \
    -e DATABASE_URL=postgres://postgres:myPassword@stringer-postgres/stringer \
    -e SECRET_KEY_BASE=$(openssl rand -hex 64) \
    -e ENCRYPTION_PRIMARY_KEY=$(openssl rand -hex 64) \
    -e ENCRYPTION_DETERMINISTIC_KEY=$(openssl rand -hex 64) \
    -e ENCRYPTION_KEY_DERIVATION_SALT=$(openssl rand -hex 64) \
    -e FETCH_FEEDS_CRON="*/5 * * * *" \ # optional
    -e CLEANUP_CRON="0 0 * * *" \ # optional
    -p 127.0.0.1:8080:8080 \
    stringerrss/stringer:latest

That's it! You now have a fully working Stringer instance up and running!

Upgrading Postgres

The setup above moved from Postgres 16 to Postgres 18. Two things changed:

  • A Postgres major version cannot read data files written by an older one.
  • The official 18+ image expects a single mount at /var/lib/postgresql (not /var/lib/postgresql/data) and stores the database in a version-named subdirectory inside it.

If you start the 18 image against an existing 16 data directory it refuses to start and prints an explanation, so nothing is lost. But you do need to migrate by hand. Stringer databases are small, so the simplest route is to dump and restore.

  1. With the old container still running, dump everything:
sh
docker compose exec stringer-postgres sh -c 'pg_dumpall -U "$POSTGRES_USER"' > stringer-pg16.sql
  1. Stop the stack and move the old data directory aside:
sh
docker compose down
sudo mv /srv/stringer/data /srv/stringer/data-pg16
  1. Update docker-compose.yml (or re-download it): change the image to postgres:18-alpine and the volume to /srv/stringer/data:/var/lib/postgresql.

  2. Start only the database and wait for it to accept connections:

sh
docker compose up -d stringer-postgres
until docker compose exec stringer-postgres pg_isready -h localhost; do sleep 1; done
  1. Restore the dump. Errors saying a role or database "already exists" are expected and harmless, because the fresh container already created them:
sh
docker compose exec -T stringer-postgres sh -c 'psql -U "$POSTGRES_USER" -d postgres' < stringer-pg16.sql
  1. Start the rest of the stack and check that your feeds and stories are there:
sh
docker compose up -d
  1. Once you are happy, delete /srv/stringer/data-pg16 and stringer-pg16.sql.

If you used the manual setup instead of docker-compose, run the same commands with docker exec stringer-postgres in place of docker compose exec stringer-postgres, and recreate the Postgres container with the new image and volume path in step 3.

For production use it's recommended to put a reverse proxy in front of Stringer.

Caddy (https://caddyserver.com/):

text
stringer.example.org {
	proxy / localhost:8080 {
      transparent
	}
}

Nginx (https://nginx.org/):

text
/* Detailed source-code truncated for AI context efficiency. */

Heroku (`docs/Heroku.md`)

sh
git clone [email protected]:stringer-rss/stringer.git
cd stringer
heroku create

heroku config:set SECRET_KEY_BASE=`openssl rand -hex 64`
heroku config:set ENCRYPTION_PRIMARY_KEY=`openssl rand -hex 64`
heroku config:set ENCRYPTION_DETERMINISTIC_KEY=`openssl rand -hex 64`
heroku config:set ENCRYPTION_KEY_DERIVATION_SALT=`openssl rand -hex 64`

git push heroku main

heroku config:set APP_URL=`heroku apps:info --shell | grep web_url | cut -d= -f2`

heroku run rake db:migrate
heroku restart

heroku addons:create scheduler
heroku addons:open scheduler

Add an hourly task that runs rake lazy_fetch (if you are not on Heroku you may want rake fetch_feeds instead).

Load the app and follow the instructions to import your feeds and start using the app.

See the "Niceties" section of the README for a few more tips and tricks for getting the most out of Stringer on Heroku.

Updating the app

From the app's directory:

sh
git pull
git push heroku main
heroku run rake db:migrate
heroku restart

OpenShift (`docs/OpenShift.md`)

Stringer on OpenShift

Deploying into OpenShift

  1. Creating new OpenShift Ruby 2.0 application with the Postgresql cartridge (command-line).
sh
rhc app create feeds ruby-2.0 postgresql-9.2
  1. Pull the code from the Stringer Github repository.
sh
cd feeds
git remote add upstream [email protected]:stringer-rss/stringer.git
git pull -s recursive -X theirs upstream main
  1. To enable migrations for the application, a new action_hook is required. Add the file, .openshift/action_hooks/deploy, with the below 3 lines into it.
text
pushd ${OPENSHIFT_REPO_DIR} > /dev/null
bundle exec rake db:migrate RAILS_ENV="production"
popd > /dev/null
  1. Make sure that the file created above is executable on Unix-like systems.
sh
chmod +x .openshift/action_hooks/deploy
  1. Set the environment variables by generating them with the commands below.
sh
rhc env set SECRET_KEY_BASE="`openssl rand -hex 64`"
   rhc env set ENCRYPTION_PRIMARY_KEY="`openssl rand -hex 64`"
   rhc env set ENCRYPTION_DETERMINISTIC_KEY="`openssl rand -hex 64`"
   rhc env set ENCRYPTION_KEY_DERIVATION_SALT="`openssl rand -hex 64`"
  1. Configuration of the database server is next. Open the file config/database.yml and add in the configuration for Production as shown below. OpenShift is able to use environment variables to push the information into the application.
text
production:
	adapter: postgresql
	database: <%= ENV["OPENSHIFT_APP_NAME"] %>
	host: <%= ENV["OPENSHIFT_POSTGRESQL_DB_HOST"] %>
	port: <%= ENV["OPENSHIFT_POSTGRESQL_DB_PORT"] %>
	username: <%= ENV["OPENSHIFT_POSTGRESQL_DB_USERNAME"] %>
	password: <%= ENV["OPENSHIFT_POSTGRESQL_DB_PASSWORD"] %>
  1. Due to an older version of bundler being used in OpenShift (1.3.5), some changes need to be made in the Gemfile.

    Remove the Ruby version specification from the Gemfile below (error reporting wrong Ruby version when deploying to OpenShift).

text
ruby '2.0.0'
text
Then change the two gem dependencies below to use the hash rocket syntax for the "require" option.
text
gem "coveralls", "~> 0.7", require: false
gem "rubocop", "~> 0.35.1", require: false
gem "sinatra-assetpack", "~> 0.3.1", require: "sinatra/assetpack"
text
to
text
gem "coveralls", "~> 0.7", :require => false
gem "rubocop", "~> 0.35.1", :require => false
gem "sinatra-assetpack", "~> 0.3.1", :require => "sinatra/assetpack"
  1. Finally, once completed, all changes should be committed and pushed to OpenShift. Note that it might take a while when pushing to OpenShift.
sh
git add .
git commit -m "Deployment of Stringer"
git push origin
  1. Check that you are able to access the website at the URL given, i.e. feeds-username.rhcloud.com. Then set your password, import your feeds and all good to go!

Adding Cronjob to Fetch Feeds

After importing feeds, a cron job is needed on OpenShift to fetch feeds.

  1. Add a new cron cartridge for the cron job.
sh
rhc cartridge add cron -a feeds
  1. Add a new executable file, .openshift/cron/hourly/fetch_feeds and put the below 4 lines into it.
text
./usr/bin/rhcsh
pushd ${OPENSHIFT_REPO_DIR} > /dev/null
bundle exec rake fetch_feeds RAILS_ENV="production"
popd > /dev/null
  1. Make the file executable.
sh
chmod +x .openshift/cron/hourly/fetch_feeds
  1. Push all changes to OpenShift.
sh
git add .
git commit -m "Added Cronjob"
git push origin
  1. Done! The cron job should fetch feeds every hour.

VPS (`docs/VPS.md`)

Install some dependencies

The first step is installing some essential dependencies from your VPS's package manager.

Ubuntu/Debian

text
sudo apt-get install git libxml2-dev libxslt-dev libcurl4-openssl-dev libpq-dev build-essential postgresql libreadline-dev

CentOS/Fedora

text
sudo yum install git libxml2-devel libxslt-devel curl-devel postgresql-devel make automake gcc gcc-c++ postgresql-server readline-devel openssl-devel

On CentOS after installing Postgres, I needed to run these commands, Fedora likely the same.

text
service postgresql initdb && service postgresql start

Arch Linux

text
pacman -S git postgresql base-devel libxml2 libxslt curl readline postgresql-libs

Here are some Arch specific instructions for setting up postgres

text
systemd-tmpfiles --create postgresql.conf
chown -c -R postgres:postgres /var/lib/postgres
sudo su - postgres -c "initdb --locale en_US.UTF-8 -E UTF8 -D '/var/lib/postgres/data'"
systemctl start postgresql
systemctl enable postgresql

Set up the database

Create a postgresql user to own the database Stringer will use, you will need to create a password too, make a note of it.

text
sudo -u postgres createuser -D -A -P stringer

Now create the database Stringer will use

text
sudo -u postgres createdb -O stringer stringer_live

Create your stringer user

We will run stringer as it's own user for security, also we will be installing a specific version of ruby to stringer user's home directory, this saves us worrying whether the version of ruby and some dependencies provided by your distro are compatible with Stringer.

text
sudo useradd stringer -m -s /bin/bash
sudo su -l stringer

Always use -l switch when you switch user to your stringer user, without it your modified .bash_profile or .profile file will be ignored.

Install Ruby for your stringer user

We are going to use Rbenv to manage the version of Ruby you use.

text
cd
git clone git://github.com/sstephenson/rbenv.git .rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> $HOME/.bash_profile
echo 'eval "$(rbenv init -)"' >> $HOME/.bash_profile
git clone git://github.com/sstephenson/ruby-build.git $HOME/.rbenv/plugins/ruby-build
source ~/.bash_profile

rbenv install 2.7.5
rbenv local 2.7.5
rbenv rehash

We also need to install bundler which will handle Stringer's dependencies

text
gem install bundler
rbenv rehash

We will also need foreman to run our app

text
gem install foreman

Install Stringer and set it up

Grab Stringer from github

text
git clone [email protected]:stringer-rss/stringer.git
cd stringer

Use bundler to grab and build Stringer's dependencies

text
bundle install
rbenv rehash

Stringer uses environment variables to configure the application. Edit these values to reflect your settings.

text
echo 'export DATABASE_URL="postgres://stringer:EDIT_ME@localhost/stringer_live"' >> $HOME/.bash_profile
echo 'export RACK_ENV="production"' >> $HOME/.bash_profile
echo 'export RAILS_ENV="production"' >> $HOME/.bash_profile
echo "export SECRET_KEY_BASE=`openssl rand -hex 64`" >> $HOME/.bash_profile
echo "export ENCRYPTION_PRIMARY_KEY=`openssl rand -hex 64`" >> $HOME/.bash_profile
echo "export ENCRYPTION_DETERMINISTIC_KEY=`openssl rand -hex 64`" >> $HOME/.bash_profile
echo "export ENCRYPTION_KEY_DERIVATION_SALT=`openssl rand -hex 64`" >> $HOME/.bash_profile
source ~/.bash_profile

Tell stringer to run the database in production mode, using the postgres database you created earlier.

text
cd $HOME/stringer
rake db:migrate RAILS_ENV=production

Run the application:

text
foreman start

Set up a cron job to parse the rss feeds.

text
crontab -e

add the lines

text
SHELL=/bin/bash
PATH=/home/stringer/.rbenv/bin:/bin/:/usr/bin:/usr/local/bin/:/usr/local/sbin
*/10 * * * *  source $HOME/.bash_profile; cd $HOME/stringer/; bundle exec rake fetch_feeds;

Manage Stringer With Systemd

You may want to manage Stringer as a systemd service on distributions come with systemd.

As stringer user, export app service files with foreman:

text
cd ~/stringer
mkdir systemd-services
foreman export systemd systemd-services -a stringer -u stringer

Logout stringer user, install systemd services:

text
sudo cp -a ~stringer/stringer/systemd-services/* /etc/systemd/system

As stringer user, close existing Stringer instance:

text
exit # exit app

Start app as a systemd service and make app run at startup

text
sudo systemctl start stringer.target
sudo systemctl enable stringer.target

Reverse Proxy With Nginx

You may want to use nginx as reverse proxy server to add SSL/TLS for security
reason. Here is a sample configuration:

nginx
server {
    listen 80;
    # listen 443 ssl;
    # ssl_certificate ssl/fullchain.pem;
    # ssl_certificate_key ssl/privatekey.pem;
    # you can try to use Mozilla SSL Configuration Generator
    # to harden your TLS configuration
    # https://mozilla.github.io/server-side-tls/ssl-config-generator/
    server_name example.com;
    location / {
        proxy_pass http://127.0.0.1:1337/;
        proxy_set_header Host $host;
    }
}

Deploy Stringer With Passenger And Apache

You may want to run Stringer with the existing Apache server. We need to install
mod_passenger and edit few files.

The installation of mod_passenger depends on VPS's system distribution release.
Offical installation guide is available at Passenger Library

After validating the mod_passenger install, we will fetch dependencies again
to meet Passenger's default GEM_HOME set. As stringer user:

text
cd ~/stringer
bundle install --path vendor/bundle

Edit database.yml with correct database url:

text
cd ~/stringer
sed -i "s|url: .*|url: $DATABASE_URL|" config/database.yml

Add VirtualHost to your Apache installation, here's a sample configuration:

bash
<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /home/stringer/stringer/public

    PassengerEnabled On
    PassengerAppRoot /home/stringer/stringer
    PassengerRuby /home/stringer/.rbenv/shims/ruby
    # PassengerLogFile /dev/null # don't flow logs to apache error.log

    <Directory /home/stringer/stringer/public>
        Options FollowSymLinks
        Require all granted
        AllowOverride All
    </Directory>


    # you can harden your connection with https, don't forget
    # change to <VirtualHost *:443>
    # SSLCertificateFile /etc/path/to/example.com/fullchain.pem
    # SSLCertificateKeyFile /etc/path/to/example.com/privkey.pem
    # Include /etc/path/to/options-ssl-apache.conf
</VirtualHost>

--- METRICS ---