A personal content aggregator for YouTube channels and blog RSS feeds. No algorithm, no recommendations, only content from sources you explicitly add.
- YouTube — add channel URL (no API key required)
- Blogs — follows any RSS/Atom feed for blogs and news sites
- Self-hosted — your data stays on your machine or server
- Add YouTube channels by URL (
@handle,/channel/UC...,/user/...) - Add any blog RSS or Atom feed
- Search videos and articles by title
- Filter by channel / feed or by date range
- Watch videos directly in-app via embedded player — no need to leave the page
- Mark videos as watched, articles as read — unread counts shown on nav tabs
- Grid and list view for videos
- Dark/light theme, persisted per browser
- Auto-refresh on a configurable interval
- Resilient YouTube sync — falls back to yt-dlp automatically when YouTube RSS is unavailable
- Single Docker command to self-host
Pull and run the pre-built image directly:
docker run -d \
--name noalgotube \
--restart unless-stopped \
-p 8080:8080 \
-v noalgotube_data:/data \
iayanpahwa/noalgotube:latestOpen http://localhost:8080. Data is persisted in the noalgotube_data volume.
To update to the latest image:
docker pull iayanpahwa/noalgotube:latest
docker rm -f noalgotube
docker run -d --name noalgotube --restart unless-stopped \
-p 8080:8080 -v noalgotube_data:/data iayanpahwa/noalgotube:latestCreate a docker-compose.yml:
services:
noalgotube:
image: iayanpahwa/noalgotube:latest
ports:
- "8080:8080"
volumes:
- noalgotube_data:/data
restart: unless-stopped
volumes:
noalgotube_data:Then run:
docker compose up -dTo update:
docker compose pull
docker compose up -dgit clone https://github.com/iayanpahwa/noalgotube.git
cd noalgotube
docker compose up --build -d- In Portainer, go to Stacks → Add stack
- Name it
noalgotube - Paste the compose snippet from Option 2 into the web editor
- Click Deploy the stack
- Open http://your-host:8080
To update: go to the stack, click Editor, change latest to a specific version tag (e.g. 1.0.0), and redeploy. Or pull the new image and restart the stack.
Change the port mapping in your compose file:
ports:
- "3000:8080" # host port : container portRequirements: Python 3.12+, uv.
cd backend
uv sync # creates .venv and installs dependencies
uv run uvicorn main:app --reload --port 8080Open http://localhost:8080.
The frontend is served as static files by FastAPI — no build step needed.
cd backend
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
uvicorn main:app --reload --port 8080- Manage tab — add YouTube channels or blog RSS feeds
- YouTube: paste any channel URL (
https://youtube.com/@name,/channel/UC..., etc.) - Blogs: paste the RSS/Atom feed URL directly (
https://example.com/feed.xml)
- YouTube: paste any channel URL (
- Videos tab — browse and watch videos; click a card to open the embedded player
- Blogs tab — browse and read articles; click a card to open the article reader
- Search — type in the search box on Videos or Blogs to filter by title
- Refresh button — manually sync all feeds for new content
- Auto-refresh — configure in Manage to sync automatically while the page is open
Environment variables (set in docker-compose.yml or your shell):
| Variable | Default | Description |
|---|---|---|
FRONTEND_DIR |
../frontend (relative to main.py) |
Path to the frontend directory |
DB_PATH |
../data/noalgotube.db |
Path to the SQLite database file |
Docker sets DB_PATH=/data/noalgotube.db automatically, mounted to the noalgotube_data volume.
- Backend: Python 3.12, FastAPI, uvicorn
- Database: SQLite (built-in, no external DB required)
- RSS: feedparser + httpx
- YouTube fallback: yt-dlp
- Frontend: Vanilla HTML/CSS/JS — no npm, no build step
noalgotube is built on top of several excellent open-source projects:
- FastAPI by Sebastián Ramírez — the backend API framework
- feedparser by Kurt McKee — RSS/Atom feed parsing
- httpx by Encode — async HTTP client
- yt-dlp by the yt-dlp contributors — YouTube fallback when RSS is unavailable
- uvicorn by Encode — ASGI server
MIT — see LICENSE.
- Mark unwatched / Mark unread — toggle watched or read state directly from the card (hover to reveal)
- Mark all watched / Mark all read — clear the entire view in one click; respects the active channel/feed filter
- Delete confirmation — channels and feeds now prompt before deletion to prevent accidental data loss
- Mobile hamburger menu — nav collapses into a ☰ dropdown on small screens; closes on navigate or outside tap
- Internal: persistent SQLite connection with WAL mode for better read/write concurrency
- Fix:
asyncio.get_event_loop()→get_running_loop()(Python 3.10+ deprecation) - Fix: initial feed sync failure no longer returns 500 on the add-feed endpoint
- Fix: items-per-source setting now persists correctly across page refresh
- Fix: browser refresh stays on the same tab (Videos / Blogs / Manage) instead of always going back to Videos
- PWA support — installable on Android and iOS home screens, works offline with cached content
- Mobile-optimised UI — bottom-sheet modals, single-column video grid, wrapped toolbar, 44 px touch targets, safe-area insets for notched phones
- Per-channel and per-feed item count — set how many recent videos/articles to show per source (global default + per-source override) in the Manage page
- Blog sort toggle — switch between "Newest first" and "Oldest first" in the Blogs toolbar; preference persisted across sessions
- Search bar on Videos and Blogs views (live client-side filter by title)
- Unwatched/unread count badges on Videos and Blogs nav tabs
- yt-dlp fallback for channel resolution and video sync when YouTube RSS is unavailable
- Multi-arch Docker image published to Docker Hub (
iayanpahwa/noalgotube, amd64 + arm64) - Docker Hub, Docker Compose, and Portainer deployment instructions



