Complete Plex, Sonarr, Radarr, Prowlarr, SABnzbd, and Tdarr Setup for Beginners
This is the missing Day 0 setup guide for the Plex, Arr, and Tdarr homelab series. The deeper articles explain architecture, storage, Prowlarr, Sonarr, Radarr, SABnzbd, Tdarr, GPU sharing, reverse proxying, backups, and monitoring. This one answers the first practical question: what do I install, in what order, what paths do I use, and how do I know it worked?
The goal is a clean beginner-safe stack on Ubuntu using Docker Compose, a shared /data layout, predictable permissions, private admin access, Plex playback, Sonarr and Radarr imports, SABnzbd categories, and Tdarr only after the basic workflow is proven.
Rights, lawful use, and scope: This article is for administering a private media server, organizing media you own or are authorized to use, preserving backups, and testing your own infrastructure. It is not legal advice and it is not a guide to finding, acquiring, sharing, or bypassing access controls for copyrighted works. Do not use Plex, Sonarr, Radarr, Prowlarr, SABnzbd, Tdarr, lists, indexers, or download clients to download, store, process, or distribute content unless you have the legal right and service permission to do so.
Safe default: Build this LAN-only or VPN-only first. Do not port-forward Sonarr, Radarr, Prowlarr, SABnzbd, Tdarr, NAS admin pages, or reverse-proxy admin pages. These are control-plane apps that can reveal secrets, change libraries, trigger jobs, delete files, or expose host paths.
What You Are Building
Prowlarr finds and tests lawful sources. Sonarr and Radarr decide what belongs in the TV and movie libraries. SABnzbd handles authorized download-client jobs and reports completion. Sonarr and Radarr import completed items into final library folders. Tdarr optimizes final imported files after they are stable. Plex serves the final media. Backups make the stack rebuildable.
Click each step to see what must be true before moving on. The goal is one clean authorized import before broad automation.
1. FoundationUbuntu + Docker
Patch the host, install Docker or native packages, and confirm the server can reboot cleanly.
2. Paths/data Layout
Create shared download, media, and cache paths before apps start creating files with mismatched ownership.
3. SourcesProwlarr
Add only lawful sources and sync indexers outward. Prowlarr finds candidates; it does not decide what belongs in the library.
4. DownloadsSABnzbd
Use category folders for movies and TV. Keep incomplete work away from final Plex library roots.
5. DecisionsSonarr / Radarr
Set roots, profiles, categories, and one controlled test item before enabling lists or broad searching.
6. PlaybackPlex
Point Plex at final media only, then confirm it can see the imported test item without permission fixes.
7. Optimize LaterTdarr
Add Tdarr only after imports work. Start with one test file, validate output, then scale carefully.
What We Are Not Building Yet
A complete beginner setup should not start with every advanced feature enabled. The fastest way to make this stack confusing is to enable lists, custom formats, reverse proxy access, GPU transcoding, and Tdarr replacement before a single controlled import works.
| Not Yet | Why It Waits | When to Add It |
|---|---|---|
| Radarr import lists | Lists can add many items quickly. | After one manual Radarr import succeeds and you understand tags, root folders, and search-on-add. |
| Complex custom formats | Scoring rules can reject everything or upgrade forever if misunderstood. | After basic Sonarr/Radarr searches and imports work. |
| Reverse proxy hostnames | A proxy can expose admin apps before authentication and access lists are correct. | After LAN-only access, app authentication, and backups are proven. |
| Library-wide Tdarr processing | Tdarr replaces files; bad settings can affect a whole library. | After one test file validates cleanly and Plex/Arr refresh works. |
| GPU tuning | Driver and container GPU issues add another troubleshooting layer. | After the stack works without GPU-specific assumptions. |
| Automatic updates | An update can migrate databases or change behavior. | After backups and rollback notes exist. |
| App | Plain-English Job | Beginner Rule |
|---|---|---|
| Plex | Playback and library presentation. | Point Plex only at final media folders, not download or cache folders. |
| Prowlarr | Indexer management and testing. | Add one source first, test it, then sync it to Sonarr/Radarr. |
| SABnzbd | Download, repair, unpack, and completion reporting. | Use categories so Sonarr and Radarr know which completed job belongs to which app. |
| Sonarr | TV intent, monitoring, imports, and upgrades. | Use final TV root folders only. Downloads are not root folders. |
| Radarr | Movie intent, root folders, lists, imports, and upgrades. | Use final movie root folders only and add lists only after manual tests pass. |
| Tdarr | Post-import file optimization. | Install it last and start with one test file, not the whole library. |
Before You Touch the Terminal
You need a few decisions and pieces of information before commands are useful. Write them down in a private note. Do not paste real passwords, API keys, Plex tokens, NAS credentials, or provider credentials into public posts, screenshots, GitHub issues, or shared config examples.
| Decision | Recommended Default | Why |
|---|---|---|
| Install style | Docker Compose on Ubuntu Server LTS. | One repeatable stack file, persistent config folders, and easier rebuilds. |
| Access model | LAN-only or VPN-only admin access. | Safer than exposing media admin apps directly to the internet. |
| Path layout | Use one shared /data path. | Prevents remote path mapping confusion and keeps imports predictable. |
| Final media | /data/media/movies and /data/media/tv. | These are the folders Plex, Sonarr, Radarr, and Tdarr agree on. |
| Temporary work | Local SSD/NVMe for incomplete downloads and transcode cache. | Faster and safer for write-heavy work than a NAS share. |
| Tdarr timing | After manual Plex, SABnzbd, Sonarr, and Radarr tests pass. | Tdarr mutates files; it should not be first-boot automation. |
| Reverse proxy | Later, after local workflow works. | A proxy is not a fix for broken app auth, bad paths, or missing backups. |
| Backups | Before broad automation. | Config folders are small and rebuild-critical. |
- Ubuntu Server LTS installed, preferably wired Ethernet.
- A router DHCP reservation or static IP plan for the server.
- An admin workstation with SSH access to the server.
- A NAS share or local disk plan for final media.
- A local disk or SSD/NVMe path for incomplete downloads and transcode cache.
- A Plex account, and Plex Pass only if you plan to use Plex hardware transcoding features.
- Legal access to any providers, indexers, or content sources you configure.
- A backup target such as NAS storage, external disk, or another protected machine.
| Placeholder | What It Means | Example Format |
|---|---|---|
YOUR_ADMIN_USER | The Ubuntu user you created during install. | dan, mediaadmin |
YOUR_SERVER_IP | The server IP address on your LAN. | 192.168.1.50 |
LAN_CIDR | Your LAN network range for firewall rules. | 192.168.1.0/24 |
YOUR_NAS | NAS hostname or IP address. | nas.local or 192.168.1.20 |
YOUR_NAS_USER | NAS account allowed to access the media share. | A dedicated media-share user, not your main admin if possible. |
PLEX_CLAIM | Temporary Plex claim token if needed for first setup. | Generated from Plex, then removed or left blank after claim. |
TZ | Timezone used by containers and logs. | America/Chicago |
Find the Server IP and Connect with SSH
SSH is how you administer the server from another computer. If you installed Ubuntu Server with OpenSSH enabled, you can connect from macOS Terminal, Windows Terminal, PowerShell, or any SSH client. First, find the server IP address from the server console.
hostname -I
ip address show
The first command usually prints one or more IP addresses. Pick the LAN address assigned by your router. Then, from your normal computer, connect to the server.
ssh YOUR_ADMIN_USER@YOUR_SERVER_IP
What success looks like: Your terminal prompt should change to the Ubuntu server prompt. If SSH fails, check that the server is powered on, connected by Ethernet, has OpenSSH installed, and is reachable from the same LAN. Reserve this IP in your router before you build the stack so app URLs and firewall rules do not change later.
Linux Survival Commands for This Guide
You do not need to become a Linux expert before building this stack, but you do need to understand the few commands this guide uses repeatedly.
| Command | What It Does | Beginner Note |
|---|---|---|
pwd | Prints the current directory. | Use this when you are unsure where you are. |
ls | Lists files and folders. | ls -lah shows more detail. |
cd /path | Changes directory. | cd /opt/media-stack moves into the stack folder. |
sudo | Runs a command as administrator. | Read the command first. Administrator mistakes matter. |
nano file | Opens a simple terminal text editor. | Save with Ctrl+O, Enter, then exit with Ctrl+X. |
systemctl status NAME | Shows service health. | Useful for Docker and optional media-stack service checks. |
journalctl -u NAME | Shows service logs. | Useful when a service fails to start. |
docker compose ps | Shows running containers. | Run from /opt/media-stack. |
docker compose logs | Shows container logs. | Use --tail=100 to avoid huge output. |
pwd
ls -lah
cd /opt
cd ~
What success looks like: You should be comfortable moving between your home directory, /opt, and later /opt/media-stack. If a command says a file or folder does not exist, run pwd and ls before guessing.
Quick Glossary
| Term | Meaning |
|---|---|
| Terminal / shell | The command-line window where you type Linux commands. |
| SSH | A secure way to connect from your computer to the Ubuntu server terminal. |
| sudo | Runs a command with administrator privileges. Read the command before pressing Enter. |
| Docker image | The packaged application template. |
| Container | A running app created from an image. |
| Docker Compose | A YAML file and command that run several containers as one project. |
| Bind mount | A real host folder made visible inside a container. |
| PUID / PGID | The user and group IDs containers use when creating files on the host. |
| UMASK | Controls default permissions for new files and folders. 002 is a common shared-media setting. |
| Root folder | The final library folder Sonarr/Radarr manage. It is not a download folder. |
| Category | The SABnzbd label that tells Sonarr or Radarr which completed job belongs to it. |
| API key | A secret token one app uses to call another app. Treat it like a password. |
| Cache | Temporary workspace for downloads, repairs, transcodes, and previews. Not final storage. |
| Direct Play | Plex plays the file as-is. |
| Transcode | Plex or Tdarr converts video or audio into another format. |
| Reverse proxy | A routing layer for web apps. It is not magic security by itself. |
Install Ubuntu Packages and Docker Engine
Start with system updates, basic troubleshooting tools, storage helpers, and Docker Engine with the Compose plugin. The Docker install block follows Docker’s current Ubuntu repository pattern.
sudo apt update
sudo apt install -y \
ca-certificates \
curl \
gnupg \
lsb-release \
acl \
jq \
unzip \
tar \
wget \
sqlite3 \
mediainfo \
cifs-utils \
nfs-common \
ufw
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL \
https://download.docker.com/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources >/dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
sudo apt install -y \
docker-ce \
docker-ce-cli \
containerd.io \
docker-buildx-plugin \
docker-compose-plugin
sudo systemctl enable --now docker
sudo usermod -aG docker "$USER"
What success looks like: The command docker compose version should print a Compose version after you log out and back in. The docker group is effectively root-equivalent on the host, so only trusted admin users should belong to it.
docker compose version
docker run --rm hello-world
Create the /data Folder Layout
Use one logical path layout. Downloads are temporary. Media folders are final. Cache folders are temporary workspace. Config folders under /opt/media-stack are rebuild-critical.
/opt/media-stack
.env
compose.yml
plex/config
prowlarr/config
sabnzbd/config
sonarr/config
radarr/config
tdarr/server
tdarr/configs
tdarr/logs
/data
downloads/incomplete
downloads/usenet/movies
downloads/usenet/tv
media/movies
media/tv
cache/plex
cache/tdarr
Create a shared media group and directories. The 2775 directory mode sets group inheritance on directories, which helps files created under these paths keep the shared group.
sudo groupadd -f media
sudo usermod -aG media "$USER"
sudo install -d -o "$USER" -g media -m 2775 \
/opt/media-stack
sudo install -d -o "$USER" -g media -m 2775 \
/data/downloads/incomplete \
/data/downloads/usenet/movies \
/data/downloads/usenet/tv
sudo install -d -o "$USER" -g media -m 2775 \
/data/media/movies \
/data/media/tv
sudo install -d -o "$USER" -g media -m 2775 \
/data/cache/plex \
/data/cache/tdarr
sudo chgrp -R media /data
sudo chmod -R g+rwX /data
sudo find /data -type d -exec chmod 2775 {} +
Stop and verify: Run ls -ld /data /data/media /data/downloads /data/cache. You should see the media group and writable group permissions. If you already mounted an existing NAS library at /data/media, do not blindly run recursive ownership changes against years of existing data. Fix mount options first.
Mount Storage Safely
If final media lives on a NAS, mount the NAS at /data/media. If final media is local, keep the folder local. Choose one mount method. CIFS/SMB is common with Synology and Windows-friendly NAS setups. NFS is common for Linux-first storage.
CIFS / SMB Example
sudo tee /root/.smb-media >/dev/null <<'EOF'
username=YOUR_NAS_USER
password=YOUR_NAS_PASSWORD
domain=WORKGROUP
EOF
sudo chmod 600 /root/.smb-media
MEDIA_GID="$(getent group media | cut -d: -f3)"
CIFS_OPTS="credentials=/root/.smb-media"
CIFS_OPTS="$CIFS_OPTS,uid=$(id -u "$USER")"
CIFS_OPTS="$CIFS_OPTS,gid=$MEDIA_GID"
CIFS_OPTS="$CIFS_OPTS,dir_mode=0775,file_mode=0664"
CIFS_OPTS="$CIFS_OPTS,vers=3.1.1"
sudo mount -t cifs \
//YOUR_NAS/media \
/data/media \
-o "$CIFS_OPTS"
findmnt /data/media
touch /data/media/.media_mount_marker
NFS Example
sudo mount -t nfs \
YOUR_NAS:/volume1/media \
/data/media
findmnt /data/media
touch /data/media/.media_mount_marker
After a manual mount works, make it persistent with /etc/fstab or a systemd mount unit. The important beginner rule is this: Plex, Sonarr, Radarr, and Tdarr should not start against an empty local folder that only exists because the NAS failed to mount. Keep the marker file and test it during boot checks.
Create the Media Stack Environment File
The .env file gives Compose a single place for common settings. It should not be published publicly. It may contain values that reveal your timezone, user IDs, Plex claim token, or other private deployment details.
cd /opt/media-stack
MEDIA_UID="$(id -u "$USER")"
MEDIA_GID="$(getent group media | cut -d: -f3)"
cat > .env <<EOF
PUID=$MEDIA_UID
PGID=$MEDIA_GID
TZ=America/Chicago
UMASK=002
UMASK_SET=002
PLEX_CLAIM=
EOF
chmod 600 .env
cat .env
API key handling: Treat Plex tokens, Arr API keys, SABnzbd keys, indexer credentials, NAS credentials, and proxy admin passwords as secrets. Keep them out of screenshots, public posts, Git repositories, shared backup bundles, shell history, and reverse-proxy URLs. Use placeholders in articles and rotate any key that may have appeared in a log, screenshot, or published command.
Create the Docker Compose Stack
This stack intentionally binds most admin apps to 127.0.0.1. That means the apps listen on the server itself, not on every network interface. From another computer, use an SSH tunnel, a VPN, or a carefully protected reverse proxy after the local workflow works. Plex uses host networking because that is common for discovery and LAN playback.
cd /opt/media-stack
cat > compose.yml <<'EOF'
services:
plex:
image: lscr.io/linuxserver/plex:latest
container_name: plex
network_mode: host
env_file: .env
environment:
- VERSION=docker
- PLEX_CLAIM=${PLEX_CLAIM:-}
volumes:
- /opt/media-stack/plex/config:/config
- /data/media/movies:/movies
- /data/media/tv:/tv
- /data/cache/plex:/transcode
restart: unless-stopped
prowlarr:
image: lscr.io/linuxserver/prowlarr:latest
container_name: prowlarr
env_file: .env
volumes:
- /opt/media-stack/prowlarr/config:/config
ports:
- "127.0.0.1:9696:9696"
networks: [media]
restart: unless-stopped
sabnzbd:
image: lscr.io/linuxserver/sabnzbd:latest
container_name: sabnzbd
env_file: .env
volumes:
- /opt/media-stack/sabnzbd/config:/config
- /data/downloads:/data/downloads
ports:
- "127.0.0.1:8080:8080"
networks: [media]
restart: unless-stopped
sonarr:
image: lscr.io/linuxserver/sonarr:latest
container_name: sonarr
env_file: .env
volumes:
- /opt/media-stack/sonarr/config:/config
- /data:/data
ports:
- "127.0.0.1:8989:8989"
networks: [media]
restart: unless-stopped
radarr:
image: lscr.io/linuxserver/radarr:latest
container_name: radarr
env_file: .env
volumes:
- /opt/media-stack/radarr/config:/config
- /data:/data
ports:
- "127.0.0.1:7878:7878"
networks: [media]
restart: unless-stopped
tdarr:
image: ghcr.io/haveagitgat/tdarr:latest
container_name: tdarr
env_file: .env
environment:
- serverIP=0.0.0.0
- serverPort=8266
- webUIPort=8265
- internalNode=true
- inContainer=true
- ffmpegVersion=7
- nodeName=MainNode
- auth=false
volumes:
- /opt/media-stack/tdarr/server:/app/server
- /opt/media-stack/tdarr/configs:/app/configs
- /opt/media-stack/tdarr/logs:/app/logs
- /data/media:/media
- /data/cache/tdarr:/temp
ports:
- "127.0.0.1:8265:8265"
- "127.0.0.1:8266:8266"
networks: [media]
restart: unless-stopped
networks:
media:
name: media
EOF
Validate the Compose file before starting anything. This catches indentation mistakes, missing variables, and obvious syntax errors.
cd /opt/media-stack
docker compose -f compose.yml config
docker compose -f compose.yml pull
What success looks like: The docker compose config command should print a normalized version of the stack without errors. If YAML fails, fix the file before starting containers.
Start the Apps in a Safe Order
Start the support apps first, then Plex, then Tdarr last. Do not enable lists, broad searches, or library-wide Tdarr processing during first boot.
cd /opt/media-stack
docker compose -f compose.yml up -d prowlarr sabnzbd sonarr radarr
docker compose -f compose.yml ps
docker compose -f compose.yml logs --tail=80 \
prowlarr sabnzbd sonarr radarr
Use an SSH tunnel from your admin workstation to reach the private localhost-bound web UIs. Replace YOUR_ADMIN_USER and YOUR_SERVER_IP. Keep this terminal open while you configure the apps.
ssh \
-L 9696:127.0.0.1:9696 \
-L 8080:127.0.0.1:8080 \
-L 8989:127.0.0.1:8989 \
-L 7878:127.0.0.1:7878 \
-L 8265:127.0.0.1:8265 \
YOUR_ADMIN_USER@YOUR_SERVER_IP
| App | URL From Your Workstation With SSH Tunnel | First Action |
|---|---|---|
| Prowlarr | http://127.0.0.1:9696 | Set authentication, then add one lawful source/indexer and test it. |
| SABnzbd | http://127.0.0.1:8080 | Set authentication, folders, and categories before connecting Sonarr/Radarr. |
| Sonarr | http://127.0.0.1:8989 | Set authentication and add /data/media/tv as the final TV root. |
| Radarr | http://127.0.0.1:7878 | Set authentication and add /data/media/movies as the final movie root. |
| Tdarr | http://127.0.0.1:8265 | Leave for later until manual imports work. |
| Plex | http://YOUR_SERVER_IP:32400/web | Claim server, set libraries to final media paths only. |
Configure SABnzbd Folders and Categories
SABnzbd should download and unpack into download staging folders. It should not sort directly into Plex library roots. Sonarr and Radarr should import from completed categories into final media folders.
| SABnzbd Setting | Value |
|---|---|
| Temporary / incomplete folder | /data/downloads/incomplete |
| Completed download folder | /data/downloads/usenet |
| TV category | tv to /data/downloads/usenet/tv |
| Movie category | movies to /data/downloads/usenet/movies |
| Manual category | manual, optional, for controlled tests only |
| Sorting | Disabled for direct-to-library moves. Let Sonarr/Radarr import. |
What success looks like: A TV job should show category tv in SABnzbd, and a movie job should show category movies. Completed files should land under /data/downloads/usenet, not under /data/media.
Connect Prowlarr to Sonarr and Radarr
In Prowlarr, add Sonarr and Radarr as apps using the internal Docker service names. From Prowlarr’s container, http://sonarr:8989 and http://radarr:7878 are the clean internal addresses. Use each app’s API key from its settings page.
| Prowlarr App | Internal URL | What to Test |
|---|---|---|
| Sonarr | http://sonarr:8989 | Prowlarr app test passes, then synced indexers appear in Sonarr. |
| Radarr | http://radarr:7878 | Prowlarr app test passes, then synced indexers appear in Radarr. |
Add one source first, run a test search, and read rejection reasons inside Sonarr/Radarr before adding more. More sources do not fix bad categories, permissions, quality profiles, or path mappings.
Configure Sonarr and Radarr
Sonarr and Radarr need final root folders, SABnzbd as a download client, completed download handling, and conservative starter profiles. Avoid remote path mappings unless apps truly see different paths. With this Compose layout, the apps agree on /data, so remote path mapping should normally not be needed.
| Area | Sonarr | Radarr |
|---|---|---|
| Root folder | /data/media/tv | /data/media/movies |
| Download client | SABnzbd at http://sabnzbd:8080 | SABnzbd at http://sabnzbd:8080 |
| SAB category | tv | movies |
| Completed download handling | Enabled | Enabled |
| Rename files | Enabled after you choose a naming pattern | Enabled after you choose a naming pattern |
| Starter profile | 1080p Balanced TV | 1080p Balanced Movies |
Automation warning: Lists and indexers are intake controls, not permission controls. A list item, search result, or available file is not evidence that you have the right to obtain or process a work. Leave search-on-add disabled for new lists, tag list additions, review them before searching, and remove or exclude items you are not authorized to use.
Configure Plex Libraries
Plex should read final media folders only. Do not add download staging folders, SAB incomplete folders, Tdarr cache, or test output folders as Plex libraries.
| Plex Library | Folder |
|---|---|
| Movies | /movies inside the Plex container, mapped from /data/media/movies on the host. |
| TV Shows | /tv inside the Plex container, mapped from /data/media/tv on the host. |
| Optional Test Library | A small controlled folder if you want to test Tdarr behavior before production. |
Start Plex after the support apps are stable.
cd /opt/media-stack
docker compose -f compose.yml up -d plex
docker compose -f compose.yml logs --tail=80 plex
Run One Safe End-to-End Test
This is the most important section in the article. Do not skip it. You are proving the workflow before adding custom formats, lists, GPU tuning, reverse proxy access, or Tdarr processing. Use only a selected authorized item that belongs in your library.
- In Sonarr, add one test series or episode you are authorized to access.
- Run an interactive search and read accepted and rejected results.
- Send one selected authorized item to SABnzbd.
- Confirm SABnzbd uses category
tv. - Confirm Sonarr imports the completed file into
/data/media/tv. - Confirm Plex sees the TV item after a scan.
- Repeat the same controlled test in Radarr using category
movies. - Confirm no manual permission repair, move, or rename was needed.
| Check | Pass Condition | Stop and Fix If |
|---|---|---|
| SAB category | TV uses tv; movies use movies. | Jobs land in the wrong completed folder. |
| Import | Sonarr/Radarr import automatically. | The item stays in activity or needs manual import every time. |
| Plex visibility | Plex sees the final imported item. | Plex cannot read files or is pointed at the wrong folder. |
| Permissions | No manual chmod/chown required. | Every import creates unreadable files. |
| Paths | Apps agree on /data. | Remote path mapping is needed in a single-host stack. |
Add Tdarr After Imports Work
Tdarr changes files. That makes it powerful, but it also means it should be added after imports and Plex scans already work. Start with a test library or one controlled file. Do not enable broad library processing on day one.
cd /opt/media-stack
docker compose -f compose.yml up -d tdarr
docker compose -f compose.yml logs --tail=100 tdarr
- Point Tdarr libraries at final media folders, not incomplete downloads.
- Use
/data/cache/tdarras local cache. - Start with one worker and one test file.
- Configure the flow to skip compliant files, transcode only when needed, validate output, then replace.
- After replacement, refresh Plex and rescan Sonarr/Radarr so codec, size, duration, and MediaInfo stay current.
- Keep Plex priority higher than Tdarr during household viewing hours.
Optional NVIDIA GPU Checks
Only do this on GPU hosts. Install NVIDIA drivers and the NVIDIA Container Toolkit according to your distribution and current vendor guidance. Test the host first, then Docker, then Plex/Tdarr.
nvidia-smi
docker run --rm \
--gpus all \
nvidia/cuda:12.5.1-base-ubuntu22.04 \
nvidia-smi
If that Docker test fails, fix GPU container access before debugging Plex or Tdarr. Once GPU access works, add GPU settings to the specific services that need them and test one Plex hardware transcode plus one Tdarr test job.
Firewall and Remote Access
Docker-published ports can interact with host firewalls in ways beginners do not expect, so the safest first step is to bind admin apps to 127.0.0.1 and use SSH tunnels or VPN. Plex can be allowed on the LAN if you want local playback from clients. Replace the LAN CIDR with your real LAN.
LAN_CIDR="192.168.1.0/24"
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow from "$LAN_CIDR" \
to any port 32400 \
proto tcp \
comment "Plex LAN"
sudo ufw enable
sudo ufw status verbose
VPN note: In this article, VPN means a private administrative access path such as WireGuard, Tailscale, or OpenVPN for reaching your own services. A VPN does not create rights to access, copy, or distribute copyrighted material.
Admin exposure warning: Sonarr, Radarr, Prowlarr, SABnzbd, Tdarr, NAS admin pages, and reverse-proxy admin pages are control-plane apps. Prefer VPN-only access. If you proxy them, require HTTPS, app authentication, strong unique passwords, access lists that allow only LAN/VPN ranges, and logs you actually review.
Optional systemd Control for the Compose Stack
Docker Compose can restart containers by itself, but a systemd unit gives you a familiar service name and a place to enforce mount checks. This is optional. Use it after the stack works manually.
sudo tee /etc/systemd/system/media-stack.service >/dev/null <<'EOF'
[Unit]
Description=Plex Arr Tdarr Docker Compose stack
Requires=docker.service
Wants=network-online.target
After=docker.service network-online.target remote-fs.target
RequiresMountsFor=/data/media /data/downloads /data/cache /opt/media-stack
[Service]
Type=simple
WorkingDirectory=/opt/media-stack
ExecStartPre=/usr/bin/test -f /data/media/.media_mount_marker
ExecStart=/usr/bin/docker compose -f compose.yml up --remove-orphans
ExecStop=/usr/bin/docker compose -f compose.yml down
Restart=on-failure
TimeoutStartSec=0
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now media-stack
sudo systemctl status media-stack
Health Checks, Logs, and Troubleshooting
findmnt /data/media
test -f /data/media/.media_mount_marker
df -h /data/media /data/downloads /data/cache
cd /opt/media-stack
docker compose -f compose.yml ps
docker compose -f compose.yml logs --tail=100 \
sonarr radarr prowlarr sabnzbd tdarr plex
journalctl -u media-stack -n 100 --no-pager
| Symptom | Likely Cause | Where to Check |
|---|---|---|
| Web UI unreachable from laptop | Port is bound to server localhost and no SSH tunnel is open. | SSH tunnel command, docker compose ps, service logs. |
| SAB finishes but Sonarr/Radarr do not import | Wrong category, wrong completed path, permissions, or path mismatch. | SAB categories, Arr activity queue, container mounts. |
| Plex cannot see media | Wrong Plex library folder or unreadable files. | Plex library paths, file permissions, docker logs plex. |
| Permission denied after import | Wrong PUID/PGID/UMASK or NAS mount ownership. | .env, mount options, stat output. |
| Search results all rejected | Quality profile, language, category, or custom-format rules. | Sonarr/Radarr interactive search rejection reasons. |
| Tdarr replaces file but Arr still shows old size | Arr rescan did not run after replacement. | Tdarr post-processing, Arr API health, library rescan. |
| Everything breaks after reboot | NAS did not mount before services started. | findmnt, marker file, systemd unit, boot logs. |
Back Up Before You Automate
Application config folders are secret-bearing backups. They often contain API keys, tokens, provider settings, private hostnames, and app passwords. Store private backups on restricted or encrypted storage, keep multiple versions, and test restores. Public article bundles, sample compose files, screenshots, and downloadable scripts should use placeholders only.
cd /opt/media-stack
docker compose -f compose.yml stop \
sonarr radarr prowlarr sabnzbd tdarr plex
tar -czf \
"/data/media-stack-configs-$(date +%Y%m%d).tgz" \
/opt/media-stack
docker compose -f compose.yml start \
sonarr radarr prowlarr sabnzbd tdarr plex
ls -lh /data/media-stack-configs-*.tgz
| Back Up This | Why |
|---|---|
/opt/media-stack | Compose file, .env, app config folders, databases, and restore clues. |
| NAS mount notes | Needed to rebuild /data/media after a host crash. |
| API placeholders and integration notes | Do not publish real keys, but record where they belong. |
| Tdarr flows/plugins/scripts | These define how files are mutated and validated. |
| Plex metadata backup strategy | Plex rebuilds are easier when server identity and metadata are protected. |
Update and Rollback Routine
Use latest only as a beginner example. Production stacks should pin tested tags or digests once you settle on a working version. Never update every container before a config backup.
cd /opt/media-stack
docker compose -f compose.yml pull sonarr
docker compose -f compose.yml up -d sonarr
docker compose -f compose.yml logs --tail=100 sonarr
docker compose -f compose.yml ps
If an update breaks an app, change the image tag back to the previous known-good tag and restart that one service. If the app database migrated and the older app cannot read it, stop the service and restore the pre-update config backup.
What to Read Next
After this Day 0 setup works, use the rest of the series to tune each area. Do not skip the storage and backup articles; they are what make this stack survivable.
- Plex Homelab Architecture: Storage, GPU Transcoding, and Library Design
- Media Server Storage Design: NAS, CIFS/NFS Mounts, Permissions, and Local Cache
- Prowlarr Setup Guide: Clean Indexer Management for Sonarr and Radarr
- Sonarr Homelab Setup Guide: Install, Update, Optimize, and Connect to Tdarr
- Radarr Homelab Setup Guide: Install, Update, Optimize, Lists, and Tdarr Integration
- Building a Production-Grade Tdarr GPU Transcoding Stack for a Homelab
- Backup and Disaster Recovery for Plex, Sonarr, Radarr, Tdarr, Prowlarr, and SABnzbd
- Monitoring and Health Checks for a Plex and Arr Homelab
Official References
- Docker Engine: Install on Ubuntu
- Docker packet filtering and firewalls
- Servarr Docker Guide
- LinuxServer.io Plex image
- LinuxServer.io Sonarr image
- LinuxServer.io Radarr image
- LinuxServer.io Prowlarr image
- LinuxServer.io SABnzbd image
- Tdarr Docker Compose documentation
- Plex hardware-accelerated streaming
Final Checkpoint
The stack is ready for deeper tuning when one authorized TV item and one authorized movie item can move through the workflow without manual permission fixes: selected in Sonarr/Radarr, sent to SABnzbd with the right category, completed under downloads, imported into final media, visible in Plex, backed up in config, and left alone by Tdarr until you intentionally test it. That is the baseline. Everything else is optimization.
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.

