A Self-Hosted YouTube Media Center -- Dockerized Backend
NullFeed is a self-hosted YouTube media center that wraps yt-dlp with a polished, multi-user experience. The backend runs as a single Docker container and provides automated channel subscriptions, download management, media streaming with HTTP range requests, AI-powered recommendations via Claude, and real-time WebSocket updates -- all consumed by the NullFeed Flutter app on iOS and tvOS.
- Instant Playback with Progressive Quality -- Start watching immediately, even while a video is still downloading. The backend serves a low-quality stream on demand, then seamlessly upgrades to the full-quality version once the download completes -- no buffering, no interruption.
- Automated Channel Subscriptions -- Subscribe to YouTube channels and automatically download new uploads on a configurable polling interval.
- Download Manager -- Celery-based task queue with configurable concurrency, retry logic, and exponential backoff.
- Media Streaming -- Built-in static file server with HTTP range request support for native seeking and scrubbing.
- Multi-User Support -- Independent profiles with per-user subscriptions, watch history, and playback positions.
- Smart Deduplication -- One copy of each video on disk, reference-counted across all subscribing users.
- AI Recommendations -- Claude-powered channel and video suggestions derived from each user's subscription graph via the Anthropic API.
- Real-Time Updates -- WebSocket push for download progress, completion events, and new episode alerts.
- Resume-Aware Feeds -- Continue Watching, New Episodes, and Recently Added API endpoints for the home screen experience.
- Unraid-Native -- Community Applications template for one-click installation on Unraid servers.
- Auto-Updating yt-dlp -- Automatically updates yt-dlp on every container start to stay current with YouTube changes.
+---------------------+
Flutter App | Docker Container |
(iOS / tvOS) <----> | |
REST | FastAPI (API) |
+ WS | Celery (Workers) |
| Redis (Broker) |
| SQLite (Database) |
| yt-dlp (Downloads)|
| ffmpeg (Encoding) |
+---------------------+
| Component | Technology | Purpose |
|---|---|---|
| API Server | FastAPI + Uvicorn | Async REST API with auto-generated OpenAPI docs |
| Database | SQLite + SQLAlchemy 2.x | Zero-config, file-based persistence |
| Migrations | Alembic | Schema versioning and upgrades |
| Task Queue | Celery + Redis | Background download scheduling and retries |
| Download Engine | yt-dlp | YouTube content acquisition |
| Media Encoding | ffmpeg | Transcoding and format support |
| AI Engine | Anthropic API (Claude) | Personalized recommendations |
| Real-Time | WebSocket (FastAPI) | Push notifications to connected clients |
- Docker (20.10+)
- Docker Compose (v2+)
-
Clone the repository:
git clone https://github.com/windoze95/nullfeed-backend.git cd nullfeed-backend -
Configure environment variables:
cp .env.example .env # Edit .env with your preferred settings -
Start the container:
docker compose up -d
-
Verify it's running:
curl http://localhost:8484/api/health
-
Explore the API docs:
Open http://localhost:8484/docs in your browser for the interactive Swagger UI.
| Variable | Default | Description |
|---|---|---|
TUBEVAULT_PORT |
8484 |
API listen port |
ANTHROPIC_API_KEY |
(none) | Anthropic API key for AI recommendations (Discover tab) |
DOWNLOAD_CONCURRENCY |
2 |
Max simultaneous yt-dlp downloads |
MEDIA_QUALITY |
1080p |
Default download quality (720p / 1080p / 4k / best) |
CHECK_INTERVAL_MINUTES |
60 |
How often to poll subscribed channels for new uploads |
PUID |
1000 |
User ID for file permissions (Unraid standard) |
PGID |
1000 |
Group ID for file permissions (Unraid standard) |
REDIS_URL |
redis://localhost:6379/0 |
Redis broker URL (internal; override for external Redis) |
DATABASE_URL |
sqlite:////data/db/nullfeed.db |
Database connection string |
| Container Path | Purpose | Example Host Path |
|---|---|---|
/data/media |
Downloaded video/audio files | /mnt/user/appdata/nullfeed/media |
/data/db |
SQLite database + migrations | /mnt/user/appdata/nullfeed/db |
/data/config |
App configuration, API keys | /mnt/user/appdata/nullfeed/config |
/data/thumbnails |
Cached channel art and thumbnails | /mnt/user/appdata/nullfeed/thumbs |
Full interactive documentation is available at /docs (Swagger UI) and /redoc (ReDoc) when the server is running.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/profiles |
List all user profiles |
| POST | /api/auth/select |
Select a profile (with optional PIN) |
| POST | /api/auth/create |
Create a new user profile (admin) |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/channels |
List all known channels |
| POST | /api/channels/subscribe |
Subscribe current user to a channel |
| DELETE | /api/channels/{id}/unsubscribe |
Unsubscribe current user |
| GET | /api/channels/{id} |
Channel detail with video list |
| GET | /api/channels/{id}/videos |
Paginated video list for a channel |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/videos/{id} |
Video metadata |
| GET | /api/videos/{id}/stream |
Stream video file (supports range requests) |
| PUT | /api/videos/{id}/progress |
Update watch position |
| DELETE | /api/videos/{id} |
Remove user's reference (ref-count check) |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/feed/continue-watching |
Videos with partial progress, by channel |
| GET | /api/feed/new-episodes |
Channels with unwatched downloads |
| GET | /api/feed/recently-added |
Chronological list of new downloads |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/discover |
AI-generated suggestions |
| POST | /api/discover/{id}/dismiss |
Dismiss a suggestion |
| POST | /api/discover/refresh |
Force-refresh recommendations |
| Endpoint | Description |
|---|---|
ws://{host}:{port}/ws/{user_id} |
Real-time events: download progress, completions, new episodes |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/health |
Container health check |
NullFeed includes a Community Applications template for one-click installation on Unraid.
- In the Unraid web UI, navigate to Apps (Community Applications).
- Search for NullFeed or add the template repository manually.
- Configure the template:
- Set the API Port (default:
8484). - Map the four volume paths (
Media,Database,Config,Thumbnails). - Optionally provide your
ANTHROPIC_API_KEYfor AI recommendations.
- Set the API Port (default:
- Click Apply to start the container.
- Access the API docs at
http://[SERVER_IP]:8484/docs.
The container includes a health check endpoint at /api/health for Unraid's built-in monitoring, and logs are written to stdout/stderr for the Unraid log viewer.
To run the backend locally without Docker:
-
Prerequisites:
- Python 3.12+
- Redis server
- ffmpeg
-
Install dependencies:
python -m venv venv source venv/bin/activate pip install -r requirements.txt -
Configure environment:
cp .env.example .env # Edit .env -- set DATABASE_URL to a local SQLite path -
Run database migrations:
alembic upgrade head
-
Start Redis:
redis-server --daemonize yes
-
Start the Celery worker:
celery -A app.tasks.celery_app worker --loglevel=info --concurrency=2
-
Start the Celery Beat scheduler:
celery -A app.tasks.celery_app beat --loglevel=info
-
Start the API server:
uvicorn app.main:app --host 0.0.0.0 --port 8484 --reload
| Repository | Description |
|---|---|
| nullfeed-backend (this repo) | Python/FastAPI backend -- Docker-based server with yt-dlp, Celery, Redis, and SQLite |
| nullfeed-flutter | Flutter client for iOS |
| nullfeed-tvos | Native Swift/SwiftUI tvOS app |
| nullfeed-demo | FastAPI demo server with Creative Commons content for App Store review |
This project is licensed under the GNU General Public License v3.0.