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. The canonical filename and published SHA256 are retained below only as audit identifiers. This local revision did not fetch from or mutate WordPress, and no matching local bundle or performed test artifact exists. The public download is deliberately not linked. Publication and use remain blocked until an operator obtains the artifact through the approved workflow, reproduces SHA256 4ee338678cefd676886bfbffcf4d19d3ce4c23d6349e8b94732b45026cef94e3, inventories every file, checks for secrets and destructive behavior, pins compatible Tdarr/plugin versions, and runs the positive, failure, quarantine, and restore paths. Retire the artifact if any gate 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.

Download blocked pending artifact audit

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 sourceCurrent findingImplementation consequence
Tdarr Getting StartedTdarr 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 VariablesEnvironment 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 TranscodingCurrent 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 CacheThe 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 VersionThe 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.
AuthenticationSetting 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 HDRCurrent 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 immediately before publication or deployment. 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.

  1. Tdarr Server: Owns libraries, flows, plugin definitions, file database, queue state, and the UI.
  2. Tdarr GPU Nodes: Run the workers. They need access to the same media paths and local cache paths used by the server.
  3. Local cache/transcode space: Temporary work should happen on local disk or fast local SSD, not over a slow network share.
  4. NAS/media storage: Stores final media. The NAS should not be hammered with temporary transcode writes.
  5. Plex: Serves the library and needs item or library refreshes after replacement.
  6. Radarr/Sonarr: Track movie/episode metadata and need rescans after Tdarr changes the underlying file.
  7. 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.

Interactive Tdarr architecture
Two-Node Tdarr GPU Homelab Architecture

The server owns queue and policy. GPU nodes do the heavy work against shared paths and local cache.

  1. PolicyTdarr Server

    Stores libraries, queues, flows, plugins, job state, and worker limits.

  2. Worker APrimary GPU Node

    Runs GPU transcodes with controlled worker count and local cache.

  3. Worker BSecondary GPU Node

    Adds capacity but should still respect Plex viewing windows.

  4. SharedMedia Paths

    Nodes must see the same media paths or have reliable path mappings.

  5. NotifyPlex + Arr APIs

    After replacement, refresh Plex and rescan Radarr/Sonarr so metadata stays accurate.

Library-wide laterTest one file before enabling broad production library processing.
SecretsPlex tokens and Arr API keys belong in private config, not screenshots or downloadable examples.

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:

  1. 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.
  2. Health check: Confirm the file can be probed and has video/audio metadata.
  3. HDR/DV guard: Detect HDR, HDR10+, HLG, BT.2020, or Dolby Vision risk and route those files to review.
  4. Codec and bit-depth decision: Decide whether the file is already compliant or needs video work.
  5. Size and quality policy: Evaluate measured facts against the written canary policy instead of codec name alone.
  6. Required-stream policy: Preserve audio, subtitle, accessibility, language, chapter, and disposition data by default; route any proposed removal to manual review.
  7. Video encode branch: If justified, encode with the selected tested hardware path.
  8. Container and stream mapping: Preserve every required stream and output to the expected container.
  9. Output validation: Confirm codec, duration, required streams, color/HDR facts, decode integrity, and size meet the written policy.
  10. Replace original: Only replace the original file after validation passes and the retention/rollback plan is active.
  11. Media-server refresh: Trigger Plex or Jellyfin so the changed item is rescanned.
  12. Radarr/Sonarr rescan: Trigger the matching Arr app so MediaInfo and quality tracking update.
  13. Review reporting: Leave any risky or failed items visible for daily review.

That order is what makes the build feel reliable. The dangerous cases are filtered early, the expensive GPU work happens only when justified, and replacement happens only after the new output passes basic sanity checks.

Interactive replacement pipeline
Safe Tdarr Replacement Pipeline

