Retro TV for your own movie and show library. Free and open source, for Plex, Jellyfin and Emby.
Nostalgex turns your library into 130+ channels in 14 bundles, plus a channel for every film series you own, with a real channel guide. Every channel plays on a set daily schedule, so tuning in feels like live TV. No account to make, and it never supplies any media of its own.
| Apple TV app | Web tuner | Docker container | |
|---|---|---|---|
| What it is | An app for your TV and Siri Remote | Nostalgex in your browser | The web tuner, running on a computer at home |
| Best for | Watching on a TV | Trying it right now, on a laptop or phone | Jellyfin or Emby setups the web tuner can't reach |
| Works with | Plex, Jellyfin, Emby | Plex, Jellyfin, Emby | Plex, Jellyfin, Emby |
| Get it | Open the web tuner |
Docker container guide |
All three are free. Not sure? Use the app on a TV and the web tuner everywhere else.
The best way to watch on a TV. Connect one or more servers, then flip channels with the Siri Remote. Needs tvOS 18 or later.
Quick start: install Nostalgex from the App Store, connect your server, pick a channel.
Nothing to install. Works on computers and phones.
Quick start: open nostalgex.app/web-tuner and sign in to your server. Plex just works. Jellyfin and Emby need a secure (https) address; if yours doesn't have one, use the Docker container instead.
The same web tuner, packaged as a Docker container that runs on a computer at home, so it can reach servers the website can't. Most Plex users don't need it.
Quick start:
docker run -d --name nostalgex -p 8080:8080 --restart unless-stopped ghcr.io/chadmueller/nostalgex:latestThen open http://YOUR-SERVER-IP:8080. Step by step help is in the Docker container guide.
Latest: tuner-v1.0.6. A new sign-in for Jellyfin and Emby, the same one the Apple TV app has: Quick Connect by default, your server's users to pick from, and the Docker tuner finds a server running on the same machine. Release notes. Already running it? docker compose pull && docker compose up -d.
The App Store went straight from 1.0.23 to 1.0.26, so this rolls up 1.0.24, 1.0.25 and 1.0.26. Full release notes
- The Docker container launched as
tuner-v1.0.0. It's ontuner-v1.0.6now. - Emby is officially supported. Thank you to the Emby community for all the logs and testing.
- Franchises. Each film series in your library (Star Wars, Rocky, whatever you own) gets its own channel and its own switch in Settings. Server collections land in FRANCHISES or OTHER COLLECTIONS. NOTABLE STARS is retired.
- iCloud sync (Settings > CONNECTION). Every Apple TV on your Apple ID shares the same lineup and settings. Off by default, and server logins never leave the device.
- REPORT PLAYBACK. Shows what you're watching in your server's Now Playing and marks a film watched once you've seen most of it, without flooding Continue Watching.
- Phone web tuner. Full width video, the guide in the right place, controls behind one menu.
- Tip jar: Popcorn, TV Dinner, Pizza Night.
- Reliability: large library crash fixed, Emby libraries load again, Plex titles with a dash (Spider-Man) keep their full name, clear cards when something can't play, stalled Jellyfin and Emby streams recover, multi-version and multipart Plex files play correctly.
- Website: nostalgex.app
- Apple TV app: App Store
- Chat: Discord
- Ideas and questions: GitHub Discussions
- Instagram: @nostalgex_
- Updates by email: sign up
- Enjoying it? Buy me a coffee
Nostalgex is free. If it gives you a good Friday night, you can buy me a coffee or leave a tip in the Apple TV app (Settings has Popcorn, TV Dinner and Pizza Night). Tips are a thank you. They cover the Apple developer fee and keep updates coming. The Sponsor button at the top of this repo goes to Buy Me a Coffee.
Helping costs nothing too: star the repo, rate the app on the App Store, or tell me about a channel that's always empty.
Privacy: what leaves your network
- Your media server. Direct from the Apple TV to your Plex, Jellyfin or Emby. Sign-in goes straight to the server, and Show Now Playing and Mark Watched only talk to that same server. No credentials are ever sent anywhere else.
- plex.tv (Plex only). The PIN sign-in flow and server discovery, like every Plex client.
- iCloud (only if you turn on sync). Your lineup and settings, in your own iCloud. Server logins stay on the device.
- MusicBrainz. Public metadata for the music video channels. No key, no account.
- TMDB, via nostalgex.app. When Supabase values are present, the TMDB ids of titles in your library go to the site's proxy (
api/tmdb.js, in this repo), which forwards them to TMDB with the site's key, keeps no log, and caches the answer at the edge. OMDb only if you build with a key. Off in your own build unless you add the Supabase values. - Supabase. The shared enrichment cache, keyed on TMDB id. Same condition as above.
- TelemetryDeck. Anonymous usage signals in the App Store build. No identifiers, no library contents, no server addresses. Off in your own build unless you keep the key.
The website and web tuner at nostalgex.app load one page analytics script (statsngraphs). The Docker container has its own list in the Docker container extras below. The privacy policy at /privacy covers all of this in plain language.
Docker container extras: Compose, updates, devices and limits
The image is nginx serving static files on port 8080, no database, nothing to sign up for. Because it's plain http on your own network, it can reach a plain http:// Jellyfin or Emby server, which the https site can't. You probably don't need it if you use Plex, or if your Jellyfin or Emby is already on https with a valid certificate.
Port 8080 already taken? Use -p 8099:8080 and open port 8099 instead.
Compose (fuller version in docker/compose.yaml, which also sets no-new-privileges), then docker compose up -d:
services:
nostalgex:
image: ghcr.io/chadmueller/nostalgex:latest
container_name: nostalgex
ports:
- "8080:8080"
environment:
NOSTALGEX_ENRICHMENT: "on" # "off" for no outside requests, see below
read_only: true
tmpfs:
- /tmp
restart: unless-stoppedUpdate: docker compose pull && docker compose up -d, or docker pull ghcr.io/chadmueller/nostalgex:latest and recreate the container. Image tags are latest, 1.0 and 1.0.0; each release adds its own version tag. Releases are tagged tuner-v*. The bottom of Settings says which one you're running, e.g. WEB TUNER 1.0.6 · DOCKER.
Platforms: amd64 and arm64 (Synology and Unraid on Intel or AMD, NUCs, Raspberry Pi 4 and 5 on a 64 bit OS). No armv7. Runs as a non root user (uid 101) and works with a read only root filesystem as long as /tmp is writable.
Where it's been tested: Chrome, Safari and Firefox on a Mac, iPhone and iPad Safari, and the TV Bro browser on Google TV. Fire TV Silk, Samsung and LG browsers are untested. Roku has no browser. On Apple TV, use the app. The current table is in the Docker container guide.
What the Docker container talks to:
| Where | When | Turn it off |
|---|---|---|
| Your media server | Always. Sign-in and playback go straight from the browser to your server. | |
| plex.tv | Plex only: PIN sign-in and finding your server, like every Plex app. | Use Jellyfin or Emby |
| The shared metadata cache (Supabase) and the nostalgex.app TMDB/OMDb relay | On by default. Nine or more channels need keywords, studios or IMDb ratings to fill (TEEN MOVIES, TRUE CRIME, CULT CLASSICS and others). Only TMDB and IMDb ids leave the browser: no titles, no server address, no credentials. | NOSTALGEX_ENRICHMENT=off |
No analytics, no scripts or fonts from a CDN, and no MusicBrainz or Deezer lookups in the image. A value the container doesn't recognize is treated as off.
- A reverse proxy puts the wall back. If the tuner is behind https and Jellyfin is plain http, the browser blocks Jellyfin again. Serve both the same way.
- Surround audio on TV browsers. Some can't decode 5.1. Try a laptop or phone first, and use the Apple TV app on a TV.
- Music video genres from MusicBrainz and Deezer are off. Titles and artists still show.
docker build -t nostalgex .
docker run -d --name nostalgex -p 8080:8080 nostalgexEnrichment needs the shared cache's address and public anon key at build time: add --build-arg VITE_SUPABASE_URL=... --build-arg VITE_SUPABASE_ANON_KEY=..., or point them at your own Supabase project. Without them it builds with enrichment off. To skip Docker, npm run build:selfhost writes the same files to dist-selfhost/; any static server works if it serves .cjs as JavaScript.
Reporting a container problem? Include your server and its version, the machine running Docker, the browser, the output of docker logs nostalgex, and anything red in the browser console.
Why the web tuner can't reach some servers
The hosted tuner is served over HTTPS, so browsers block mixed content and it cannot reach a server on plain http:// or on a raw LAN IP. Plex works because plex.direct issues real certificates for local addresses. Jellyfin and Emby need an HTTPS URL with a valid certificate (reverse proxy or tunnel). A local only server with no HTTPS is reachable from the Apple TV app but not from the hosted site, unless you use the Docker container, where it is plain http too.
How channels and schedules work
Both apps read the same channel configuration, channels.json (130+ channels in 14 bundles). The Apple TV app ships its own copy, so a channel change reaches it with an app update. The web tuner reads the site's copy on every load, and the Docker image carries the copy it was built with. Franchise and collection channels are built on top of that from your own library. See CHANNELS.md for a readable breakdown of each channel.
Both apps use a daily seeded shuffle. Each channel's pool is shuffled once per day (seeded by date + channel ID), then looped end to end. Your spot in the loop comes from the clock, so tuning in at 2:15 PM always lands at the same place that day. On tvOS, when a video ends the next one starts from the beginning; on first tune in you join mid stream like real TV.
The two schedules have diverged. tvOS adds premiere priority, sequel adjacency, prime time premieres for new additions, and a 6 hour refresh. The web tuner has none of those, so the same server on the same day will not show the same lineup in both places.
Channel rules
Channels filter your library (Plex, Jellyfin or Emby, normalized to one internal model) using combinations of:
- type: movie or episode
- genres: include, exclude, requireAll
- studios: Disney, HBO, etc.
- yearRange: min/max year
- contentRatings: whitelist (TV-Y, PG, R, etc.)
- durationRange: min/max minutes
- watchedOnly / unwatchedOnly / rewatched: watch history filters
- timeRestrictions: block mature content before a set hour
Franchise channels
Each film series in your library (Star Wars, Rocky, whatever you own) gets its own channel and its own switch in Settings. Server collections land in FRANCHISES or OTHER COLLECTIONS. NOTABLE STARS is retired. There's no fixed list: it depends on your library.
Help, support and other devices
- Bugs and feature requests: GitHub Issues. Include your server (Plex, Jellyfin or Emby), its version, and your Apple TV model.
- Email: [email protected]
- Help pages: support, Docker container guide and docs/FAQ.md.
Other devices: a Roku port by tydude001 is in the works (discussion #8); it installs by sideloading for now. A Fire TV fork by WSINTRA exists; it's unofficial and I don't support it. For a supported option on a TV today, use the Apple TV app, or the Docker container in a TV browser.
Who maintains it and how often it updates
Nostalgex has been on the App Store since April 19, 2026, and gets updates every month. I'm one person, and I read every issue.
- Releases: 1.0.26 is live on the App Store. History is in GitHub releases, the App Store version history and
content/changelog.json. - Dev log: docs/DEVLOG.md explains why things changed and what's still half done.
- Outside contributors: thank you to @w6trp for #18, #19, #20 and #21 in 1.0.26, and to @Gorbataras for #5 and #7 (Emby seek fix, and no more sound over a black picture on Jellyfin and Emby).
- Tests run in CI on every push to
mainand on every pull request.
Contributing
Issues and pull requests are welcome. A few things that make them land faster:
- The easiest first PR is a rule change in
channels.json(a title on the wrong channel, a channel that is always empty).CHANNELS.mdexplains every rule field. - Keep PRs small and about one thing.
- Web changes:
npm testmust pass. tvOS changes: build for a real Apple TV, not just the simulator, because the simulator keychain and storage behave differently. - Not sure where to start? Ask in Discord or open an issue first.
- One person maintains this in their spare time. You will get a reply, but not always the same day.
For developers: code layout, building and deploying
Source of truth: this repository (chadMueller/Nostalgex) contains the public site, web tuner, and tvOS app together. Read docs/REPOSITORY-LAYOUT.md for boundaries, deploy split, and how to avoid duplicating Nostalgex inside unrelated monorepos.
- Web tuner (
web-tuner.html+plex-tuner.html).web-tuner.html(routed as/web-tuner) is the connect screen; after sign-in it navigates toplex-tuner.html, the tuner itself. Stack: Vanilla HTML/JS, Vite, Vercel. - tvOS (
Nostalgex/). SwiftUI, AVPlayer, Xcode 26+, tvOS 18+. Key files:App/AppState.swift(playback engine, channel selection, filtering),Models/ChannelSchedule.swift(daily schedule builder),Models/Channel.swift(channel config and rules),Services/MediaBackend.swift(the backend protocol),Services/PlexAPIService.swift,Services/JellyfinAPIService.swift,Services/EmbyAPIService.swift(server clients),Views/(TunerView, PlayerView, ChannelGuideView, etc.). - Public site (
index.html). Marketing page and the path into the tuner, the Docker container guide and the App Store. Served at/on Vercel.
Web: npm install, then npm run dev.
tvOS: open Nostalgex/Nostalgex.xcodeproj in Xcode. Build target is Nostalgex (tvOS). To run on a physical Apple TV: Xcode > Window > Devices and Simulators > pair your Apple TV, then select it as the run destination.
Debug logging (tvOS): all playback logs are prefixed with [Plex90]. Filter the Xcode console to see channel selection, item loading, AVPlayer status changes, retries, and auto advance events.
Build it yourself (tvOS): you need Xcode 26 or newer and an Apple TV or the tvOS simulator. Open the project, pick your own team under Signing, and run the Nostalgex scheme. No API keys are required. Two optional extras, both off unless you turn them on:
| What | How to enable | What it adds |
|---|---|---|
| TMDB / OMDb / Supabase metadata | Copy Nostalgex/Secrets.example.xcconfig to Nostalgex/Secrets.xcconfig and fill in SUPABASE_URL and SUPABASE_ANON_KEY (see docs/REPOSITORY-LAYOUT.md). TMDB needs no key in the app: it goes through the site's proxy, api/tmdb.js. For a direct TMDB or OMDb connection set TMDB_API_KEY or OMDB_API_KEY as environment variables in the Xcode scheme. Read in Services/TMDBConfig.swift. |
Keyword and collection data for finer channel rules. Supabase is a cache keyed on TMDB ids; you can point it at your own project. |
| TelemetryDeck | TelemetryDeckAppID in Nostalgex/Info.plist. Delete the key to turn it off. |
Anonymous launch and playback counts. The App Store build has it on; your own build does not have to. |
Never commit real keys. .gitignore already covers .env, Secrets.plist and *.xcconfig secrets.
Deployment: the web app deploys to Vercel automatically on push to main. The Docker image is published to ghcr.io/chadmueller/nostalgex when a tuner-v* tag is pushed; the tagged commit bumps "tunerVersion" in package.json to match (see RELEASING.md).
License
MIT, see LICENSE. The Roku app in roku/ carries its own MIT license, roku/LICENSE. Bundled fonts and packages are listed in THIRD-PARTY-LICENSES.md.
The Nostalgex name, icon and the nostalgex.app site are not part of the MIT grant. Fork the code all you like, but please ship it under your own name.
