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
- Open
http://your-server-ip:8280in your browser (orhttp://localhost:8280if local) - Log in with username
adminand the password you set inADMIN_USER_PASS(or the one from the container logs) - Change your admin password immediately under Preferences → Personal data / Authentication → Password
- 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:
| Plugin | Purpose |
|---|---|
af_readability | Fetches full article content from truncated feeds |
note | Adds personal notes to articles |
share | Generates public share links for articles |
bookmarklets | Browser 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 articlen/p— next / previous feeds— subscribe to a new feedt— tag/label the current articleo— 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:
- 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"
- Enable the
feverplugin under Preferences → Plugins - 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)
- 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.
Related
- FreshRSS vs Tiny Tiny RSS: Which RSS Reader?
- Miniflux vs Tiny Tiny RSS: RSS Readers Compared
- How to Self-Host FreshRSS
- How to Self-Host Miniflux
- FreshRSS vs Miniflux
- Best Self-Hosted RSS Readers
- Replace Feedly with Self-Hosted RSS
- Docker Compose Basics
- Reverse Proxy Setup
- Backup Strategy
- Linux Basics for Self-Hosting
Get self-hosting tips in your inbox
Get the Docker Compose configs, hardware picks, and setup shortcuts we don't put in articles. Weekly. No spam.
Comments