The safest flow makes a decision before spending GPU time and validates output before touching the original.

  1. 1. SettleFile Age Check

    Skip files that were created or modified too recently so partial writes are not processed.

  2. 2. ProbeMedia Health

    Confirm the file has readable video/audio metadata before policy decisions.

  3. 3. GuardHDR / DV Review

    Route risky HDR, HDR10+, HLG, or Dolby Vision sources to review instead of flattening them blindly.

  4. 4. DecideHEVC10 Size Policy

    Skip files that are already good enough; re-encode HEVC10 only if oversized by policy.

  5. 5. CleanAudio + Subtitles

    Keep useful English audio/subtitles, remove commentary and unwanted streams.

  6. 6. EncodeGPU Transcode

    Encode eligible files to HEVC/H.265 10-bit with controlled worker limits.

  7. 7. ValidateReplace or Review

    Check duration, streams, codec, bit depth, size ratio, and playback signals before replacement.

  8. 8. NotifyPlex + Arr Refresh

    Refresh Plex and rescan Radarr/Sonarr only after a successful replacement.

Human review queueFailed, risky, or unexpected jobs should be visible for review, not silently deleted.
Rights gateUse these workflows only for media you own or are authorized to store, download, organize, or transcode.

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.

AreaMovie FlowTV Flow
Primary goalMeasured space savings without damaging premium sourcesConsistent output and predictable playback across many episodes
HDR handlingConservative; review every risky HDR/DV sourceEqually conservative; SDR prevalence does not make an unreviewed rule safe
Audio/subtitlesPreserve all streams unless a documented canary policy explicitly requires a reviewed changePreserve all streams unless a documented canary policy explicitly requires a reviewed change
Size policyEvaluate large files against measured quality and playback requirementsEvaluate repeated episodes against the same quality and client requirements
Queue strategyLower parallelism simplifies reviewMore 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.

The key mistake is assuming every HEVC 10-bit file should be skipped. Some already-HEVC files are bloated. They may be high-bitrate remuxes, weak encodes, or files with extra audio/subtitle baggage. That is why the size policy exists.

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.

ResolutionPreserved bundle thresholdRequired interpretation
480p2500 KbpsUnverified decision threshold; not a quality target.
720p4500 KbpsUnverified decision threshold; validate by source class and client.
1080p8500 KbpsUnverified decision threshold; do not assume transparency.
1440p14000 KbpsUnverified decision threshold for an uncommon resolution.
2160p28000 KbpsUnverified decision threshold; HDR and grain require separate review.

The preserved bundle uses 10 percent headroom and ignores files smaller than 1 GB. 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 typeSafe disposition before a proved policy exists
Main audio at any channel countPreserve; channel count alone does not select the preferred mix.
Stereo or compatibility audioPreserve until every required client and household use is documented.
Director commentaryPreserve by default; remove only under an explicit collection policy after canary review.
Audio description/descriptive audioPreserve as accessibility content.
Alternate-language audioPreserve until household language requirements are documented.
Standard subtitlesPreserve until client compatibility and household needs are documented.
Forced, hearing-impaired, or sign-language tracksPreserve as required narrative or accessibility content.
Alternate-language subtitlesPreserve 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 default duration window is 98 percent to 102 percent of the original duration. The default minimum size ratio is 8 percent. That is not a quality guarantee, but it catches many obvious failures before they become permanent replacements.

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 exact counts depend on your GPU, Plex load, and tolerance for queue speed. For a balanced two-node setup, a conservative starting point is two GPU workers on the stronger node and one on the secondary node off-peak, then one worker on the primary node and zero on the secondary node during peak viewing hours. That leaves capacity for Plex while still letting Tdarr make progress.

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.

  1. Create a separate canary library from copied media. Record checksums, stream facts, licenses, and expected retained audio/subtitle/HDR behavior.
  2. Pin matching server/node images and every flow/plugin revision. Back up Tdarr state, export the flow, and record the rollback point.
  3. Start the node paused with one GPU worker. Prove paths, free cache space, the selected hardware encoder, and UI authentication.
  4. 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.
  5. Run already-compliant skip, oversized-policy, required audio/subtitle retention, HDR/Dolby Vision review, and failed-output branches separately.
  6. 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.
  7. 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.
  8. Restart Tdarr server/node and then reboot the GPU host. Repeat path, encoder, authentication, and one canary check.
  9. 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.

