How to Self-Host Tiny Tiny RSS with Docker

What Is Tiny Tiny RSS?

Tiny Tiny RSS (tt-rss) is a self-hosted, web-based RSS and Atom feed reader written in PHP. It replaces services like Feedly, Inoreader, and The Old Reader with something you fully own and control. The project has been actively maintained since 2005, making it one of the oldest and most mature self-hosted feed readers available. It supports multi-user accounts, a plugin system, keyboard shortcuts, OPML import/export, feed categories and labels, content filters, and API access for mobile app integration. tt-rss uses PostgreSQL as its database backend and runs as a multi-container Docker setup with separate app, updater, and web server services.

Prerequisites

  • A Linux server (Ubuntu 22.04+ recommended)
  • Docker and Docker Compose installed (guide)
  • 1 GB of free RAM (minimum)
  • 2 GB of free disk space
  • A domain name (optional, for remote access)

Docker Compose Configuration

Tiny Tiny RSS uses a multi-container architecture: a PHP-FPM application server, a feed updater daemon, an Nginx web server, and a PostgreSQL database. The official images are published by supahgreg on Docker Hub and use commit-SHA-based tags rather than semantic versions.

Create a project directory and the required files:

mkdir -p ~/ttrss/config.d && cd ~/ttrss

Create a .env file:

# Database credentials
TTRSS_DB_USER=ttrss
TTRSS_DB_NAME=ttrss
TTRSS_DB_PASS=change_this_to_a_strong_password    # CHANGE THIS

# Web access — bind to localhost if using a reverse proxy
HTTP_PORT=127.0.0.1:8280

# Admin password — set this on first run, then remove or leave empty
# If unset, a random password is generated and printed to container logs
ADMIN_USER_PASS=change_this_admin_password         # CHANGE THIS

# Container ownership (match your host user's UID/GID)
# OWNER_UID=1000
# OWNER_GID=1000

Create a docker-compose.yml file:

