Tdarr is a distributed transcoding system for your media library. Point it at your films and TV and it can convert everything to one codec (H.265 saves a lot of disk), remove unwanted audio and subtitle tracks, and health-check files for corruption, all automatically and in the background. The free tier covers everything a homelab needs.
In this guide we will run the Tdarr server, with its built-in node, in Docker using the official image, optionally pass through an Intel or AMD GPU for hardware transcoding, set up a first library, and get it ready to sit behind a reverse proxy.
ghcr.io/haveagitgat/tdarr.✅ Before you start
- A Linux host with Docker and Docker Compose v2 installed (
docker compose versionshould work). - Your media on a local disk or mounted share, e.g.
/path/to/data/media/moviesand/path/to/data/media/tv. - A fast disk with plenty of free space for the transcode cache (an SSD is ideal); at least the size of your largest file.
- Optional but recommended: an Intel or AMD GPU for VAAPI/QSV hardware transcoding, or an NVIDIA GPU with the NVIDIA Container Toolkit installed.
- Important: a backup of anything you cannot replace. Tdarr replaces the original files once a transcode passes.
STEP 1: Find your user and group IDs
Tdarr should run as a normal user, not as root. Log in to your Docker host over SSH and run:
idYou will see something like uid=1000(you) gid=1000(you). Make a note of the two numbers; they become PUID and PGID below.
If you want hardware transcoding, also check the GPU is visible on the host: ls -l /dev/dri should show renderD128.
STEP 2: Create the folders
I like to keep every app's data under one parent folder so backups are a single job. Create the folders for Tdarr, with the transcode cache on your fastest disk:
mkdir -p /path/to/appdata/tdarr/{server,configs,logs}
mkdir -p /path/to/transcode_cacheWhen you are done, the layout should look like this:
/path/to/appdata/tdarr/
docker-compose.yml
server/ # database, samples and plugins
configs/ # Tdarr_Server_Config.json, Tdarr_Node_Config.json
logs/
/path/to/transcode_cache/ # temporary files while transcoding (fast disk)
/path/to/data/media/
movies/
tv/Folder layout
/data/media, the hardlink to the copy still seeding in /data/torrents is broken and both copies use space until the torrent is removed. Many people only transcode once seeding has finished.Finally, this guide puts the app on the shared media Docker network used by the rest of my media stack, so other containers can reach it by name. If you followed the Jellyfin guide you already have it; otherwise create it once:
docker network create mediaSTEP 3: The compose file
Create /path/to/appdata/tdarr/docker-compose.yml and paste in the following. Use the copy button in the top right corner of the block.
services:
tdarr:
image: ghcr.io/haveagitgat/tdarr:latest
container_name: tdarr
hostname: tdarr
restart: unless-stopped
environment:
- PUID=1000 # your user ID from STEP 1
- PGID=1000 # your group ID from STEP 1
- TZ=Europe/London # your time zone
- UMASK_SET=002
- serverIP=0.0.0.0
- serverPort=8266
- webUIPort=8265
- internalNode=true # run a node inside the server container
- inContainer=true
- ffmpegVersion=7
- nodeName=MyInternalNode
- auth=false # see STEP 7 before exposing Tdarr
volumes:
- /path/to/appdata/tdarr/server:/app/server
- /path/to/appdata/tdarr/configs:/app/configs
- /path/to/appdata/tdarr/logs:/app/logs
- /path/to/data/media:/media
- /path/to/transcode_cache:/temp
devices:
- /dev/dri:/dev/dri # Intel/AMD GPU for hardware transcoding
ports:
- 8265:8265 # web UI
- 8266:8266 # server port (for extra nodes)
networks:
- media
networks:
media:
external: truedocker-compose.yml
Delete the
devices: section and Tdarr will transcode on the CPU. NVIDIA? Install the NVIDIA Container Toolkit on the host, add NVIDIA_DRIVER_CAPABILITIES=all and NVIDIA_VISIBLE_DEVICES=all to environment, and add a GPU reservation under deploy: resources: reservations: devices: with driver: nvidia, count: all and capabilities: [gpu], as in the official compose example.STEP 4: The settings explained
| Setting | What it does |
|---|---|
PUID / PGID |
The user and group Tdarr runs as. It must be able to read and write your media, because it replaces files. |
TZ |
Time zone, for schedules and logs. List of zones. |
UMASK_SET |
002 keeps new files group-writable, as in the official example. |
serverIP / serverPort / webUIPort |
Where the server listens inside the container. 8265 is the web UI and 8266 is what extra nodes connect to. |
internalNode / nodeName |
Runs a worker (a "node") inside the server container, so one container does everything. You can add more nodes on other machines later with the tdarr_node image. |
ffmpegVersion |
Which bundled FFmpeg major version the node uses. |
auth |
Turns the login on or off. Off is fine on a trusted LAN; switch it on before exposing Tdarr (see STEP 7). |
/app/server |
Tdarr's database, samples and plugins. Back this up along with /app/configs. |
/media |
Your library. Tdarr reads and rewrites files here. |
/temp |
The transcode cache. Work-in-progress files live here until they pass and replace the originals. |
devices |
Hands the GPU to the container for VAAPI/QSV hardware transcoding. |
STEP 5: Start Tdarr
From the folder containing your compose file:
cd /path/to/appdata/tdarr
docker compose up -d
docker compose logs -fWait until the logs settle down (press Ctrl+C to stop following them; the container keeps running), then check it is up:
docker ps --filter name=tdarrTo check Docker can use your Intel GPU, run the quick QSV test from the Tdarr docs. It starts a throwaway tdarr_node container and removes it again afterwards:
docker run --rm --device=/dev/dri:/dev/dri ghcr.io/haveagitgat/tdarr_node:latest \
/bin/bash -e -c 'ffmpeg -hwaccel qsv -f lavfi -i color=c=black:s=256x256:d=1:r=30 -c:v:0 hevc_qsv -f null /dev/null'If it finishes without errors, hardware encoding is available. (On AMD, the docs have a matching VAAPI test.)
STEP 6: First-run setup
Open http://192.168.1.20:8265 in your browser, replacing the IP with your Docker host's address. The dashboard should show MyInternalNode as connected.
- Add a library. Go to the Libraries tab and add one. Set the Source to
/media/moviesand the Transcode cache to/temp. - Pick what should happen to each file. Under Transcode Options, choose a Flow (the visual editor) or a classic plugin stack. A good first goal is "re-encode to HEVC and drop unneeded subtitle tracks". Start with a small test folder rather than your whole library.
- Set the workers. On the dashboard, open your node and set how many GPU and CPU transcode and health check workers it may run at once. One GPU transcode worker is a sensible start.
- Scan. Back on the library, run a Scan (Find new). Files appear in the queue and start processing.
- Repeat for
/media/tvonce you are happy with the results.
Watch a few transcoded files from start to finish before letting Tdarr loose on everything. Tdarr's own health checks catch broken files, but your eyes and ears are the real test.
STEP 7: Put Tdarr behind a reverse proxy
Tdarr is an admin tool, so I keep it on the LAN or behind a VPN. If you do want it on a domain, first turn on the login: set auth=true and restart, and Tdarr will ask you to create a username and password in the web UI. Any separate nodes then need an API key (created under Tools → API Keys) in their apiKey variable. Never forward ports 8265 or 8266 from your router.
Option A: Traefik (Docker labels)
If Traefik runs on the same Docker host and is attached to the media network, add these labels to the tdarr service. They assume your HTTPS entry point is called websecure and your certificate resolver is called cloudflare; change them to match your Traefik config.
labels:
- traefik.enable=true
- traefik.http.routers.tdarr.rule=Host(`tdarr.example.com`)
- traefik.http.routers.tdarr.entrypoints=websecure
- traefik.http.routers.tdarr.tls.certresolver=cloudflare
- traefik.http.services.tdarr.loadbalancer.server.port=8265docker-compose.yml (extra lines)
Option B: Traefik (file provider)
If Traefik runs on a different machine, add a router and service to your dynamic config file instead:
http:
routers:
tdarr:
rule: Host(`tdarr.example.com`)
entryPoints:
- websecure
tls:
certResolver: cloudflare
service: tdarr
services:
tdarr:
loadBalancer:
servers:
- url: http://192.168.1.20:8265dynamic.yml
Option C: Nginx Proxy Manager
Add a Proxy Host for tdarr.example.com with scheme http, forward hostname tdarr (if NPM is on the media network) or 192.168.1.20, and forward port 8265. Request a certificate on the SSL tab and tick Force SSL. Turn Websockets Support on.
Only the web UI (8265) goes through the proxy. Extra nodes on your LAN connect straight to port 8266.
Updating Tdarr
The latest tag always follows the newest stable release. To update:
cd /path/to/appdata/tdarr
docker compose pull
docker compose up -d
docker image prune -f/path/to/appdata/tdarr/server and configs, and read the release notes. Update any separate nodes to the same version as the server.Troubleshooting
Files fail with Permission denied. The PUID/PGID user cannot write to your media. Check with docker exec -it tdarr ls -ln /media/movies and fix ownership on the host.
Hardware transcodes fail but CPU ones work. Make sure /dev/dri exists on the host and is passed through, then run the QSV test from STEP 5. On Proxmox, the iGPU must be passed through to the VM first.
The transcode cache fills the disk. Point /temp at a disk with more space, and lower the number of workers so fewer files are in flight at once.
The node does not appear on the dashboard. Check internalNode=true and look at docker logs tdarr --tail 50. With auth=true, nodes need a valid apiKey.
port is already allocated when starting. Something else is using 8265 or 8266. Change the left-hand side of the port mapping, for example 8275:8265.
That's it! Your library will slowly shrink and tidy itself while you sleep. Keep an eye on that first batch, then let it get on with it. 🎞️