GatePass evidenceFailure action
Input protectedOriginal checksum, backup/restore point, replacement disabled for initial runStop; do not enqueue
Paths/cacheServer/node read same logical media path; node writes cache; free-space alert testedPause node and correct mappings/permissions
Hardware pathJob log names intended encoder; GPU engine and CPU state align for same UTC intervalQuarantine output; fix device/driver/plugin or use CPU intentionally
Streams/HDRBefore/after probes match the written retention and HDR policyKeep original; route to manual review
Integrity/playbackFull decode check, duration/stream checks, visual review, representative Plex/Jellyfin playbackKeep original; preserve failure artifact
Application statePlex or Jellyfin plus owning Arr app shows current file factsDo not claim completion; fix rescan/refresh separately
RecoveryPrior image/config/flow and original canary restored after restartProduction remains blocked

Planned Evidence Checklist

A future run belongs under artifacts/labs/production-grade-tdarr-gpu-transcoding-homelab/YYYY-MM-DD/. No item below may be marked performed without a reviewed immutable artifact.

  • Environment record: run ID, UTC times, operator, topology, CPU/GPU/RAM/storage/network, OS/kernel, firmware/driver, Docker/Compose, image digests, Tdarr, FFmpeg, HandBrake, flow IDs, plugin hashes, and config hashes.
  • Authorized canary manifest: source license, synthetic title, checksum, container, codecs, bit depth, color/HDR, duration, bitrate, audio, subtitles, chapters, and expected policy.
  • Positive path: before probe, exact job/encoder log, GPU/CPU trace, cache peak, output checksum/size/duration, after probe, decode check, visual/quality method, client playback, and application refresh.
  • Negative path: forced validation failure, unchanged original, quarantined output, visible job/report/alert, and no Plex/Jellyfin/Arr replacement state.
  • Persistence: server/node restart and GPU-host reboot with path, auth, node, encoder, and canary proof.
  • Recovery: prior image/config/flow restored, original canary restored, checksum/probe/playback verified, and no unexplained database or queue drift.
  • Screenshots: library/flow/node versions, successful item, quarantined item, probes, and GPU activity at fixed viewports with all identities, paths, addresses, tokens, titles, history, and notifications redacted.
  • Measurements: source/output bytes, duration, elapsed time, frames per second, cache peak, CPU/GPU use, power method, and quality-method limitation. Do not generalize one sample to the whole library.

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.

Example protected environment creation - not performed: run once from an empty canary Compose directory after recording one intended GPU ID or UUID from nvidia-smi. The guard refuses to overwrite an existing secret file.

nvidia-smi --query-gpu=index,uuid,name --format=csv
: "${NVIDIA_GPU_ID:?export one reviewed GPU ID or UUID}"
umask 077
test ! -e .env || {
  printf 'refusing to overwrite existing .env\n' >&2
  exit 1
}
{
  printf 'TZ=Etc/UTC\n'
  printf 'PUID=%s\n' "$(id -u)"
  printf 'PGID=%s\n' "$(id -g)"
  printf 'NVIDIA_GPU_ID=%s\n' "$NVIDIA_GPU_ID"
  printf 'TDARR_API_KEY=tapi_%s\n' "$(openssl rand -hex 24)"
} > .env
chmod 0600 .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.

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 | head -n 1
docker exec tdarr-node HandBrakeCLI --version | head -n 1

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

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

Run only the command for the mapped accelerator. These commands generate two seconds of synthetic color video inside a disposable container and discard output. They do not test image quality, source decode, HDR, subtitles, storage, or Tdarr flow behavior.

