A Public Demo for YT ▶ PLEX
Why: Damien wanted people to be able to try yt-plex before installing it: "a safe way to demo this that balances maximum accuracy with basic internet safety."
The demo lives at https://yt-plex.skyhouse.dev, and skyhouse.dev/yt-plex/demo/ redirects there. It runs the same code as the real app with YTP_DEMO=1. Everything demo-specific sits on top of the app in ytplex/demo.py plus two small scripts the server adds to the pages, so the main code barely changed. The core gained a per-request database switch, live updates scoped to their database, two columns, and two hooks in main.py.
1. What's real and what's pretend
- Real: yt-dlp lookups on any site, TVDB episode matching, Radarr title suggestions and Sonarr show search (through our own instances), playlists (first 8 videos), and the whole UI, including the Settings pages, History, Details/Edit and guest links.
- Pretend: downloads (a progress bar labelled as a demo, then a "saved" row with the path it would have), uploads and file links (a sample Blender open movie stands in, and no file bytes are accepted), rename/move/delete, "Refresh in Plex", and subtitle checks. Posters and thumbnails are shown from their original addresses (
/thumb/…redirects), so the server stores no images at all. - Settings: sample libraries (Movies, TV Shows, Standup, Documentaries, Concerts, News, Sports, Personal), Damien's five shows, and example connection values (
*.exampleaddresses, fake keys). Library ticks and shows save within the visitor's sandbox; connection and account settings never save.
2. A sandbox per visitor
Each browser gets its own SQLite database in /home/plex/yt-plex-demo/data/sandboxes/, so nobody ever sees what someone else pasted. A sandbox is deleted after 24 hours unused (7 days at most), each holds up to 50 videos, there are at most 300 at once, and one address can start at most 10 an hour. Guest links are recorded against the sandbox that made them, so a real /u/<code> link opens the real guest page on another device. Its "upload" arrives on the creator's Home page as a sample video.
3. Arbitrary links, without exposing the LAN
Damien wanted every site to work, not just YouTube. The real danger in letting strangers paste links is that the server could be made to fetch things on the home network. Action: every yt-dlp run in the demo goes through a small proxy inside the app. It resolves each address itself and only connects to public internet IPs on ports 80/443/8080/8443. I tested it with 192.168.1.x, localhost, [::1], the cloud-metadata address, and a public redirect that bounces back to 127.0.0.1. All were refused with a plain message. archive.org and SoundCloud lookups worked.
Rate limits protect the home IP's standing with YouTube: 5 lookups a minute by hand and 20 an hour per address (a playlist's videos count toward the hour but not the minute), and 200 a day for everyone together. Past the hourly or daily limit, a sample video stands in. NPM passes real client addresses (we're not behind Cloudflare), and the container trusts forwarded addresses only from NPM's network.
4. The container
docker run -d --name yt-plex-demo --restart unless-stopped --user 1000:1000 \
--env-file /home/plex/yt-plex-demo/demo.env -v /home/plex/yt-plex-demo/data:/data \
--network npm_default --read-only --tmpfs /tmp --cap-drop ALL \
--security-opt no-new-privileges --memory 1g --cpus 1.5 --pids-limit 256 \
yt-plex-demo:latest uvicorn ytplex.main:app --host 0.0.0.0 --port 8430 --proxy-headers \
--forwarded-allow-ips=172.18.0.0/16 --timeout-graceful-shutdown 5
docker network connect media-downloads_default yt-plex-demo
- No host port and no media mounts. NPM reaches it as
yt-plex-demo:8430onnpm_default, and it reachesradarr:7878/sonarr:8989onmedia-downloads_default. demo.env(mode 600) holds the demo switch, its public address and the Radarr/Sonarr keys.- To update:
docker build -t yt-plex-demo:latest /home/plex/yt-plex, thendocker rm -f yt-plex-demoand the two commands above. - Damien's part: DNS for
yt-plex.skyhouse.devand an NPM proxy host (http →yt-plex-demo:8430, Let's Encrypt, Force SSL).
5. Also in the real app
Testing turned up a Vimeo video that needs a Vimeo sign-in. yt-plex used to show yt-dlp's raw "use --cookies" advice and suggest YouTube cookies. Now a non-YouTube site that wants a sign-in gets a plain message: "Vimeo only shows this video to signed-in users. yt-plex can sign in to YouTube only…". I restarted yt-plex to pick it up.
Update (same day): Damien set up DNS and the NPM proxy host and the demo is live. At his request the demo banner is now a centered, full-width strip above the header with a flat rule under it, so the site below reads as the real app. I rebuilt and recreated the container with the commands above.
6. Release, with a link to the demo
The download page at skyhouse.dev/yt-plex/ now opens with Try the live demo / Download buttons. I republished both bundles with today's work: the demo, the plain sign-in messages, and the guest-link line joined to the file line under the URL box, at Damien's request.
Caught along the way: scanning the built image (not just the source) turned up compiled Python caches (ytplex/__pycache__) carrying local build paths like /home/plex/yt-plex/ytplex/routes/items.py. .dockerignore listed __pycache__, which only matches a top-level folder, so every published image since 2026-09-29 included them. They held paths only: no keys, addresses or data. Action: I changed the patterns to **/__pycache__ and **/*.pyc and rebuilt. The new image has no cached files and nothing private, so the published image no longer carries them. Image scanning is now part of the release checklist in the project notes. Both files were checked through the public URL, and a fresh container starts on /setup. I also rebuilt and recreated the demo container.
Net effect: anyone can try yt-plex end to end with real lookups, while the server downloads, stores and accepts nothing, and the home network stays out of reach.
← Back to Admin Hub