services:
  db:
    image: postgres:17-alpine
    container_name: ttrss-db
    restart: unless-stopped
    env_file:
      - .env
    environment:
      POSTGRES_USER: ${TTRSS_DB_USER}
      POSTGRES_PASSWORD: ${TTRSS_DB_PASS}
      POSTGRES_DB: ${TTRSS_DB_NAME}
    volumes:
      - db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${TTRSS_DB_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5

  app:
    image: supahgreg/tt-rss:sha-5cbcc14
    container_name: ttrss-app
    restart: unless-stopped
    env_file:
      - .env
    volumes:
      - app:/var/www/html
      - ./config.d:/opt/tt-rss/config.d:ro    # Custom PHP config overrides
    depends_on:
      db:
        condition: service_healthy

  updater:
    image: supahgreg/tt-rss:sha-5cbcc14
    container_name: ttrss-updater
    restart: unless-stopped
    env_file:
      - .env
    volumes:
      - app:/var/www/html
      - ./config.d:/opt/tt-rss/config.d:ro
    depends_on:
      - app
    command: /opt/tt-rss/updater.sh            # Runs the feed update daemon

  web-nginx:
    image: supahgreg/tt-rss-web-nginx:sha-5cbcc14
    container_name: ttrss-web
    restart: unless-stopped
    env_file:
      - .env
    ports:
      - ${HTTP_PORT}:80
    volumes:
      - app:/var/www/html:ro                   # Read-only access to app files
    depends_on:
      - app

volumes:
  db:
  app:

Start the stack:

docker compose up -d

The app and updater services share the same image but serve different roles. The app container runs PHP-FPM to handle web requests, while the updater container runs the background feed update daemon. The web-nginx container serves static files and proxies PHP requests to the app container.

If you set ADMIN_USER_PASS in .env, the admin password is applied on every container start. If you left it unset, check the logs for the generated password:

docker compose logs app | grep -i password

Initial Setup

  1. Open http://your-server-ip:8280 in your browser (or http://localhost:8280 if local)
  2. Log in with username admin and the password you set in ADMIN_USER_PASS (or the one from the container logs)
  3. Change your admin password immediately under Preferences → Personal data / Authentication → Password
  4. Start adding feeds: click the Subscribe to feed button (or press s), paste a feed URL, and select a category

tt-rss auto-discovers RSS/Atom feeds from website URLs, so you can paste a site URL and it will find the feed.

Configuration

Feed Categories and Labels

Organize feeds into categories under Preferences → Feeds. Create categories first, then assign feeds to them. Labels are tag-like markers you can apply to individual articles for cross-cutting organization — useful for flagging articles by topic regardless of their source feed.

OPML Import

To migrate from another reader, export your feeds as OPML from your old service, then go to Preferences → Feeds → OPML and upload the file. tt-rss preserves your category structure from the OPML file.

Plugins

tt-rss has a built-in plugin system. Enable plugins under Preferences → Plugins. Notable built-in plugins:

PluginPurpose
af_readabilityFetches full article content from truncated feeds
noteAdds personal notes to articles
shareGenerates public share links for articles
bookmarkletsBrowser bookmarklet for subscribing to feeds

System-wide plugins (affecting all users) are configured via the TTRSS_PLUGINS environment variable. Per-user plugins are toggled in the web UI.

Themes

tt-rss ships with a default light theme and supports custom themes. The built-in night.css theme provides a dark mode. Select your theme under Preferences → Preferences → Themes.

Keyboard Shortcuts

tt-rss has extensive keyboard shortcuts for power users:

  • j / k — next / previous article
  • n / p — next / previous feed
  • s — subscribe to a new feed
  • t — tag/label the current article
  • o — open article in a new tab
  • *s — star article
  • ? — show all keyboard shortcuts

Content Filters

Create filters under Preferences → Filters to automatically process articles. Filters can match on title, content, author, or feed URL, and actions include starring, labeling, marking as read, or deleting. Filters run on new articles as they arrive.

Advanced Configuration (Optional)

API Access for Mobile Apps

tt-rss provides a built-in JSON API. Enable it under Preferences → Personal data / Authentication → Enable API access. Compatible mobile apps include:

  • Android: Tiny Tiny RSS (official), TTRSS-Reader, FeedMe (with tt-rss backend)
  • iOS: Fiery Feeds, Tiny Reader RSS

The API endpoint is https://your-domain/api/.

Fever API Plugin

Many popular RSS apps support the Fever API. To enable it:

  1. Install the Fever API plugin by cloning it into the plugins directory:
docker compose exec app sh -c \
  "cd /var/www/html/plugins.local && git clone https://github.com/DigitalDJ/tinytinyrss-fever-plugin fever"
  1. Enable the fever plugin under Preferences → Plugins
  2. Set a Fever API password in the new Fever Emulation section in Preferences (use a different password from your login — Fever uses unsalted MD5 hashing)
  3. Configure your mobile app with the endpoint: https://your-domain/plugins/fever/

FreshRSS / Google Reader API Plugin

For apps that support the Google Reader API (like NetNewsWire, Reeder, or NewsFlash), install the FreshAPI plugin:

docker compose exec app sh -c \
  "cd /var/www/html/plugins.local && git clone https://github.com/eric-pierce/freshapi"

Enable it under Preferences, then configure your app with the Google Reader API endpoint.

Custom Update Interval

The updater daemon checks feeds at a default interval. Override the per-feed update interval under Preferences → Feeds → [feed] → Update interval. Options range from the default interval to every 15 minutes, hourly, or daily. Aggressive polling (every 15 minutes) works for high-volume feeds but increases server load.

PHP Worker Tuning

For high-traffic multi-user instances, tune PHP-FPM workers via environment variables in .env:

PHP_WORKER_MAX_CHILDREN=5        # Default is usually sufficient for 1-5 users
PHP_WORKER_MEMORY_LIMIT=256M     # Increase if processing large feeds

Custom PHP Configuration

Drop PHP configuration overrides into the config.d directory. Files in this directory are loaded by tt-rss at startup. Example — increase the article purge limit:

<?php
// config.d/custom.php
putenv('TTRSS_PURGE_OLD_DAYS=90');
putenv('TTRSS_SPHINX_ENABLED=false');

Reverse Proxy

tt-rss binds to 127.0.0.1:8280 by default, which means it only accepts local connections. Place a reverse proxy in front of it for HTTPS and public access.

When using a reverse proxy, set TTRSS_SELF_URL_PATH in your .env file to the public-facing URL:

TTRSS_SELF_URL_PATH=https://rss.yourdomain.com/

Example Nginx Proxy Manager config:

  • Scheme: http
  • Forward Hostname: ttrss-web (or your server IP)
  • Forward Port: 8280
  • Enable WebSocket Support: No
  • SSL: Request a new SSL certificate and enable Force SSL

Your reverse proxy must pass the X-Forwarded-Proto header correctly for tt-rss to detect HTTPS. Most reverse proxies do this by default.

See Reverse Proxy Setup for full configuration with Nginx Proxy Manager, Traefik, or Caddy.

Backup

Three things need backing up: the PostgreSQL database, the app volume (which contains plugins and cached data), and your .env and config.d files.

Database Backup

docker compose exec db pg_dump -U ttrss ttrss > ttrss-backup-$(date +%Y%m%d).sql

App Volume Backup

docker run --rm -v ttrss_app:/data -v $(pwd):/backup alpine \
  tar czf /backup/ttrss-app-backup-$(date +%Y%m%d).tar.gz /data

Restore

# Restore database
cat ttrss-backup-20260224.sql | docker compose exec -T db psql -U ttrss ttrss

# Restore app volume
docker run --rm -v ttrss_app:/data -v $(pwd):/backup alpine \
  sh -c "cd / && tar xzf /backup/ttrss-app-backup-20260224.tar.gz"

tt-rss also offers an optional backups service in Docker Compose that creates weekly database dumps automatically. Add it to your docker-compose.yml if you want hands-off backups:

  backups:
    image: supahgreg/tt-rss:sha-5cbcc14
    container_name: ttrss-backups
    restart: unless-stopped
    env_file:
      - .env
    volumes:
      - backups:/backups
      - app:/var/www/html
    depends_on:
      - db
    command: /opt/tt-rss/dcron.sh -f

Add backups: to your volumes: section as well.

See Backup Strategy for a complete backup approach.

Troubleshooting

Feeds Not Updating

Symptom: Articles stop appearing. Feed timestamps are stale. Fix: Verify the updater container is running:

docker compose ps updater
docker compose logs updater --tail 50

If the updater has exited, restart it with docker compose restart updater. Check that the app volume is properly shared between the app and updater services — both must mount the same named volume.

”SELF_URL_PATH” Mismatch Error

Symptom: tt-rss displays a warning about SELF_URL_PATH not matching the URL you’re accessing it from. Fix: Set TTRSS_SELF_URL_PATH in your .env to the exact URL you use to access tt-rss, including the trailing slash:

TTRSS_SELF_URL_PATH=https://rss.yourdomain.com/

Restart the stack after changing: docker compose down && docker compose up -d

Database Connection Refused

Symptom: App container logs show connection to server at "db" failed: Connection refused. Fix: The database container may not be ready. Ensure the db service has a health check and that app uses depends_on with condition: service_healthy. If the issue persists, check that TTRSS_DB_USER, TTRSS_DB_NAME, and TTRSS_DB_PASS in your .env match the PostgreSQL container’s POSTGRES_* variables.

White Screen or 500 Error After Update

Symptom: Blank page or server error after pulling a new image tag. Fix: Clear the template cache:

docker compose exec app sh -c "rm -rf /var/www/html/cache/templates/*"
docker compose restart app web-nginx

If the problem persists, check the app logs for PHP errors:

docker compose logs app --tail 100

Plugins Not Showing Up

Symptom: Installed a plugin to plugins.local but it doesn’t appear in Preferences. Fix: Plugins must be installed inside the shared app volume at /var/www/html/plugins.local/. If you cloned the plugin outside the container, it won’t be visible. Use docker compose exec app to install plugins. After installing, refresh the Preferences page — no restart is needed.

Resource Requirements

  • RAM: ~80 MB idle (app + updater + nginx), ~200 MB under load with 500+ feeds
  • CPU: Low — the updater uses brief CPU spikes during feed refresh cycles
  • Disk: ~150 MB for the application, plus PostgreSQL storage (grows with feed count and article retention — expect 1-5 GB for a heavy user with 200+ feeds and months of history)

The multi-container architecture means tt-rss uses more baseline resources than single-binary alternatives like Miniflux. The trade-off is a richer plugin ecosystem and more customization options.

Verdict

Tiny Tiny RSS is the most customizable self-hosted RSS reader available. Its plugin system, extensive keyboard shortcuts, and powerful filtering make it the best choice for power users who want full control over their reading experience. The multi-container Docker setup is more involved than FreshRSS or Miniflux, and the project’s non-standard versioning (commit SHAs instead of semver tags) makes updates less predictable.

For most people, FreshRSS is the better first choice — it’s simpler to deploy, lighter on resources, and has native Google Reader API support without plugins. If you want a minimalist, API-first reader with zero bloat, Miniflux is unbeatable. But if you’ve outgrown both and want deep customization, content filters, and the ability to extend your reader with plugins, Tiny Tiny RSS delivers in ways the others cannot.

Choose Tiny Tiny RSS if: you want maximum customization, have specific workflow needs that require plugins, or manage feeds for multiple users who each need different configurations.

Choose FreshRSS if: you want the best balance of features and simplicity, native mobile app support, and easy deployment.

Choose Miniflux if: you prefer a clean, fast, single-binary reader with a built-in Fever API and zero bloat.

Frequently Asked Questions

How does Tiny Tiny RSS compare to FreshRSS?

FreshRSS is simpler to deploy (single container), lighter on resources, and has native Google Reader API support for mobile apps. Tiny Tiny RSS has a more powerful plugin system, better content filtering, and deeper customization. FreshRSS is the better first choice for most people; tt-rss is for power users who’ve outgrown simpler readers.

Does Tiny Tiny RSS have mobile apps?

Yes. The built-in JSON API supports several mobile apps: Tiny Tiny RSS (official Android app), TTRSS-Reader, and FeedMe on Android; Fiery Feeds and Tiny Reader RSS on iOS. You can also install the Fever API plugin for compatibility with apps like Reeder and NetNewsWire that use the Fever protocol.

Can I migrate my feeds from Feedly or another reader?

Yes. Export your feeds as an OPML file from your current reader, then import it in Tiny Tiny RSS under Preferences → Feeds → OPML. Category structure from the OPML file is preserved. Most RSS readers and services support OPML export.

Why does tt-rss use commit SHAs instead of version numbers?

The tt-rss project uses a rolling-release model with commit-SHA-based Docker image tags instead of semantic versioning. This means there are no “v1.0” or “v2.0” releases — updates are continuous. The trade-off is less predictable upgrade paths, but the project maintains stability through its rolling process.

How many feeds can Tiny Tiny RSS handle?

tt-rss handles hundreds of feeds comfortably on modest hardware. With 200+ feeds and months of article history, expect PostgreSQL to use 1-5 GB of disk space. The updater daemon can be tuned with per-feed update intervals (15 minutes to daily) to balance freshness against server load.

Is Tiny Tiny RSS multi-user?

Yes. tt-rss supports multiple user accounts, each with their own feeds, categories, labels, filters, and preferences. Each user’s data is fully isolated. This makes it suitable for families or small organizations that want a shared RSS infrastructure with individual reading experiences.

Comments