# NVIDIA NVENC example - not performed
: "${NVIDIA_GPU_ID:?set one reviewed GPU ID or UUID}"
docker run --rm --gpus "device=$NVIDIA_GPU_ID" \
  -e NVIDIA_DRIVER_CAPABILITIES=compute,video,utility \
  -e NVIDIA_VISIBLE_DEVICES="$NVIDIA_GPU_ID" \
  ghcr.io/haveagitgat/tdarr_node:2.81.01 \
  ffmpeg -hide_banner -v warning \
  -f lavfi -i 'color=c=black:s=256x256:d=2:r=30' \
  -c:v hevc_nvenc -f null -

# Intel QSV example - not performed
docker run --rm --device=/dev/dri:/dev/dri \
  ghcr.io/haveagitgat/tdarr_node:2.81.01 \
  ffmpeg -hide_banner -v warning \
  -hwaccel qsv -f lavfi -i 'color=c=black:s=256x256:d=2:r=30' \
  -c:v hevc_qsv -f null -

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

RUN_DIR="$HOME/tdarr-evidence/$(date -u +%Y%m%dT%H%M%SZ)"
INPUT=/srv/tdarr-canary/media/input/sample.mkv
OUTPUT=/srv/tdarr-canary/media/output/sample.mkv
mkdir -p "$RUN_DIR"

sha256sum -- "$INPUT" > "$RUN_DIR/input.sha256"
ffprobe -v error -show_format -show_streams -of json -- "$INPUT" \
  > "$RUN_DIR/before.ffprobe.json"

# Run the pinned canary flow here. Do not replace INPUT.

sha256sum -- "$OUTPUT" > "$RUN_DIR/output.sha256"
ffprobe -v error -show_format -show_streams -of json -- "$OUTPUT" \
  > "$RUN_DIR/after.ffprobe.json"
ffmpeg -v error -xerror -i "$OUTPUT" \
  -map 0:v? -map 0:a? -f null - \
  2> "$RUN_DIR/decode-check.stderr"
printf 'decode_check_exit=%s\n' "$?" \
  > "$RUN_DIR/decode-check.exit"

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

Planned review commands - not performed for this revision: run them in an isolated review directory after obtaining the ZIP through the approved publishing workflow. Do not execute scripts directly from the archive.

ZIP=tdarr-homelab-automation.zip
EXPECTED=4ee338678cefd676886bfbffcf4d19d3ce4c23d6349e8b94732b45026cef94e3
for tool in sha256sum zipinfo bsdtar rg awk tee find grep; do
  command -v "$tool" >/dev/null || {
    printf 'missing required audit tool: %s\n' "$tool" >&2
    exit 1
  }
done
ACTUAL=$(sha256sum -- "$ZIP" | awk '{print $1}')
test "$ACTUAL" = "$EXPECTED" || {
  printf 'checksum mismatch: expected=%s actual=%s\n' "$EXPECTED" "$ACTUAL" >&2
  exit 1
}

zipinfo -1 "$ZIP" | tee bundle-members.txt
if rg -n '(^/|^[A-Za-z]:[\\/]|(^|/)\.\.(/|$)|\\)' bundle-members.txt; then
  printf 'unsafe archive member path; refusing extraction\n' >&2
  exit 1
fi
mkdir -m 700 bundle-review
bsdtar --no-same-owner --no-same-permissions -xf "$ZIP" -C bundle-review
if find bundle-review -type l -print -quit | grep -q .; then
  printf 'symbolic link found; refusing bundle\n' >&2
  exit 1
fi
find bundle-review -type f -print0 | sort -z | xargs -0 sha256sum
rg -n --hidden -i \
  '(api[_-]?key|token|password|secret|authorization|192\.168\.|10\.[0-9]|172\.(1[6-9]|2[0-9]|3[01])\.)' \
  bundle-review

Illustrative expected state - not observed: the checksum comparison exits 0; archive paths are relative and expected; the file inventory and per-file hashes are saved; and every secret/network-pattern match is either a typed placeholder or a reviewed finding. A clean text scan does not prove the archive is safe. Review source, executable permissions, dependencies, API calls, path assumptions, plugin schema, and destructive operations manually.

