Getting Started¶
Get preview thumbnails generating in minutes — for Plex, Emby, Jellyfin, or any combination of them.
Important
This page is the source of truth for installation and first-time setup. For operations and troubleshooting, use Guides & Troubleshooting. For exact settings and API contracts, use Configuration & API Reference.
Contents¶
- Prerequisites
- Quick Start (Docker)
- Recommended media-server settings
- Volume Mounts
- Authentication Token
- Docker Compose
- GPU Acceleration
- Unraid
- Networking
- Common Operations
- Next Steps
Related Docs¶
Prerequisites¶
- At least one media server reachable from this container — any of:
- Plex Media Server — sign in with your Plex account in the Setup Wizard, or paste a server URL + token if you'd rather skip the sign-in
- Emby Server — server URL + API key
- Jellyfin Server — use Quick Connect in the Setup Wizard (a friendlier sign-in code, no token to copy), or paste a server URL + API key
- Docker installed on your server
You can configure several servers — even a mix of vendors — and a single FFmpeg pass will publish to every server that owns the file.
Quick Start (Docker)¶
Step 1: Run the Container¶
docker run -d \
--name media-preview-generator \
--restart unless-stopped \
-p 8080:8080 \
--device /dev/dri:/dev/dri \
-e PUID=1000 \
-e PGID=1000 \
-v /path/to/media:/media:ro \
-v /path/to/plex/config:/plex:rw \
-v /path/to/app/config:/config:rw \
-v /etc/localtime:/etc/localtime:ro \
stevezzau/media_preview_generator:latest
Replace /path/to/media, /path/to/plex/config, and /path/to/app/config with your actual paths.
Important
This command is for Plex, which only writes into /plex, so the media can stay :ro. Using Emby, or Jellyfin? Change the media mount to :rw, because those previews are written next to each video. (Jellyfin's off-media mode is the exception.) See Volume Mounts.
Note
No environment variables are required for first-time setup. Server connections (Plex / Emby / Jellyfin), libraries, GPU/CPU threads, and path mappings are all configured in the Setup Wizard and Settings. Environment variables are optional overrides (see Reference).
Tip
Timezone: The /etc/localtime mount ensures log timestamps and scheduled jobs use your local time. If your host doesn't have this file (e.g. some NAS devices), use -e TZ=America/New_York instead (replace with your timezone).
Step 2: Get Your Access Token¶
Find your token using the Authentication Token section below.
Step 3: Complete the Setup Wizard¶
- Open
http://YOUR_SERVER_IP:8080 - Enter the authentication token from the logs
- Follow the wizard:
- Step 1 — Pick your first server: Plex, Emby, or Jellyfin. The vendor card expands to its friendliest sign-in flow:
- Plex → Sign in with Plex OAuth (or paste a URL + token). If your Plex account has access to multiple servers, the wizard lists them all — tick one to configure in detail, or tick several to batch-add with shared defaults.
- Emby → enter the server URL + username/password (or API key).
- Jellyfin → enter the server URL + Quick Connect code (shown inside Jellyfin's web UI — no password leaves your browser), or paste a URL + API key.
- Step 2 — Libraries (Plex single-server flow): pick which Plex libraries to enable. Emby and Jellyfin flows skip this step; libraries are managed later from the Servers page alongside their per-server path mappings.
- Step 3 — Path Configuration (Plex single-server flow): confirm the Plex application data folder (where Plex stores its preview bundles — usually mounted as
/plexon this container) and any media path mappings. - Step 4 — Processing Options: GPU/CPU workers, FFmpeg threads, thumbnail interval, quality — these are global (shared across every configured server).
- Step 5 — Security: view or replace your access token (optional).
You can add more servers — any vendor, any number of each — at any time from Servers in the top nav. A file present on multiple servers is generated once and published to each in its native format.
Recommended media-server settings¶
This tool generates video preview thumbnails only — the small frames you see when you drag the scrub bar. Plex stores them as BIF bundles, Emby reads a BIF sidecar next to the media file, and Jellyfin reads a folder of JPG tile sheets called trickplay next to the media file. The app writes the right format for each. It does not generate chapter thumbnails, intro/credit detection, or other media analysis.
Turn off built-in preview generation on each server you configure so this tool isn't competing with a redundant CPU job:
- Plex — Settings → Library → Generate video preview thumbnails → Never.
- Emby — Emby can make its own thumbnails during library scans. Turn
off its scan-time thumbnail extraction and chapter-image extraction on
each library (
ExtractTrickplayImagesDuringLibraryScanandExtractChapterImagesDuringLibraryScan). The Previews Readiness card recommends this and turns them off when you click Disable; the app never changes them on its own. - Jellyfin — keep "Trickplay image extraction" enabled on each library (Jellyfin only reads this app's published tiles when that flag is on). What to do with "Extract trickplay images during library scan" depends on the Media Preview Bridge plugin:
- Plugin installed: turn it off. The plugin registers new tiles directly, so Jellyfin's own extraction is wasted CPU.
- No plugin: keep it on. Without the plugin, this flag is what lets Jellyfin pick up the tiles on its next library scan. Off means new previews wait for Jellyfin's daily trickplay task (3 AM by default).
The Previews Readiness card (Servers → Edit → Setup Health) checks all of these and can toggle them for you.
Tip
After setup, you probably want one or both of:
- Radarr/Sonarr webhooks — auto-process new imports.
- A daily cron schedule (0 2 * * *) in the web UI under Schedules — catches anything the webhooks miss.
Volume Mounts¶
| Container Path | Purpose | Mode | Required when… |
|---|---|---|---|
/media |
Your media files. FFmpeg reads the videos from here. | ro if only Plex is configured. rw if Emby is configured, or Jellyfin in its default layout. |
Always. |
/plex |
Plex application data (where Plex stores BIF bundles) | rw |
Plex is configured — BIFs land inside Plex's config at Media/localhost/<hash>/.../index-sd.bif. Omit this mount entirely if you don't configure any Plex server. |
/config |
This app's settings, schedules, and job history | rw |
Always |
Which servers need the media mounted read-write:
- Plex: no. Previews go into
/plex. Nothing is written next to the video, so:rois fine. - Emby: yes. The BIF is written next to each video (for example
<video>-320-10.bif). - Jellyfin, default layout: yes. Tiles are written next to each video (
<video>.trickplay/). - Jellyfin, off-media mode: no. Tiles go into Jellyfin's data folder instead. This needs the Media Preview Bridge plugin and Jellyfin's config folder mounted read-write in this container. See off-media mode.
With a :ro media mount, Emby and default-layout Jellyfin can't save anything. Each file fails for that server with "Could not write preview file: … Read-only file system" in the job's per-server result and the logs. Those files aren't retried. Other servers on the same file are unaffected. No setup check warns about this in advance.
Note
Jellyfin and Emby don't need Plex's config mount. Emby BIFs and
default-layout Jellyfin tiles are written next to each media file under
/media, and the scan-nudge is sent over HTTP. So a Jellyfin-only or
Emby-only setup needs /media (read-write) + /config. Jellyfin's
off-media mode needs Jellyfin's config folder (read-write) as well. A mixed
setup needs whichever mount(s) each configured server requires.
Authentication Token¶
Use this section whenever documentation asks for your authentication token.
You can also set a fixed token for predictable logins:
Docker Compose¶
See docker-compose.example.yml for ready-to-use configurations:
| Configuration | Use Case |
|---|---|
| GPU | Single service covering Intel, AMD, and NVIDIA with inline comments |
| CPU-Only | Minimal service; disable all GPUs in Settings after first run |
| Unraid | Intel iGPU default with NVIDIA alternative, Unraid paths and permissions |
Copy the file, uncomment the section for your hardware, and adjust volume paths.
Warning
Don't set init: true. This container manages its own processes internally; init: true conflicts with that and prevents the container from starting.
GPU Acceleration¶
Hardware-accelerated video processing for faster thumbnail generation. To check what's detected on your system, open the web UI (http://YOUR_IP:8080) and go to Settings or Setup — detected GPUs are listed there with device IDs, names, and types.
Supported GPUs¶
| GPU Type | Platform | Acceleration | Docker Support |
|---|---|---|---|
| NVIDIA | Linux | CUDA/NVENC | NVIDIA Container Toolkit |
| AMD | Linux | VAAPI | Device passthrough |
| Intel | Linux | VAAPI/QuickSync | Device passthrough |
| NVIDIA | Windows | CUDA/NVENC | Docker Desktop (WSL2 backend) + --gpus all |
| AMD/Intel | Windows | — | Not available — CPU only |
| Apple Silicon / Intel | macOS | — | Not available — CPU only |
Note
NVIDIA on Windows works under Docker. The NVIDIA Windows driver exposes CUDA and NVDEC into WSL2, so Docker Desktop using the WSL2 backend accelerates much like Linux does. Follow the NVIDIA GPU steps below — you do not need to install anything inside WSL. Treat it as best-effort: GPU detection under WSL2 is less reliable than on a native Linux host.
Note
AMD/Intel on Windows and all GPUs on macOS cannot be accelerated under Docker. Docker Desktop runs a Linux VM, and D3D11VA (Windows AMD/Intel) and VideoToolbox (macOS) are host-OS frameworks that the VM cannot reach. Those setups process on CPU — raise CPU Workers in Settings to compensate. If you need GPU acceleration, run the container on a Linux host. Apple Silicon users still benefit from the native ARM64 image (no Rosetta overhead).
Intel iGPU (QuickSync)¶
Most common setup, especially on Unraid.
docker run -d \
--device /dev/dri:/dev/dri \
-e PUID=1000 \
-e PGID=1000 \
stevezzau/media_preview_generator:latest
Verify the device exists:
The container auto-detects GPU device groups at startup and adds the
internal user to them. If you still see permission errors, check that you are
passing the entire /dev/dri directory (not a single sub-device):
# Correct — pass the whole directory
--device /dev/dri:/dev/dri
# Wrong — single device may prevent group auto-detection for other nodes
--device /dev/dri/renderD128:/dev/dri/renderD128
To debug, find the device group on the host:
NVIDIA GPU¶
Prerequisites (Linux hosts):
- Install NVIDIA drivers
- Install NVIDIA Container Toolkit
Note
On Windows, skip step 2. Docker Desktop provides the toolkit itself — you only need the NVIDIA Windows driver and the WSL2 backend. See Windows below. The docker run command itself is identical.
docker run -d \
--gpus all \
-e NVIDIA_VISIBLE_DEVICES=all \
-e NVIDIA_DRIVER_CAPABILITIES=all \
stevezzau/media_preview_generator:latest
Tip
Why NVIDIA_DRIVER_CAPABILITIES=all? Dolby Vision Profile 5 videos need the NVIDIA Vulkan driver to be tone-mapped; the all value is what makes that driver available inside the container. Without it, the app skips tone mapping for those files and their thumbnails come out visibly dim. (The older compute,video,utility setting is fine for everything except Dolby Vision Profile 5.)
Tip
Multi-GPU? Hosts with two or more NVIDIA cards are detected automatically — each card appears as a separate row in Settings → Processing Options → GPU Configuration with its own enable toggle, worker count, and FFmpeg thread setting. Work spreads across cards.
Docker Compose:
services:
plex-previews:
image: stevezzau/media_preview_generator:latest
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
AMD GPU¶
docker run -d \
--device /dev/dri:/dev/dri \
-e PUID=1000 \
-e PGID=1000 \
stevezzau/media_preview_generator:latest
AMD requires proper VAAPI drivers on the host system. GPU device groups are auto-detected at container startup.
Windows¶
NVIDIA: works under Docker. Requirements:
- Docker Desktop using the WSL2 backend (Settings → General → Use the WSL 2 based engine) — GPU support is WSL2-only, it does not work with the Hyper-V backend.
- A current NVIDIA Windows driver with WSL2 GPU paravirtualization support. This is what provides CUDA and NVDEC inside WSL2.
- An up-to-date WSL2 kernel — run
wsl --update.
Then use the same docker run command as Linux, including --gpus all; see NVIDIA GPU above. There is nothing to install inside WSL itself, and the NVIDIA Container Toolkit step is not needed — Docker Desktop wires that up for you.
Omit --device /dev/dri:/dev/dri on Windows. Under WSL2 that node is a WSLg/d3d12 stub rather than a real GPU, and passing it through can register a phantom device or muddy vendor detection.
Verify by opening the web UI and checking Settings → Processing Options → GPU Configuration; your card should be listed with CUDA acceleration.
Note
Treat WSL2 as best-effort. GPU vendor detection via lspci is unreliable inside WSL2, so the app leans on nvidia-smi instead and logs a note saying so. If GPU jobs misbehave, run the container on a Linux host.
AMD and Intel: not accelerated under Docker. These rely on D3D11VA, a Windows-only framework that Docker Desktop's Linux VM cannot reach, so processing falls back to CPU. Raise CPU Workers in Settings to compensate, or run the container on a Linux host to use VAAPI/QuickSync.
macOS¶
Not accelerated under Docker, on either Apple Silicon or Intel. VideoToolbox is a macOS framework and Docker Desktop's Linux VM has no access to it, so processing uses CPU. Raise CPU Workers in Settings to compensate, or run the container on a Linux host for GPU acceleration.
Apple Silicon Macs do still benefit from the native ARM64 image — the container runs without Rosetta emulation overhead, which makes CPU processing meaningfully faster than an emulated x86 image.
Worker Configuration¶
A worker is a parallel slot that processes one file at a time — more workers means more files processed simultaneously, but higher CPU/GPU load.
In Settings → Processing Options, the GPU panel lists all detected GPUs. Enable or disable each GPU independently and set workers and FFmpeg threads per GPU. For CPU-only mode, disable every GPU (or set workers to 0) and set CPU Workers to your desired value (e.g. 8).
Performance Tuning¶
| Workers | Recommendation |
|---|---|
| 1 GPU × 1 worker, CPU: 1 | Default (safe for all hardware) |
| 1 GPU × 4 workers, CPU: 2 | Balanced (mid-range systems) |
| 1 GPU × 8 workers, CPU: 4 | High-end systems |
| 0 GPU, CPU: 8 | CPU-only |
Configure per-GPU workers and FFmpeg threads in Settings → Processing Options. Start with the defaults and increase gradually; monitor system load to find the best balance.
Unraid¶
Two install paths: the Community Applications template (easiest) or a manual docker run command (more control).
Easiest: search for media-preview-generator in Community Applications and install the template.
Quick start (either path):
- Run the container (CA template or
docker runbelow). - Open the Web UI at
http://YOUR_UNRAID_IP:8080. - Get the authentication token from container logs, or set
WEB_AUTH_TOKENon the container. - Complete the Setup Wizard — sign in with Plex, configure settings.
Tip
The setup wizard guides you through Plex OAuth, Emby URL + API key, or Jellyfin Quick Connect — no need to copy tokens by hand for any of the supported vendors.
Manual Docker Run — Intel iGPU (Most Common)¶
docker run -d \
--name media-preview-generator \
--restart unless-stopped \
-p 8080:8080 \
-l net.unraid.docker.webui="http://[IP]:[PORT:8080]/" \
--device /dev/dri:/dev/dri \
-e WEB_AUTH_TOKEN=my-secret-password \
-e PUID=99 \
-e PGID=100 \
-v /mnt/user/data/plex:/data/plex:ro \
-v "/mnt/cache/appdata/plex/Library/Application Support/Plex Media Server":/plex:rw \
-v /mnt/user/appdata/media-preview-generator:/config:rw \
-v /etc/localtime:/etc/localtime:ro \
stevezzau/media_preview_generator:latest
| Setting | Value | Description |
|---|---|---|
-l net.unraid.docker.webui |
Web UI label | Adds a Web UI button in Unraid |
--device /dev/dri |
GPU passthrough | Intel VAAPI/QuickSync |
WEB_AUTH_TOKEN |
my-secret-password |
Your login password (set your own!) |
PUID/PGID |
99/100 |
Unraid nobody:users account |
/data/plex |
Media path | Same as Plex container = no path mapping |
/config |
App config | Settings persist here |
Intel iGPU with Custom Network (br0/br1)¶
If you use a custom Docker network with fixed IPs (common on Unraid):
docker run -d \
--name media-preview-generator \
--restart unless-stopped \
--network=br0 \
--ip=192.168.1.50 \
--device /dev/dri:/dev/dri \
-e WEB_AUTH_TOKEN=my-secret-password \
-e PUID=99 \
-e PGID=100 \
-v /mnt/user/data/plex:/data/plex:ro \
-v "/mnt/cache/appdata/plex/Library/Application Support/Plex Media Server":/plex:rw \
-v /mnt/user/appdata/media-preview-generator:/config:rw \
-v /etc/localtime:/etc/localtime:ro \
stevezzau/media_preview_generator:latest
Note
When using --network with a fixed IP, you don't need -p 8080:8080 — access the web UI directly at http://192.168.1.50:8080
NVIDIA GPU on Unraid¶
Requires: Nvidia-Driver plugin from Community Applications.
docker run -d \
--name media-preview-generator \
--restart unless-stopped \
--runtime=nvidia \
-p 8080:8080 \
-e NVIDIA_VISIBLE_DEVICES=all \
-e NVIDIA_DRIVER_CAPABILITIES=all \
-e WEB_AUTH_TOKEN=my-secret-password \
-e PUID=99 \
-e PGID=100 \
-v /mnt/user/data/plex:/data/plex:ro \
-v "/mnt/cache/appdata/plex/Library/Application Support/Plex Media Server":/plex:rw \
-v /mnt/user/appdata/media-preview-generator:/config:rw \
-v /etc/localtime:/etc/localtime:ro \
stevezzau/media_preview_generator:latest
Warning
On Unraid, use --runtime=nvidia — not --gpus all. If you see
docker: Error response from daemon: AMD CDI spec not found, that's
Docker trying to resolve --gpus all through the Container Device
Interface (CDI). Unraid doesn't ship CDI specs, so the daemon falls
through to whatever spec it finds. Switching to --runtime=nvidia
bypasses CDI and uses the Nvidia-Driver plugin directly.
Important Unraid Notes¶
PUID/PGID Values — Unraid uses nobody:users by default:
| Variable | Value | Description |
|---|---|---|
PUID |
99 |
nobody user |
PGID |
100 |
users group |
Emby or Jellyfin on Unraid — the commands above are for Plex. For Emby, or Jellyfin in its default layout, change /data/plex:ro to /data/plex:rw, because those previews are written next to each video. See Volume Mounts.
Network Considerations — When completing the Setup Wizard, make sure each media server you pick is reachable from the container (not localhost from Unraid's perspective). Plex appears as a dropdown after OAuth sign-in; Emby and Jellyfin are connected by URL.
Check Intel GPU Exists:
Path Mapping for Plex¶
Path mapping is only needed when this container mounts media to a different path than Plex uses.
No Mapping Needed (Recommended) — mount media to the same container path as Plex:
Plex: /mnt/user/data/plex → /data/plex
This container: /mnt/user/data/plex → /data/plex ← Same path!
With Path Mapping — if you prefer mounting to /media:
Plex: /mnt/user/data/plex → /data/plex
This container: /mnt/user/data/plex → /media ← Different path
Add path mapping:
See Path Mappings for more examples.
TRaSH Guide Folder Structure¶
For users following TRaSH Guides:
- Configure Plex Container — add a second container path:
- Container path:
/server/media/plex/ -
Host path:
/mnt/user/media/plex/ -
Update Plex Libraries — use the new mapping:
- Format:
//server/media/plex/<media-folder> -
Example:
//server/media/plex/tv -
Set Permissions:
(Older guides recommended# Make the Plex Media folder writable by the Unraid 'users' group (PGID=100) # so this app's container (running as PUID=99 / PGID=100) can write previews into it. chown -R 99:100 /mnt/cache/appdata/plex/Library/Application\ Support/Plex\ Media\ Server/Media/ chmod -R 775 /mnt/cache/appdata/plex/Library/Application\ Support/Plex\ Media\ Server/Media/chmod 777— that works but grants world-writable access;chown+775is the safer equivalent.)
Networking¶
Important
Use a routable IP address for your media servers, not localhost. The
container can't reach localhost on your Docker host. If you set a server
URL manually, use something like http://192.168.1.100:32400 (Plex),
http://192.168.1.100:8096 (Emby/Jellyfin), etc. The Setup Wizard picks
routable URLs for you when it can.
Quick Decision Tree¶
Where is the media server?
│
├── Same Docker host as this app?
│ ├── Server uses host network → Use --network host
│ ├── Server uses bridge (default) → Use same network or server IP:port
│ └── Server uses custom (br1, macvlan) → Use same custom network
│
└── Different machine?
└── Use bridge network with the server's IP address
The same logic applies whether the target is Plex (port 32400), Emby (port 8096), or Jellyfin (port 8096). If you run more than one server, check each is reachable from the container independently — the Servers page shows a red connection pill for any server the app can't reach.
Custom Network Example (Unraid)¶
docker run -d \
--name media-preview-generator \
--restart unless-stopped \
--network=br1 \
--ip=192.168.1.51 \
-l net.unraid.docker.webui="http://[IP]:[PORT:8080]/" \
--device /dev/dri:/dev/dri \
-e WEB_AUTH_TOKEN=your-password \
-e PUID=99 \
-e PGID=100 \
-v /mnt/user/data/plex:/data/plex:ro \
-v "/mnt/cache/appdata/plex/Library/Application Support/Plex Media Server":/plex:rw \
-v /mnt/user/appdata/media-preview-generator:/config:rw \
-v /etc/localtime:/etc/localtime:ro \
stevezzau/media_preview_generator:latest
Note
Use Unraid's PUID=99, PGID=100 (nobody:users).
Common Operations¶
View Logs¶
Update¶
docker pull stevezzau/media_preview_generator:latest
docker stop media-preview-generator
docker rm media-preview-generator
# Re-run your docker run command
Your /config/settings.json persists between upgrades, so Plex auth, GPU config, and schedules come back automatically after re-running the container.
Image Tags¶
| Tag | Source | Use for |
|---|---|---|
:latest |
Latest GitHub release | Recommended. Stable. |
:X.Y.Z (version) |
A specific release (e.g. :3.7.5) |
Pinning to a known-good version |
:dev |
Every push to dev |
Bleeding edge — may break |
See the releases page for version history and per-release notes.
The web UI's version banner behaves accordingly: :latest / pinned versions
compare against the latest GitHub release; :dev compares the baked commit
SHA against the dev branch HEAD on GitHub.
Next Steps¶
- Run and monitor jobs from the Web Interface Guide
- Configure webhooks in Webhook Integration
- Review all tunables in Configuration & API Reference