What It Is
This is a self-built home media library: movies and TV series are stored on a 3TB disk on the NAS and managed and played uniformly through Jellyfin, viewable directly on phones, computers, and TVs. External access uses https://video.xiejun-server.top; http://192.168.50.223:8096 is faster; the homepage https://xiejun-server.top also has a "Media" card entry.
Why Build It
Public platforms have scattered content, subscriptions keep getting more expensive, and you still have to worry about titles being removed. Manage your own titles yourself: to add titles, just drop the files into the corresponding NAS directory; Jellyfin automatically scans and adds them to the library, and posters and synopses are filled in automatically. Transcoding is handled by the Intel Iris Plus 655 iGPU (VAAPI) on .223, so weak clients such as TV boxes can also play smoothly without buying extra hardware.
Current Status
Running. The container is jellyfin/jellyfin:latest, and the compose file is in /opt/jellyfin; the public link is public 443 → frps on the Hong Kong VPS splitting by SNI → NPM reverse proxy to 192.168.50.223:8096. Recently fixed the issue where domestic DNS pollution prevented poster fetching: the container is pointed to local unbound, and only movie/TV metadata domains are sent through an SSH tunnel to Hong Kong for resolution; regular domains still use domestic DNS. The media library is currently empty, waiting for content.
Features
- NAS 上的电影/剧集目录自动扫描入库,加片就是丢文件
- Intel 核显 VAAPI 硬件转码,弱客户端也能流畅播
- 手机/外网与内网双入口,主页有「影音」卡片
- 公网 443 → 香港 VPS frps 按 SNI 分流 → NPM 反代
- 自建 DNS 通道解决 TMDB 被污染、海报抓不到的问题
- 带一键重装脚本与明确回退步骤
Stack: Jellyfin · Docker Compose · Nginx Proxy Manager · frp · VAAPI · unbound
Deployment Steps
1. Prepare directories Create the settings and cache directories on the host. Mount the media directories read-only:
/opt/jellyfin/config # 设置
/opt/jellyfin/cache # 转码缓存
/nas/影音/电影 # 只读
/nas/影音/剧集 # 只读
2. Write the compose file Place it at /opt/jellyfin/docker-compose.yml and use the jellyfin/jellyfin:latest image. Map ports 8096 (HTTP), 7359/udp (client discovery), and 1900/udp (DLNA). For hardware transcoding, pass /dev/dri/renderD128 into the container and specify dns: [192.168.50.223, 223.5.5.5]. Then run docker compose up -d.
3. First-time wizard Wait until Main: Startup complete appears in the logs before going through the /Startup/* wizard. Otherwise, you may encounter “the wizard is marked complete but no user was created,” and the only fix is to rebuild the database. Note that /health and /System/Info/Public return 200 early, so they cannot be used as readiness criteria.
4. Configure media libraries When adding a media library, the path must be the container path (e.g. /media/电影), not the host path. Naming recommendations: for movies, Title (Year).mkv, with one folder per movie; for TV series, Series Name/Season 01/Series Name S01E01.mkv. Afterward, you can manually trigger a scan via “Dashboard → Libraries → Scan All Libraries.”
5. Hardware transcoding In the dashboard, select VAAPI and choose renderD128 as the device. In testing, h264_vaapi encoding passed.
6. Public entry point Forward public port 443 to the frps on the Hong Kong VPS, split traffic by SNI, and then have NPM reverse proxy to 192.168.50.223:8096 using 12-jellyfin.conf; add video.xiejun-server.top on the frpc side.
7. What to do if posters fail to load First run dig +short @192.168.50.223 api.themoviedb.org. It is only normal if it returns a real CloudFront IP; a direct request to https://api.themoviedb.org/3/ from inside the container should return 401. If posters suddenly disappear, first run systemctl restart dns-hk-tunnel.
Rollback: Delete NPM’s 12-jellyfin.conf and reload, then delete the corresponding two lines in frpc and restart. For complete removal, run docker compose down && rm -rf /opt/jellyfin.