Import flows only into the 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 article names codexHevcSizePolicy, codexHdrGuard, codexCleanEnglishStreams, and codexOutputValidator, but no reviewed source or current flow ID is available locally. Do not invent or publish those IDs.

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

A world-class transcoding flow includes a way out. Before you allow automatic replacement, decide where failed outputs, risky HDR items, and questionable results go. A simple quarantine folder plus daily report is enough for many homelabs.

# Example pre-change backup - not performed
RUN_ID=$(date -u +%Y%m%dT%H%M%SZ)
BACKUP_DIR="$HOME/tdarr-backups/$RUN_ID"
COMPOSE_FILE=compose.yaml
test -f "$COMPOSE_FILE" || {
  printf 'run from the canary Compose directory; missing %s\n' "$COMPOSE_FILE" >&2
  exit 1
}
install -d -m 700 "$BACKUP_DIR"

docker compose stop tdarr-node
docker compose stop tdarr
cp --preserve=timestamps "$COMPOSE_FILE" "$BACKUP_DIR/compose.yaml"
tar --acls --xattrs -C /opt/media-stack/tdarr \
  -czf "$BACKUP_DIR/tdarr-state.tgz" server configs
(
  cd "$BACKUP_DIR"
  sha256sum compose.yaml tdarr-state.tgz > SHA256SUMS
)
docker image inspect --format '{{index .RepoDigests 0}}' \
  ghcr.io/haveagitgat/tdarr:2.81.01 \
  > "$BACKUP_DIR/server-image.txt"
docker image inspect --format '{{index .RepoDigests 0}}' \
  ghcr.io/haveagitgat/tdarr_node:2.81.01 \
  > "$BACKUP_DIR/node-image.txt"
docker compose start tdarr
docker compose start tdarr-node

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.

Example container-state rollback - not performed: first pause all workers and confirm no item is in a copy/replace stage. Restore into a disposable path before replacing production state.

BACKUP_DIR="$HOME/tdarr-backups/<RUN_ID>"
RESTORE_DIR="$HOME/tdarr-restore-check"
test ! -e "$RESTORE_DIR" || { echo "restore directory already exists" >&2; exit 1; }
install -d -m 700 "$RESTORE_DIR"
cd "$BACKUP_DIR"
sha256sum -c SHA256SUMS
tar -tzf tdarr-state.tgz
tar --acls --xattrs -xzf tdarr-state.tgz -C "$RESTORE_DIR"

# After reviewing RESTORE_DIR, stop the canary and restore the prior
# pinned Compose file and validated state through the operator runbook.
# Start with all workers paused, then repeat path/auth/encoder/canary checks.

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

SymptomLikely boundaryFirst proofSafe recovery
Node never connectsserverURL, API key, DNS/network, auth, or version mismatchInspect 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 filesServer/node logical paths differ or cross-platform translation is wrongRun 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: nvidiaNVIDIA runtime was configured where Docker does not list itInspect docker info and the NVIDIA Container Toolkit installation.Remove the runtime setting, retain software/paused fallback, and fix the host toolkit first.
Encoder initialization failsDriver, device mapping, unsupported codec, missing toolkit, or image mismatchRun 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 jobSoftware decode/filter/subtitle/audio stage or fallbackRead 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 fillsToo many workers, stalled job, undersized volume, or cleanup failuredf -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 wrongWeak policy, missing stream/HDR/duration/playback/quality checksCompare 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 factsRefresh/rescan failed, path mapping differs, or replacement never completedVerify filesystem checksum/path first, then application logs and item MediaInfo.Do not rerun the encode; repair refresh/rescan independently.
Server/node fails after updateUnmatched images, schema/plugin change, driver/runtime change, or stale configCompare 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

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.

Need help applying this?

Bring TechGeeks into the real environment.

If you are working through this on a live network, WordPress site, Linux server, AI workflow, or PisoWiFi deployment, send the context and we can help turn it into a practical plan.

Request helpGet field notesRecommended gear

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

Leave a Reply

Your email address will not be published. Required fields are marked *