Skip to content

greg-hass/Feeds

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

669 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Feeds

A fast, reliable, private self-hosted feed reader supporting RSS/Atom, YouTube, Reddit, and Podcasts.

Feeds Docker Tests Coverage

Features

  • πŸ“° RSS/Atom feeds - Standard web feed support
  • πŸ“Ί YouTube channels - Subscribe to YouTube content via RSS
  • πŸŽ™οΈ Podcasts - Full podcast support with audio player
  • πŸ” Full-text search - Search across all articles (FTS5)
  • πŸ“ Folders - Organize feeds by folder
  • πŸ“± PWA - Install as an app on mobile
  • πŸ”„ Background sync - Automatic feed updates
  • πŸ“– Reader mode - Clean article view using Mozilla Readability
  • πŸŒ™ Dark theme - Easy on the eyes
  • πŸ“€ OPML import/export - Migrate from other readers
  • πŸ” Password protection - Secure your instance with JWT auth

Quick Start

Using Docker Compose (Recommended)

# Clone the repository
git clone https://github.com/greg-hass/Feeds.git
cd Feeds

# Create environment file
cp .env.example .env

# Edit .env and set required variables (see Configuration section below)
nano .env

# Start with Docker Compose
docker-compose up -d

Access at http://localhost:3080

Using Pre-built Image

docker run -d \
  --name feeds \
  -p 3080:80 \
  -v feeds_data:/data \
  -e JWT_SECRET=your-secret-key-min-32-characters \
  -e CORS_ORIGIN=http://localhost:3080 \
  ghcr.io/greg-hass/feeds:latest

Configuration

Create a .env file with these variables:

Variable Required Description
JWT_SECRET Yes Secret key for authentication (min 32 chars)
APP_PASSWORD No Optional bootstrap password for first-time setup or password reset
CORS_ORIGIN Production Your domain (e.g., https://feeds.yourdomain.com)
GEMINI_API_KEY No Google AI key for feed discovery
YOUTUBE_API_KEY No YouTube Data API key (improves channel info)
LOG_LEVEL No debug, info, warn, error (default: info)

Generating JWT_SECRET

# Option 1: OpenSSL
openssl rand -base64 64

# Option 2: Node.js
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"

First Run

  1. Set environment variables (see Configuration above)
  2. Start the app: docker-compose up -d
  3. Open http://localhost:3080
  4. Create password: On first visit, you'll see a setup screen
  5. Login: Use the password you just created

Note: APP_PASSWORD is optional. If set, it can be used as a bootstrap password for first-time setup and controlled resets. After setup, authentication uses the password you created in the app.

Development

Backend

cd backend
npm install
npm run dev

Frontend

cd frontend
npm install
npm run web

Data Persistence

Data is stored in a Docker volume:

  • Database: /data/feeds.db
  • Icons: /data/icons/
  • Thumbnails: /data/thumbnails/
  • Backups: /data/backups/ (auto-created daily, 7 days retention)

Backup & Restore

Manual Backup:

# Create backup
docker exec feeds cp /data/feeds.db /data/backups/feeds-manual-$(date +%Y%m%d).db

# Copy to host
docker cp feeds:/data/backups/feeds-manual-YYYYMMDD.db ./

Restore from Backup:

# Stop container
docker-compose down

# Restore database (replace YYYYMMDD with backup date)
docker cp ./feeds-backup-YYYYMMDD.db feeds:/data/feeds.db

# Start container
docker-compose up -d

Automated Backups: The container automatically creates daily backups at /data/backups/ with 7-day retention. To persist backups outside the container:

# docker-compose.yml
volumes:
  - feeds_data:/data
  - ./backups:/data/backups  # Mount backups to host

Troubleshooting

"Authentication not configured" error

  • Open the setup screen and create an initial password
  • If you want an environment-controlled bootstrap/reset flow, set APP_PASSWORD in your .env and restart the container

Cannot login / "Invalid password"

  • Check that JWT_SECRET is set and hasn't changed
  • If you changed JWT_SECRET, you'll need to login again (tokens are invalidated)

Rate limited (429 error)

  • Login is rate-limited: 5 attempts per 15 minutes per IP
  • Wait 15 minutes or restart container to reset

Port already in use

  • Change the port in docker-compose.yml: - "3081:80"
  • Access at http://localhost:3081

Reset everything (⚠️ deletes all data)

docker-compose down -v  # Removes volume
docker-compose up -d    # Fresh start

Security Features

  • Password hashing: bcrypt with 12 rounds
  • JWT tokens: 30-day sliding expiration (extends with use)
  • Rate limiting: 5 login attempts per 15 minutes
  • Path traversal protection: Validated file paths
  • SQL injection prevention: Parameterized queries
  • XXE protection: Disabled XML entity processing

API Documentation

See API.md for full API documentation.

Tech Stack

  • Backend: Node.js, Fastify, SQLite (better-sqlite3)
  • Frontend: React Native Web, Expo, Zustand
  • Deployment: Docker, nginx
  • Search: SQLite FTS5

License

MIT

About

Personal Feed Reader

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors