Building a Production-Grade Tdarr GPU Transcoding Stack for a Homelab
Rights and lawful use: Run this Tdarr workflow only against media you own or have permission to transcode and retain. A technical ability to rewrite a file does not grant a right to obtain, copy, distribute, or bypass controls on it.
Quick Answer
A production-grade Tdarr stack separates server policy from GPU workers, gives every node consistent media/cache paths, pins matching server and node images, and processes copied canary files before any replacement. Success requires a before probe, hardware-encoder log, after probe, playback check, quarantine failure, restart test, and restore. Keep originals until the retention window closes. The Compose, flow, commands, and expected states below are documentation-backed examples; TechGeeks did not execute them for this revision.
A basic Tdarr flow can re-encode files that should have been left alone, damage HDR, keep the wrong audio track, strip required subtitles, or replace a source before Plex, Jellyfin, Radarr, or Sonarr can verify the result. The operational goal is controlled replacement, not maximum queue speed.
This build is a production-minded Tdarr pattern for a server plus GPU worker. It is designed around controlled automation: decide whether a file actually needs work, protect risky sources, standardize eligible output around a written codec policy, preserve required audio, subtitles, accessibility, language, chapters, and HDR data by default, validate the finished file before replacement, and notify Plex plus Radarr/Sonarr after a file changes.
The important part is not just "use the GPU." The important part is building guardrails around the GPU so the automation saves space without silently lowering library quality.
What This Setup Is Trying To Solve
- Convert only eligible, authorized media under a written codec, quality, and size policy.
- Do not waste GPU time on files that are already compliant.
- Recheck already-HEVC10 files by measured facts, not just codec name.
- Preserve all audio, subtitle, accessibility, language, chapter, and HDR streams by default.
- Route any proposed stream removal to explicit household policy and manual canary review.
- Route HDR, HDR10+, HLG, and Dolby Vision files to review instead of blindly flattening them.
- Validate output before replacing the original file.
- Let Plex or Jellyfin know a changed item should be refreshed.
- Let Radarr and Sonarr rescan so MediaInfo, file size, codec, and quality details stay current.
- Limit Tdarr worker counts during peak playback hours.
- Generate a daily review report so failed or risky jobs do not disappear into the queue.
Download status: blocked. No local bundle matching the published SHA256 or performed test artifact is available. The filename and checksum below identify the unverified bundle; they do not establish that it is safe. Do not download, import, or execute it. Release remains blocked pending checksum verification, file and secret review, compatible version pins, and successful encode, failure, quarantine, and restore tests. Retire the artifact if any check fails.
Preserved Tdarr automation bundle (blocked)
The baseline describes local Tdarr Flow plugins, helper scripts, config templates, and a cron example. Those contents, paths, behaviors, and credential claims are unverified. Do not download, import, copy, or execute the bundle from this article.
Canonical filename: tdarr-homelab-automation.zip
Published SHA256, not reproduced: 4ee338678cefd676886bfbffcf4d19d3ce4c23d6349e8b94732b45026cef94e3
The baseline article claims the bundle uses typed placeholders instead of live tokens, passwords, hostnames, private addresses, or API keys. That claim has not been verified against the ZIP bytes and is not repeated as a fact here. Treat any discovered credential or environment-specific value as a release blocker, rotate it, and retire the affected artifact.
Before You Start: Safe Defaults
- Use this workflow only for media you are allowed to store and transform.
- Keep original media protected until the flow has passed validation on real samples.
- Keep Tdarr cache on local fast storage and final media on the NAS or media volume.
- Back up Tdarr config, flow plugins, helper scripts, and Arr/Plex integration settings before production changes.
- Do not expose Tdarr, Radarr, Sonarr, SABnzbd, or Prowlarr directly to the public internet.
Tdarr Requirements Rechecked on 2026-08-24
| Primary source | Current finding | Implementation consequence |
|---|---|---|
| Tdarr Getting Started | Tdarr V2 separates Server and Node roles. The server does not encode; at least one node is required. Extra remote nodes are optional. | Back up server state and prove node connectivity separately from encoder capability. |
| Configuration Variables | Environment variables override JSON and defaults. serverURL is recommended for nodes; serverIP and serverPort are deprecated for node connection. Authentication defaults to false. | Use one documented configuration source, set auth=true, give nodes API keys, and avoid stale JSON overriding assumptions. |
| Docker Hardware Transcoding | Current Tdarr containers document NVENC and VA-API, plus QSV verification. NVIDIA needs the host container toolkit; --runtime=nvidia is added only when Docker lists that runtime. | Prove the encoder in a disposable node container before adding it to a library worker. |
| Transcode Cache | The cache is mandatory; files are transcoded there and then copied back to replace source media. Tdarr recommends a separate inexpensive SSD. | Cache failure and copy-back are data-risk boundaries. Monitor free space and never use the only media copy as the canary. |
| Changing Version | The documentation's stable selector showed 2.81.01 and published matching server/node Docker tags when checked. Docker changes version by changing the image tag. | Pin server and node to the same tested version or digest. Back up state and keep the prior images before upgrading or downgrading. |
| Authentication | Setting auth=true enables web login. Nodes then use API keys created in Tools -> API Keys; seeded keys must begin with tapi_ and meet the documented format. | Keep the UI private, store keys outside Compose, restrict file permissions, and rotate any value exposed in logs or screenshots. |
| HandBrake HDR | Current HandBrake documentation differentiates HDR10, HDR10+, and Dolby Vision support by encoder and records metadata limitations for some hardware encoders. | Do not infer HDR preservation from 10-bit output. Keep HDR/Dolby Vision in a separate tested flow. |
The stable release is a dated observation, not a permanent recommendation. Reopen the version, cache, authentication, hardware, configuration-variable, and HDR pages before deploying this example. No current custom plugin ID or downloadable-bundle compatibility is verified by these vendor pages.
High-Level Architecture
The reference pattern uses one Tdarr server and at least one Tdarr node. The server coordinates queue and policy while GPU nodes perform transcode work. Plex or Jellyfin, Radarr, Sonarr, and media storage remain separate systems whose state must be checked after a file is replaced.
- Tdarr Server: Owns libraries, flows, plugin definitions, file database, queue state, and the UI.
- Tdarr GPU Nodes: Run the workers. They need access to the same media paths and local cache paths used by the server.
- Local cache/transcode space: Temporary work should happen on local disk or fast local SSD, not over a slow network share.
- NAS/media storage: Stores final media. The NAS should not be hammered with temporary transcode writes.
- Plex: Serves the library and needs item or library refreshes after replacement.
- Radarr/Sonarr: Track movie/episode metadata and need rescans after Tdarr changes the underlying file.
- Cron/helper scripts: Adjust worker limits by time of day and produce review reports.
The design separates "decision work" from "heavy work." The flow decides what should happen first. Only then does the GPU spend time encoding. That keeps the system predictable and avoids the classic problem where a transcoder happily burns through thousands of files but leaves you with a mess to audit later.
The server owns queue and policy. GPU nodes do the heavy work against shared paths and local cache.
PolicyTdarr Server
Stores libraries, queues, flows, plugins, job state, and worker limits.
Worker APrimary GPU Node
Runs GPU transcodes with controlled worker count and local cache.
Worker BSecondary GPU Node
Adds capacity but should still respect Plex viewing windows.
SharedMedia Paths
Nodes must see the same media paths or have reliable path mappings.
NotifyPlex + Arr APIs
After replacement, refresh Plex and rescan Radarr/Sonarr so metadata stays accurate.
The Production Flow Order
The best Tdarr flow is not just a pile of plugins. The order matters. A safe production flow should look something like this:
- File settling check: Ignore files that were created or modified too recently. This avoids grabbing a file while an authorized ingest or library process is still writing it.
- Health check: Confirm the file can be probed and has video/audio metadata.
- HDR/DV guard: Detect HDR, HDR10+, HLG, BT.2020, or Dolby Vision risk and route those files to review.
- Codec and bit-depth decision: Decide whether the file is already compliant or needs video work.
- Size and quality policy: Evaluate measured facts against the written canary policy instead of codec name alone.
- Required-stream policy: Preserve audio, subtitle, accessibility, language, chapter, and disposition data by default; route any proposed removal to manual review.
- Video encode branch: If justified, encode with the selected tested hardware path.
- Container and stream mapping: Preserve every required stream and output to the expected container.
- Output validation: Confirm codec, duration, required streams, color/HDR facts, decode integrity, and size meet the written policy.
- Replace original: Only replace the original file after validation passes and the retention/rollback plan is active.
- Media-server refresh: Trigger Plex or Jellyfin so the changed item is rescanned.
- Radarr/Sonarr rescan: Trigger the matching Arr app so MediaInfo and quality tracking update.
- Review reporting: Leave any risky or failed items visible for daily review.
The intended order is to reject or review risky inputs before encoding, then validate output before replacement. The canary tests below must establish whether the actual flow enforces that order, including when validation fails.
The safest flow makes a decision before spending GPU time and validates output before touching the original.
1. SettleFile Age Check
Skip files that were created or modified too recently so partial writes are not processed.
2. ProbeMedia Health
Confirm the file has readable video/audio metadata before policy decisions.
3. GuardHDR / DV Review
Route risky HDR, HDR10+, HLG, or Dolby Vision sources to review instead of flattening them blindly.
4. DecideHEVC10 Size Policy
Skip files that are already good enough; re-encode HEVC10 only if oversized by policy.
5. CleanAudio + Subtitles
Keep useful English audio/subtitles, remove commentary and unwanted streams.
6. EncodeGPU Transcode
Encode eligible files to HEVC/H.265 10-bit with controlled worker limits.
7. ValidateReplace or Review
Check duration, streams, codec, bit depth, size ratio, and playback signals before replacement.
8. NotifyPlex + Arr Refresh
Refresh Plex and rescan Radarr/Sonarr only after a successful replacement.
Movie Flow vs TV Flow
Separate movie and television flows when their retention, stream, HDR, size, or queue policies differ. A 4K movie and a short television episode should not inherit the same threshold merely because both are video files. If the policies are identical, duplicate flows add drift without adding safety.
| Area | Movie Flow | TV Flow |
|---|---|---|
| Primary goal | Measured space savings without damaging premium sources | Consistent output and predictable playback across many episodes |
| HDR handling | Conservative; review every risky HDR/DV source | Equally conservative; SDR prevalence does not make an unreviewed rule safe |
| Audio/subtitles | Preserve all streams unless a documented canary policy explicitly requires a reviewed change | Preserve all streams unless a documented canary policy explicitly requires a reviewed change |
| Size policy | Evaluate large files against measured quality and playback requirements | Evaluate repeated episodes against the same quality and client requirements |
| Queue strategy | Lower parallelism simplifies review | More files make pacing and reporting important |
The logic can share plugins, but the thresholds and queue behavior do not have to be identical. A 4K movie and a 22-minute TV episode should not be treated like the same operational object.
HEVC/H.265 10-Bit Strategy
The preserved bundle targets HEVC/H.265 Main10. Treat that as an unverified local policy, not a universal quality or compatibility recommendation. A target format is acceptable only when representative Plex and Jellyfin clients Direct Play it, the encoder settings pass controlled quality review, required HDR metadata and streams survive, and the measured storage benefit justifies irreversible library churn.
Codec and bit depth alone do not decide whether a file meets your policy. Compare size, playback, required streams, and accepted quality before considering another encode. A large source file or an alternate audio track is not, by itself, a reason to discard information.
The baseline article describes a codexHevcSizePolicy plugin that checks resolution, duration, file size, and calculated bitrate, then marks oversized HEVC 10-bit files for re-encode. No local source verifies that implementation. Inspect the actual plugin, hash it, exercise every branch with copied fixtures, and compare its decisions with the written policy before import.
| Resolution | Preserved bundle threshold | Required interpretation |
|---|---|---|
| 480p | 2500 Kbps | Unverified decision threshold; not a quality target. |
| 720p | 4500 Kbps | Unverified decision threshold; validate by source class and client. |
| 1080p | 8500 Kbps | Unverified decision threshold; do not assume transparency. |
| 1440p | 14000 Kbps | Unverified decision threshold for an uncommon resolution. |
| 2160p | 28000 Kbps | Unverified decision threshold; HDR and grain require separate review. |
The baseline describes 10 percent headroom and a rule to ignore files smaller than 1 GB; the bundle itself remains unavailable for verification. Those values have not been validated for this revision. Record them as versioned policy inputs, test the false-positive and false-negative cases, and change them only from measured storage benefit and accepted quality.
HEVC10 decision example:
1080p movie
Actual bitrate: 14,500 Kbps
Target bitrate: 8,500 Kbps
Headroom: 10%
Allowed bitrate: 9,350 Kbps
Result: re-encode, because 14,500 Kbps is above the allowed target.
HDR and Dolby Vision Guardrails
HDR is a high-risk automated-transcoding branch. Feeding HDR10, HDR10+, HLG, BT.2020, or Dolby Vision into an SDR flow without proved tone mapping and metadata handling can produce washed-out colors, missing dynamic metadata, broken playback, or a technically valid file with incorrect output.
The baseline article describes a codexHdrGuard plugin that looks for signals such as PQ/SMPTE 2084 transfer, HLG transfer, BT.2020 color, Dolby Vision metadata, HDR10+ metadata, mastering display data, maximum content light level, and maximum frame-average light level. The source is unavailable here, so its detection behavior is unproved. The safe written policy is to route any detected or uncertain HDR item to review instead of the normal SDR HEVC flow.
- SDR file: Continue through the normal flow.
- HDR10 file: Route to review unless you have a dedicated HDR-preserving workflow.
- Dolby Vision file: Route to review. Do not casually strip or flatten DV metadata.
- Unknown HDR metadata: Route to review. Unknown is not the same as safe.
Long term, a separate HDR-preserving flow can be added. I would still keep it separate from the everyday SDR workflow. HDR deserves its own testing, preview samples, and acceptance criteria.
Audio and Subtitle Cleanup
Audio and subtitle removal is an accessibility, language, and retention decision before it is a storage decision. Inventory household requirements and preserve originals. Commentary, descriptive audio, sign-language, forced, and hearing-impaired tracks may be intentionally retained even when a simple language/title rule would remove them.
The baseline claims an unverified codexCleanEnglishStreams component would keep one English non-commentary audio stream, prefer more channels, keep English subtitles, and remove other language or commentary-style streams. The bundle has not been audited, so neither the component nor that behavior is established. Even if present, such a policy is unsafe for multilingual or accessibility needs without explicit review. Title matching can misclassify streams, and channel count does not establish quality or household preference.
| Stream type | Safe disposition before a proved policy exists |
|---|---|
| Main audio at any channel count | Preserve; channel count alone does not select the preferred mix. |
| Stereo or compatibility audio | Preserve until every required client and household use is documented. |
| Director commentary | Preserve by default; remove only under an explicit collection policy after canary review. |
| Audio description/descriptive audio | Preserve as accessibility content. |
| Alternate-language audio | Preserve until household language requirements are documented. |
| Standard subtitles | Preserve until client compatibility and household needs are documented. |
| Forced, hearing-impaired, or sign-language tracks | Preserve as required narrative or accessibility content. |
| Alternate-language subtitles | Preserve until household language requirements are documented. |
The preserved configuration describes E-AC-3 at 640 Kbps, six channels, 48 kHz for surround and AAC stereo at 192 Kbps, 48 kHz for stereo-only sources. These are unperformed starting settings, not a transparent-quality result. Validate channel mapping, loudness, sync, passthrough behavior, accessibility, and representative clients before replacement.
Output Validation Before Replacement
The most important safety rule is simple: never replace the original just because FFmpeg produced a file. A file can exist and still be wrong. It can be missing audio, missing video, too short, too small, the wrong codec, the wrong bit depth, or the result of accidentally running an HDR source through the wrong branch.
The baseline article says a codexOutputValidator plugin checks the following conditions. Treat this as a proposed acceptance contract, not verified implementation behavior, until source review and deliberately passing/failing fixtures prove every branch:
- At least one video stream.
- At least one audio stream.
- HEVC output when HEVC is required.
- 10-bit/Main10 output when 10-bit is required.
- Output duration within the expected ratio of the original.
- Output file size above a minimum sane ratio of the original.
- Original HDR/DV indicators that should not be auto-replaced by the SDR flow.
The baseline describes a duration window of 98 to 102 percent and a minimum size ratio of 8 percent. Neither value has been verified against the bundle or tested here. Treat them as proposed checks, not a quality guarantee or proof that missing streams, playback faults, or damaged HDR will be caught.
Plex and Arr Refreshes
After Tdarr replaces a file, the media server and the management apps need to know. Plex may keep stale stream and bitrate data until it rescans. Radarr and Sonarr may still think the old file exists with the old size, codec, quality profile, or MediaInfo values.
The baseline article describes local Plex and Arr refresh plugins: the Plex path maps changed paths to library sections, while the Arr path calls Radarr or Sonarr to rescan a matching item. Those files and API behaviors are not available for review here. Inspect authentication, URL construction, path scoping, timeouts, retries, idempotency, and error handling before allowing either integration to reach production.
{
"plexBaseUrl": "http://YOUR_PLEX_HOST:32400",
"plexToken": "YOUR_PLEX_TOKEN",
"timeoutMs": 30000,
"libraries": [
{
"title": "Movies",
"sectionId": "1",
"pathPrefix": "/mnt/media/Movies"
},
{
"title": "TV",
"sectionId": "2",
"pathPrefix": "/mnt/media/TV"
}
]
}
{
"instances": [
{
"name": "Radarr",
"type": "radarr",
"baseUrl": "http://YOUR_RADARR_HOST:7878",
"apiKey": "YOUR_RADARR_API_KEY",
"timeoutMs": 60000,
"pathPrefixes": ["/mnt/media/Movies"]
},
{
"name": "Sonarr",
"type": "sonarr",
"baseUrl": "http://YOUR_SONARR_HOST:8989",
"apiKey": "YOUR_SONARR_API_KEY",
"timeoutMs": 60000,
"pathPrefixes": ["/mnt/media/TV"]
}
]
}
This is also why Radarr and Sonarr quality strategy matters. If those apps are configured to keep upgrading until a preferred HEVC 10-bit release is found, you do not want Tdarr to create confusion. After Tdarr changes a file, Radarr/Sonarr should rescan and update MediaInfo so future upgrade decisions are based on the file that actually exists, not stale release-title assumptions.
Worker Scheduling and GPU Sharing
A GPU that is perfect for Tdarr can also be needed by Plex. If Tdarr consumes every available encode session during prime viewing time, users can feel it. The better pattern is to let Tdarr work harder off-peak and back down during peak Plex hours.
The baseline article describes a tdarr_worker_schedule.py helper that adjusts GPU worker limits by time of day and supports a disable-file. That script is not available locally, so neither API compatibility nor fail-safe behavior is verified. Review and hash the source, start with zero or one canary worker, test authentication and network failure, and prove the disable path before installing cron.
CRON_TZ=America/Chicago
TDARR_API_BASE=http://127.0.0.1:8266/api/v2
# /etc/tdarr-worker-schedule.env
TDARR_OFFPEAK_TARGETS='{
"YOUR_PRIMARY_TDARR_NODE_ID": 2,
"YOUR_SECONDARY_TDARR_NODE_ID": 1
}'
TDARR_PEAK_TARGETS='{
"YOUR_PRIMARY_TDARR_NODE_ID": 1,
"YOUR_SECONDARY_TDARR_NODE_ID": 0
}'
TDARR_PEAK_START=18:00
TDARR_PEAK_END=23:00
# /home/YOUR_USER/bin/run_tdarr_worker_schedule.sh
#!/usr/bin/env bash
source /etc/tdarr-worker-schedule.env
exec /usr/bin/python3 /home/YOUR_USER/bin/tdarr_worker_schedule.py \
>> /home/YOUR_USER/logs/tdarr_worker_schedule.log \
2>&1
# crontab
CRON_TZ=America/Chicago
*/5 * * * * /home/YOUR_USER/bin/run_tdarr_worker_schedule.sh
The two-node counts above are illustrative, not measured capacity limits. Start with one canary worker, then test representative Plex playback before increasing concurrency. Reduce or pause Tdarr if playback regresses; no worker count in this article establishes reserved capacity for Plex.
Cache and Storage Placement
Do not put heavy transcode cache work on a NAS share unless you have a very specific reason. Network shares are great for final media storage. They are usually not the best place for temporary encode output, partial files, or high-churn work directories.
- Good: Tdarr cache on local SSD/NVMe.
- Good: SABnzbd incomplete downloads on local disk, completed downloads to the final import path.
- Good: Final Plex media on NAS-backed storage.
- Risky: Tdarr temporary output over CIFS/NFS while multiple workers are active.
- Risky: Plex transcode temp and Tdarr cache fighting on the same slow disk.
The practical rule is simple: temporary work should be local and fast; final media can live on shared storage. That reduces network chatter, lowers the chance of partial-write problems, and makes GPU workers feel much smoother.
New File Settling
Tdarr should not immediately process a file that just appeared. Radarr, Sonarr, SABnzbd, or another process may still be moving, unpacking, renaming, importing, or setting permissions. A settling delay prevents Tdarr from grabbing a file while another app still owns it.
The preserved flow uses a 15-minute starting delay. A fixed delay does not prove a file is closed or stable. Prefer a supported settling check that observes unchanged size/mtime across intervals and coordinates with the importer; increase the interval when imports cross slow mounts or retain locks longer.
Daily Review Report
Automation still needs an operator view. The baseline article describes a tdarr_review_report.py script that reads Tdarr state and writes daily attention counts without clearing the queue or deleting files. Those non-destructive properties are unverified until the source and a restricted canary run confirm them. Grant read-only API/filesystem access where the implementation permits it.
# /home/YOUR_USER/bin/run_tdarr_review_report.sh
#!/usr/bin/env bash
exec /usr/bin/python3 /home/YOUR_USER/bin/tdarr_review_report.py \
>> /home/YOUR_USER/logs/tdarr_review_report.log \
2>&1
# crontab
15 8 * * * /home/YOUR_USER/bin/run_tdarr_review_report.sh
The daily report should surface HDR review items, output validation failures, required-audio/subtitle policy violations, cache pressure, and failed jobs without clearing or deleting them. Alert delivery must be tested; creating a report file alone does not prove anyone reviewed it.
Validation: Canary Before Production
Status: planned, not performed. Do not point a new flow at a production library on day one. Use copied, owned or open-licensed canary media and keep replacement disabled until every acceptance branch has a reviewed artifact.
- Create a separate canary library from copied media. Record checksums, stream facts, licenses, and expected retained audio/subtitle/HDR behavior.
- Pin matching server/node images and every flow/plugin revision. Back up Tdarr state, export the flow, and record the rollback point.
- Start the node paused with one GPU worker. Prove paths, free cache space, the selected hardware encoder, and UI authentication.
- Run one SDR encode without source replacement. Keep the Tdarr job report, encoder log, GPU/CPU evidence, output, before/after probes, decode check, and visual/playback review.
- Run already-compliant skip, oversized-policy, required audio/subtitle retention, HDR/Dolby Vision review, and failed-output branches separately.
- Force one safe validation failure. Prove the original checksum is unchanged, replacement did not occur, the failed output is quarantined, and the report/alert is visible.
- After an accepted canary replacement, verify Plex or Jellyfin rescans the exact item and displays current codec/size/stream facts. Then verify Radarr or Sonarr where it owns that file.
- Restart Tdarr server/node and then reboot the GPU host. Repeat path, encoder, authentication, and one canary check.
- Roll back the flow/image/config and restore the canary original. Re-probe and play the restored file before considering production.
The goal is to prove every branch: encode, skip, policy-selected re-encode, required-stream retention, HDR review, output validation failure, Plex or Jellyfin refresh, and Arr rescan. A happy-path encoder result is not a system test.
| Gate | Pass evidence | Failure action |
|---|---|---|
| Input protected | Original checksum, backup/restore point, replacement disabled for initial run | Stop; do not enqueue |
| Paths/cache | Server/node read same logical media path; node writes cache; free-space alert tested | Pause node and correct mappings/permissions |
| Hardware path | Job log names intended encoder; GPU engine and CPU state align for same UTC interval | Quarantine output; fix device/driver/plugin or use CPU intentionally |
| Streams/HDR | Before/after probes match the written retention and HDR policy | Keep original; route to manual review |
| Integrity/playback | Full decode check, duration/stream checks, visual review, representative Plex/Jellyfin playback | Keep original; preserve failure artifact |
| Application state | Plex or Jellyfin plus owning Arr app shows current file facts | Do not claim completion; fix rescan/refresh separately |
| Recovery | Prior image/config/flow and original canary restored after restart | Production remains blocked |
Evidence Status
No canary, failure, restart, refresh, or restore test was performed for this revision. The preceding validation checklist describes tests for your environment, not reported results. Keep version records, before-and-after file probes, encoder logs, playback checks, quarantine evidence, and a restore result before enabling replacement.
Install Options: Native Ubuntu or Docker
Tdarr can run directly on Ubuntu or in Docker. Docker is easier to rebuild because the server, node, config, cache, and media mappings are visible in one Compose file. Native Ubuntu is fine when you already manage services with systemd, but document the service user, paths, and plugin locations.
Docker Compose Pattern
This example is a same-host server-and-worker canary. Both containers map the same underlying media and cache at the same logical paths. A remote mapped node must also reach the same underlying media and cache through identical logical paths, or use reviewed path translators where supported; an independent remote cache at the same pathname is not shared state and is not a valid substitute.
name: tdarr-canary
services:
tdarr:
image: ghcr.io/haveagitgat/tdarr:2.81.01
container_name: tdarr
environment:
TZ: ${TZ:?set TZ}
PUID: ${PUID:?set PUID}
PGID: ${PGID:?set PGID}
UMASK: "002"
serverIP: 0.0.0.0
serverPort: "8266"
webUIPort: "8265"
auth: "true"
seededApiKey: ${TDARR_API_KEY:?set a protected tapi_ key}
inContainer: "true"
ffmpegVersion: "7"
volumes:
- /opt/media-stack/tdarr/server:/app/server
- /opt/media-stack/tdarr/configs:/app/configs
- /opt/media-stack/tdarr/logs:/app/logs
- /srv/tdarr-canary/media:/media
- /var/cache/tdarr:/temp
ports:
- "127.0.0.1:8265:8265"
restart: unless-stopped
logging:
options:
max-size: "10m"
max-file: "5"
tdarr-node:
image: ghcr.io/haveagitgat/tdarr_node:2.81.01
container_name: tdarr-node
environment:
TZ: ${TZ:?set TZ}
PUID: ${PUID:?set PUID}
PGID: ${PGID:?set PGID}
UMASK: "002"
nodeName: tdarr-node-gpu-canary
serverURL: http://tdarr:8266
apiKey: ${TDARR_API_KEY:?set the matching protected tapi_ key}
inContainer: "true"
ffmpegVersion: "7"
nodeType: mapped
NVIDIA_DRIVER_CAPABILITIES: compute,video,utility
NVIDIA_VISIBLE_DEVICES: ${NVIDIA_GPU_ID:?set one reviewed GPU ID or UUID}
startPaused: "true"
transcodegpuWorkers: "1"
transcodecpuWorkers: "0"
healthcheckgpuWorkers: "0"
healthcheckcpuWorkers: "1"
deploy:
resources:
reservations:
devices:
- driver: nvidia
device_ids: ["${NVIDIA_GPU_ID:?set one reviewed GPU ID or UUID}"]
capabilities: [gpu]
volumes:
- /opt/media-stack/tdarr/configs:/app/configs
- /opt/media-stack/tdarr/logs:/app/logs
- /srv/tdarr-canary/media:/media
- /var/cache/tdarr:/temp
depends_on:
- tdarr
restart: unless-stopped
logging:
options:
max-size: "10m"
max-file: "5"
Example configuration - not deployed by TechGeeks for this revision. The dated 2.81.01 tags match the stable version shown by Tdarr documentation on 2026-08-24; pin immutable digests after pulling and testing them. The server seeds the same protected tapi_ API key used by the mapped node. The node starts paused with one GPU worker and reserves one reviewed NVIDIA device instead of every GPU. For Intel/VA-API, remove NVIDIA environment and reservation settings, then map only the selected render device with the host's numeric render-group ID.
The UI is loopback-only and port 8266 stays on the Compose network. For a remote node, publish 8266 only on a dedicated management/LAN address, allow only the node source address in the host firewall, and protect the API key. Do not expose 8265 or 8266 to the public internet. Install and verify the NVIDIA Container Toolkit and a Docker Compose release that supports GPU reservations before adding an NVIDIA node.
Canary-only environment creation, not a bundle installer: run the following Bash block as the intended non-root canary owner in an existing private 0700 Compose directory beneath trusted parent directories. Obtain the intended GPU index or UUID from a separately reviewed nvidia-smi inventory. It refuses an existing or symlinked .env and validates key generation before creating the file with noclobber. On failure, do not launch containers or reuse an incomplete file without review. These guards were checked statically, not on a Tdarr deployment.
(
set -euo pipefail
set -C
umask 077
[[ -O . && "$(stat -c %a .)" == 700 ]]
: "${NVIDIA_GPU_ID:?export one reviewed GPU ID or UUID}"
[[ "$NVIDIA_GPU_ID" =~ ^[0-9]+$ || "$NVIDIA_GPU_ID" =~ ^GPU-[a-fA-F0-9-]+$ ]]
[[ ! -e .env && ! -L .env ]] || { printf 'Existing .env left unchanged.\n' >&2; exit 1; }
puid=$(id -u)
pgid=$(id -g)
[[ "$puid" != 0 && "$pgid" != 0 ]]
key=$(openssl rand -hex 24)
[[ "$key" =~ ^[a-f0-9]{48}$ ]]
printf 'TZ=Etc/UTC\nPUID=%s\nPGID=%s\nNVIDIA_GPU_ID=%s\nTDARR_API_KEY=tapi_%s\n' "$puid" "$pgid" "$NVIDIA_GPU_ID" "$key" > .env
)
Container Preflight and Version Record
Example commands - not performed by TechGeeks for this revision: run from the canary Compose directory. Save the resolved config privately because it can disclose paths and environment-derived values.
(
set -euo pipefail
date -u +'%Y-%m-%dT%H:%M:%SZ'
docker compose config --quiet
docker compose config --images
docker compose pull
docker image inspect --format '{{index .RepoDigests 0}}' \
ghcr.io/haveagitgat/tdarr:2.81.01
docker image inspect --format '{{index .RepoDigests 0}}' \
ghcr.io/haveagitgat/tdarr_node:2.81.01
docker compose up -d
docker compose ps
curl -fsS -o /dev/null http://127.0.0.1:8265/
docker exec tdarr-node ffmpeg -version
docker exec tdarr-node HandBrakeCLI --version
)
Illustrative expected state - not observed: both image references resolve to immutable digests; server and node containers are running; the loopback UI returns success; and FFmpeg plus HandBrake print version lines. Record the actual digests and full version output. A running container does not prove a connected node, usable encoder, path consistency, or safe replacement.
Prove Media and Cache Paths
(
set -euo pipefail
docker exec tdarr sh -lc \
'stat -c "%u:%g %a %n" /media /temp && test -r /media/input/sample.mkv'
docker exec tdarr-node sh -lc \
'stat -c "%u:%g %a %n" /media /temp && \
test -r /media/input/sample.mkv && \
test -w /temp && \
printf "paths-ok\n"'
)
Illustrative expected state - not observed: server and node report compatible ownership/modes for the same logical /media and /temp paths, and the node prints paths-ok. If it does not, stop. Do not compensate with chmod -R 777, privileged mode, or a whole-host filesystem mount.
Prove the Encoder With Synthetic Video
Select the documented test for the mapped accelerator and record its input and output handling before running it. An encoder-initialization test does not establish source decode, quality, HDR retention, subtitle handling, storage behavior or the safety of a Tdarr flow. Keep those acceptance checks separate.
Select one accelerator and use the official Tdarr hardware-transcoding tests for the reviewed image, driver and runtime. Map only the selected GPU/render device, not the whole /dev/dri tree. Run in a disposable canary with no writable production-media mount. Preserve the full encoder output and exit status; stop on initialization or encode failure. No accelerator command was run for this revision.
Illustrative expected state - not observed: the selected command exits 0 without an encoder-initialization error. Preserve stderr and the exit code. Exit 0 establishes only that this synthetic encode initialized and finished on that image/host combination.
Capture Before and After Facts
(
set -euo pipefail
umask 077
INPUT=/srv/tdarr-canary/media/input/sample.mkv
OUTPUT=/srv/tdarr-canary/media/output/sample.mkv
test -f "$INPUT"
test -f "$OUTPUT"
# Both must already be copied canary files, never production media.
RUN_DIR=$(mktemp -d "$HOME/tdarr-evidence.XXXXXXXX")
sha256sum -- "$INPUT" > "$RUN_DIR/input.sha256"
ffprobe -v error -show_format -show_streams -of json -- "$INPUT" > "$RUN_DIR/before.ffprobe.json"
sha256sum -- "$OUTPUT" > "$RUN_DIR/output.sha256"
ffprobe -v error -show_format -show_streams -of json -- "$OUTPUT" > "$RUN_DIR/after.ffprobe.json"
status=0
ffmpeg -v error -xerror -i "$OUTPUT" -map 0:v? -map 0:a? -f null - 2> "$RUN_DIR/decode-check.stderr" || status=$?
printf 'decode_check_exit=%s\n' "$status" > "$RUN_DIR/decode-check.exit"
printf 'Evidence directory: %s\n' "$RUN_DIR"
exit "$status"
)
Illustrative expected state - not observed: both checksums and both JSON probes exist; the output checksum differs from the input after a real encode; required video/audio/subtitle, duration, color/HDR, language, and disposition facts meet the written policy; and the full decode check records exit 0 with empty error output. These checks still do not prove transparent quality or every-client playback.
Native Ubuntu Pattern
# Inventory only - not performed. Do not recursively mutate the media tree.
namei -l /data/media /data/cache/tdarr
findmnt -T /data/media
findmnt -T /data/cache/tdarr
getfacl -p /data/media /data/cache/tdarr
df -h /data/media /data/cache/tdarr
# Follow the exact release's official native-install documentation.
# Create dedicated service identities and state/cache directories.
# Grant only reviewed path-specific access, then test as each identity.
# Record the release, package hashes, service definitions, paths, and rollback.
No native installation was validated for this revision. Do not apply recursive group or mode changes to an existing media tree as an installation shortcut; inventory ownership, mounts, ACLs, and dependent services first, then make the narrowest reviewed change.
Audit the Preserved Download Before Use
Download and installation remain blocked. The exact archive is unavailable for checksum reproduction or source review. The list below is a no-execute audit requirement, not a shell procedure: do not extract the archive on a media host, import its flows, or run any included program. Filename listings and a post-extraction symlink check are not sufficient validation.
A qualified reviewer must obtain the exact archive through a trusted channel and compare it with the recorded SHA256 4ee338678cefd676886bfbffcf4d19d3ce4c23d6349e8b94732b45026cef94e3. Before any extraction, use a maintained archive parser in a disposable, resource-limited, unprivileged environment with no host media, secrets or network access. Reject absolute/traversal paths, ambiguous or duplicate names, links, special files, unexpected types and unreasonable member counts or expanded sizes. Only a reviewed extraction method may write into a new isolated destination. Review every source file, dependency, permission, API action and secret finding; a hash match or clean text search is not approval to execute.
No archive audit result is available. Required evidence includes the exact archive hash, pre-extraction member/type checks, resource-limit results, per-file hashes and manual source findings. Keep source review separate from later canary execution. Neither a clean scan nor this checklist establishes safety, compatibility or permission to release the bundle.
Only after the artifact audit and release hold are resolved may a reviewed flow be considered for an isolated canary. Record the Tdarr release, server/node digests, exported flow JSON, flow ID, each local plugin path and SHA256, configuration hash and compatibility result. The baseline names codexHevcSizePolicy, codexHdrGuard, codexCleanEnglishStreams and codexOutputValidator, but no reviewed source or current flow ID is available locally. Do not invent those IDs or import the blocked bundle.
Bundle Installation Remains Blocked
No archive member path, plugin identifier, flow schema, helper-script behavior, executable permission, dependency, API call, or configuration template has been verified. Do not copy plugins, execute scripts, create config files from alleged templates, or import a flow from tdarr-homelab-automation.zip. Installation instructions can be added only after the checksum and full artifact audit above pass against the exact pinned Tdarr release in an isolated canary.
Rollback and Quarantine Plan
Before allowing automatic replacement, define where failed outputs and uncertain HDR results go, how they appear in the daily report, and how the original is restored. Test the quarantine and restore paths with copied media before enabling them on a library.
Backup preconditions, not an executable recipe: inventory every local and remote worker, active replacement, state path, secret store and original-media backup. Pause all workers and drain copy/replace work before stopping any writer. Record which services were running, then verify that every writer to the captured state has stopped. In a new private backup location, capture consistent state, the pinned Compose definition, image digests and flow/plugin hashes; keep secrets in the approved secret backup. On any failure, mark the backup incomplete and leave workers paused. Restart only the previously running, reviewed services after checking the backup result; do not automatically restart a worker from a failure trap.
Back up the protected .env or secret-manager entries through the site's approved secret process; do not put them in the evidence bundle. The backup is not accepted until sha256sum -c SHA256SUMS passes in a separate restore directory and the exported flow/plugin hashes are present.
Container-state rollback is unperformed. First pause all workers and confirm no item is in a copy/replace stage. Establish the isolated restore procedure below before any production change; these requirements do not authorize restoring the blocked download.
Isolated restore requirements: use only a backup whose origin, manifest and checksums have been verified. Inspect archive member paths, types, links and expanded sizes before extraction, using a reviewed parser and a fresh unprivileged disposable environment with no production mounts or secrets. Abort on any mismatch. Restore through the site-specific runbook into a new destination, never over the live state. Verify database compatibility, ownership, configuration, flow/plugin hashes and retained originals. Keep workers paused while repeating path, authentication, encoder and copied-sample checks. No restore was performed here.
If validation fails, keep the original checksum unchanged, move only the failed output to quarantine, retain the job report and log, and block Plex/Jellyfin/Arr refresh for that replacement. Do not teach the flow to delete originals because an encoder exited successfully. Media restore must come from a separately verified original/backup, never from the failed output.
Troubleshooting
| Symptom | Likely boundary | First proof | Safe recovery |
|---|---|---|---|
| Node never connects | serverURL, API key, DNS/network, auth, or version mismatch | Inspect node config and logs; confirm http://tdarr:8266 resolves inside the Compose network and the key is current. | Keep node paused; rotate an exposed key; restore matching pinned images/config. |
| Node connects but cannot find files | Server/node logical paths differ or cross-platform translation is wrong | Run the same stat and read check in both containers; inspect pathTranslators if used. | Pause queue and correct mappings. Do not broaden permissions. |
unknown or invalid runtime name: nvidia | NVIDIA runtime was configured where Docker does not list it | Inspect docker info and the NVIDIA Container Toolkit installation. | Remove the runtime setting, retain software/paused fallback, and fix the host toolkit first. |
| Encoder initialization fails | Driver, device mapping, unsupported codec, missing toolkit, or image mismatch | Run the synthetic encoder preflight and save complete stderr; compare host/container GPU identity. | Stop GPU workers and restore the prior driver/image/config. |
| CPU rises during a GPU job | Software decode/filter/subtitle/audio stage or fallback | Read the exact job/FFmpeg log and GPU decode/process/encode engines for the same UTC interval. | Quarantine output and lower workers; do not assume the GPU path is complete. |
| Cache fills | Too many workers, stalled job, undersized volume, or cleanup failure | df -h /var/cache/tdarr, queue/job state, open files, and worker count. | Pause new workers; preserve active-job evidence; remove only confirmed orphaned cache through the runbook. |
| Output passes codec check but is wrong | Weak policy, missing stream/HDR/duration/playback/quality checks | Compare full before/after probes, decode check, job report, and representative client playback. | Keep original, quarantine output, and add a failing acceptance rule before retrying. |
| Plex/Jellyfin/Arr still shows old facts | Refresh/rescan failed, path mapping differs, or replacement never completed | Verify filesystem checksum/path first, then application logs and item MediaInfo. | Do not rerun the encode; repair refresh/rescan independently. |
| Server/node fails after update | Unmatched images, schema/plugin change, driver/runtime change, or stale config | Compare image digests, release notes, flow/plugin hashes, and startup logs. | Restore the prior pinned pair and validated state with workers paused. |
Operational Runbook
- Before updates: Pause library processing, snapshot or back up Tdarr config, and make sure no jobs are mid-replace.
- Before enabling a new flow: Run a small manual test library.
- During production: Watch GPU utilization, Plex transcode sessions, Tdarr error states, and disk free space.
- Daily: Review the Tdarr report and manually inspect HDR/DV review items.
- Weekly: Check whether Radarr/Sonarr are still seeing upgraded files correctly.
- After driver or kernel changes: Verify NVIDIA, NVENC, Tdarr nodes, and supported encoder-session settings before restarting full processing.
Security, Privacy, Legal, and Recovery Boundaries
- Security: Keep Tdarr on a trusted management network or authenticated private access path. Enable Tdarr authentication, use a separate least-privilege node API key, restrict firewall sources, patch the host/runtime, and avoid privileged containers, Docker-socket mounts, broad device access, and whole-host filesystem mounts.
- Secrets: Store Plex/Jellyfin/Arr tokens and Tdarr keys outside article text, flow exports, screenshots, Compose source, and job reports. Restrict secret-file ownership/mode and rotate any value exposed during support or review.
- Privacy: Filenames, directory structure, metadata, queue history, logs, dashboard sessions, user names, addresses, and notifications can reveal household behavior. Minimize collection, define retention, keep raw artifacts private, and redact reviewed derivatives.
- Legal: Process only media the operator owns, created, or is authorized to transform and retain. Transcoding, automation, GPU access, or format conversion does not bypass copyright, license, DRM, workplace, or service terms.
- Accessibility and language: Do not delete descriptive audio, sign-language, forced, hearing-impaired, commentary, or alternate-language tracks solely from title or language tags. Record household requirements and preserve originals.
- Recovery: Back up Tdarr state, exported flows/plugins, application integration settings, and protected originals separately from cache. Keep the prior image/driver/config and test restoration with workers paused.
What Not To Automate Blindly
Some things should stay conservative unless you have tested them deeply:
- Automatic HDR to SDR tone mapping for the whole library.
- Automatic Dolby Vision conversion or metadata stripping.
- Deleting all alternate audio without checking household language needs.
- Replacing originals without duration and stream validation.
- Running every GPU worker at maximum while Plex users are active.
- Letting Tdarr process files that are still being imported.
What the Available Evidence Does Not Prove
The automation pack is a preserved canonical artifact identity, not a verified artifact or active link in this revision. It was not fetched, unpacked, secret-scanned, imported, or executed, and no local source matches its published checksum. The worker counts, codecs, bitrates, alleged stream-removal rules, plugin names, and flow order are unperformed starting policies that must be reviewed against authorized media, accessibility/language requirements, playback clients, and the exact GPU, driver, Tdarr, FFmpeg, and HandBrake versions.
Neither the ZIP, Compose example, successful encoder exit, nor Tdarr job completion proves hardware decode/filters/encode, visual quality, duration integrity, required stream retention, HDR or Dolby Vision correctness, client compatibility, Plex/Jellyfin/Arr refresh, cache headroom, concurrency, power, or recovery. Those claims require the saved job report, complete encoder log, GPU/CPU trace, before-and-after probes, decode check, quality method, playback matrix, failed-validation artifact, application state, and restore result for the exact sample.
Tdarr image tags, configuration variables, update behavior, hardware instructions, plugin schemas, and third-party integrations change. The 2026-08-24 documentation check showed stable 2.81.01, but that dated observation is not a future recommendation. Reopen official sources, pin matching server/node digests, and retain prior images plus a verified state backup until the canary, failure, restart, refresh, and restore gates pass again.
References
- Tdarr Getting Started
- Tdarr Configuration Variables and Path Translators
- Tdarr Docker Volume Mapping
- Tdarr Hardware Transcoding
- Tdarr Transcode Cache
- Tdarr Authentication and Node API Keys
- Tdarr Changing Version
- HandBrake HDR Encoding Documentation
- TRaSH Guides: Hardlinks, Atomic Moves, and Permissions
Final Thoughts
A production-minded homelab workflow surrounds any encode with file settling, HDR review, a measured codec/size policy, required-stream preservation, output validation, media-server refresh, Radarr/Sonarr rescan, scheduled worker limits, and daily visibility.
Only move beyond a copied canary library after every branch, failed validation, restart, application refresh, and restore has a reviewed artifact. Production trust comes from those gates, not from a successful encoder exit code.
Related TechGeeks resources
Use these next if you are sizing hardware or operating Tdarr against a real media library.



8 thoughts on “Building a Production-Grade Tdarr GPU Transcoding Stack for a Homelab”