NostalgiaTV server - 30-day schedules, web UI, HDHR tuner, Plex integration
10K+
A self-hosted companion server for the NostalgiaTV Android app. Generates 30-day schedules, indexes your Plex, Jellyfin, or Emby content, manages channels and commercials, and exposes a browser-based Watch and Configure UI — plus an HDHR/IPTV tuner so other apps (Plex DVR, Channels DVR, Jellyfin, TiviMate, etc.) can tune in too.
Built for amd64 and arm64 — PCs, servers, Raspberry Pi 4/5, Synology/QNAP NAS, Apple Silicon Macs.
http://<server>:19850 in any browser/watch — Live TV guide with an inline player, plus On Demand, Movies, and TV Shows browsing. Sign in with Plex, Jellyfin, Emby, or a local account./configure — admin tabs: Channels, Commercials, Groups, Schedule, Design, Profile Configuration, Servers, Admin (External Sources, Automatic Backup, Logs).HALFWAY, CHAPTERS (from the item's own server), and SILENCE — real ffmpeg silence detection, which runs on the Docker server only (too CPU-heavy for a TV stick).Exposes your whole channel lineup to anything that speaks HDHR:
GET /discover.json, /lineup.json, /device.xml, /lineup_status.jsonGET /channels.m3u (M3U playlist) and GET /xmltv.xml (EPG)GET /stream/{channelId} for tuner playbackCreate a folder, save this as docker-compose.yml inside it:
services:
nostalgiatv:
image: purestream711/nostalgiatv-server:latest
container_name: nostalgiatv-server
ports:
- "19850:19850"
volumes:
- ./data:/app/data
- ./config:/app/config
- ./logos:/app/logos
environment:
- TZ=America/New_York
# Run as your host user so the bind-mounted folders above stay writable
# (find yours with `id -u` / `id -g`). See "Volumes & permissions" below.
- PUID=1000
- PGID=1000
restart: unless-stopped
Then start it:
docker compose up -d
Or pull and run directly, no compose file:
docker pull purestream711/nostalgiatv-server:latest
docker run -d \
--name nostalgiatv-server \
-p 19850:19850 \
-v "$(pwd)/data:/app/data" \
-v "$(pwd)/config:/app/config" \
-v "$(pwd)/logos:/app/logos" \
-e TZ=America/New_York \
-e PUID=1000 \
-e PGID=1000 \
--restart unless-stopped \
purestream711/nostalgiatv-server:latest
Then open http://localhost:19850 (or http://<your-server-ip>:19850 from another device) and sign in with Plex, Jellyfin, or Emby.
http://<server>:19850 — you'll land on the Watch UI's sign-in screen./configure and adjust channels, commercials, profiles, themes, etc.http://192.168.1.100:19850).The tuner is enabled by default. In your DVR app, point it at:
http://<server>:19850http://<server>:19850/channels.m3uhttp://<server>:19850/xmltv.xmlTo disable the tuner, set HDHR_ENABLED=false.
| Variable | Default | Description |
|---|---|---|
TZ | America/New_York | Timezone (affects schedule generation and EPG display) |
PUID | 1000 | Host user ID the server runs as, so files it writes to the bind mounts are owned by you. id -u to find yours |
PGID | 1000 | Host group ID the server runs as. id -g to find yours |
SCHEDULE_HOURS | 720 | Hours of schedule to generate ahead (720 = 30 days). Supported range 24–2160 (90 days) |
SCHEDULE_REFRESH_INTERVAL | 21600 | Seconds between scheduler ticks (21600 = 6 hours) |
SCHEDULE_REGEN_THRESHOLD_HOURS | 168 | Regenerate a channel when its remaining schedule falls below this many hours (168 = 7 days) |
PLEX_CONTENT_POLL_INTERVAL_SECONDS | 300 | How often to probe your servers for content adds/removals (5 minutes) |
HDHR_ENABLED | true | Whether the HDHR tuner emulator is on |
WEATHER_API_KEY | (bundled key) | WeatherAPI.com key for the native Storm Channel. Defaults to the same key the Android app ships with — override to use your own |
LOG_LEVEL | INFO | Logging level — DEBUG, INFO, WARNING, ERROR |
SCHEDULE_HOURS, SCHEDULE_REFRESH_INTERVAL, and HDHR_ENABLED can also be changed at runtime via POST /api/settings, which persists them to config/server_settings.json (they then override the env vars on restart).
| Path | Purpose |
|---|---|
/app/data | SQLite database, content index, registered devices, automatic backups, persistent state |
/app/config | Profiles, channel overrides, custom channels, themes, server settings |
/app/logos | Custom channel logos uploaded via the UI |
Back up data/ and config/ together — they're a matched pair.
The container starts as root, chowns data/, config/, and logos/ to PUID:PGID, then drops to that unprivileged user before running — so a first run on a native-Linux host (Synology, QNAP, Portainer, Dockhand, Raspberry Pi) works even though the Docker daemon creates those bind-mount folders as root. Set PUID/PGID to your host user (id -u / id -g) if it isn't the default 1000:1000, and the database and config will be created and owned correctly with no manual chown. Docker Desktop on Windows/macOS handles bind-mount ownership through its own file-sharing layer and generally works regardless of PUID/PGID.
The full API is documented at http://<server>:19850/docs (FastAPI auto-generated, ~230 endpoints across status, channels, schedule, profiles, servers, design, commercials, groups, content, HDHR, Live TV, trailers, weather, logs, backups, and config import/export).
Key endpoints for orientation:
GET /api/status — health check and server statePOST /api/register — Android device registration (returns API key)POST /api/register/jellyfin — first-run bootstrap for a Jellyfin/Emby-only installGET /api/schedule — bulk schedule download for app playbackGET/POST /api/servers/{id}/libraries — backend-neutral per-server library selectionGET /api/config/export and POST /api/config/import — full config transferGET /discover.json, /lineup.json, /channels.m3u, /xmltv.xml, /stream/{id} — HDHR/IPTVAll /api/* endpoints (except /api/status, /api/register*, and /api/auth/*) require either an X-API-Key header or an authenticated browser session. HDHR endpoints are intentionally unauthenticated so legacy tuner apps can reach them.
docker compose pull
docker compose up -d
Or with plain Docker:
docker pull purestream711/nostalgiatv-server:latest
docker stop nostalgiatv-server && docker rm nostalgiatv-server
# re-run your `docker run` command
Your data/, config/, and logos/ volumes are preserved.
Run from source instead of the published image:
docker compose -f docker-compose.dev.yml up -d --build
Or without Docker:
cd docker
pip install -r requirements.txt
uvicorn app.main:app --host 0.0.0.0 --port 19850 --reload
The latest image is published manually with a multi-arch buildx push — this is the only publish path. It reuses a persistent buildx instance named ntv-builder to avoid accumulating stray builders in Docker Desktop:
docker login -u purestream711
docker buildx use ntv-builder 2>/dev/null || \
docker buildx create --name ntv-builder --driver docker-container --use
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t purestream711/nostalgiatv-server:latest \
--push .
The Docker Hub overview page is not linked to a source repo, so it does not auto-sync from this README — update the repository description manually (or via the Hub API) after changing this file.
Content type
Image
Digest
sha256:2578bfe71…
Size
232.2 MB
Last updated
5 days ago
docker pull purestream711/nostalgiatv-server