A Debian 12.11 install guide and helper script for a home ARR media stack using Docker for Radarr, Sonarr, Lidarr, Whisparr v3, and Whisparr v2, with NZBGet installed natively on Debian and media stored on Windows SMB shares.
This project is built around one practical goal: keep the application configs safe in /opt/media-stack, keep the media on the Windows storage box, and make every container see the same paths so imports do not turn into a path-mapping circus.
scripts/install-debian.sh prepares a Debian 12 server for the stack. It installs base packages, Docker Engine, native NZBGet, unrar, ffmpeg, and the Docker Compose plugin, then stages the Compose file, example environment file, Caddy examples, and SMB fstab example under /opt/media-stack.
The script intentionally does not write your SMB password, does not edit /etc/fstab, and does not start the stack unless you pass --start. Those steps are left manual so the storage mounts can be verified before any app starts writing to them.
Apps:
Radarr Docker, hotio image
Sonarr Docker, hotio image
Lidarr Docker, hotio image
Whisparr v3 Docker, hotio image
Whisparr v2 Docker, hotio image
NZBGet Native Debian package
Caddy Optional reverse proxy
Storage:
Windows storage server: 192.168.137.110
Cinema movies: \\192.168.137.110\cinema\movies
Cinema series: \\192.168.137.110\cinema\series
Cinema music: \\192.168.137.110\cinema\music
Adult movies: \\192.168.137.110\adult\movies
Adult series: \\192.168.137.110\adult\adultseries
Debian mount paths:
/mnt/media/cinema
/mnt/media/adult
Container paths:
/mnt/media/cinema
/mnt/media/adult
/data/cinema
/data/adult
The /mnt/media/... paths are the important ones. Native NZBGet reports those paths, and the containers can see those same paths, so remote path mappings stay simple.
This stack can run on a small VM, but it becomes unstable when memory is tight. The Arr apps use extra RAM during RSS syncs, library refreshes, imports, media analysis, and Whisparr v3 matching. NZBGet unpacking can also spike memory and disk I/O.
Recommended Hyper-V sizing:
Minimum test VM: 8 GB RAM, 4 vCPU
Better daily use: 12 GB RAM, 4 to 6 vCPU
Recommended stable VM: 16 GB RAM, 6 to 8 vCPU
Large libraries/imports: 24 GB RAM if available
Storage layout:
Debian OS disk: 60 GB minimum, 100 GB recommended
/opt/media-stack: Keep on the VM's local virtual disk
/opt/media-stack/appdata: local VM disk, not SMB
Media libraries: SMB shares are fine
Downloads: SMB shares are used in this guide, but local SSD is faster if you redesign paths later
Hyper-V recommendations:
Use a fixed memory assignment if possible.
If using Dynamic Memory, set Minimum RAM to at least 8 GB.
Set Startup RAM to 12 GB or 16 GB.
Do not let the VM balloon down while imports are running.
Keep checkpoints/snapshots short-lived; do not run the stack long-term on old checkpoint chains.
Add swap on Debian even if you increase RAM. Swap is not a replacement for RAM, but it prevents sudden crashes when imports or unpacking briefly spike memory:
sudo fallocate -l 8G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
echo 'vm.swappiness=20' | sudo tee /etc/sysctl.d/99-media-stack-memory.conf
sudo sysctl --system
free -h
swapon --showFor best stability:
Do not import every library at once.
Do not run Radarr, Sonarr, Lidarr, and both Whisparr refreshes at the same time.
Let one large import finish before starting the next.
Keep app configs in /opt/media-stack/appdata and back them up.
Watch memory during first imports with: docker stats
Check for OOM kills with: sudo dmesg -T | grep -Ei 'oom|killed process|out of memory'
This project installs the stack, but it does not include Usenet access or indexer accounts. Before the Arr apps can search and download, you need:
1. One or more Usenet indexers added in each Arr app.
2. A Usenet provider/news server added in NZBGet.
Use only content you have the right to access. Indexer registrations, API limits, invite status, pricing, and provider deals change often, so treat the lists below as starting points to research.
Common NZB indexers to look at:
Common Usenet providers/news servers to look at:
In the Arr apps, indexers usually go under:
Settings -> Indexers
In NZBGet, your news server goes under:
Settings -> News-Servers
Run this on the Debian 12.11 server:
sudo apt update
sudo apt install -y git
cd /tmp
git clone https://github.com/DisturbedMind/arr-media-stack-debian.git
cd arr-media-stack-debianIf you already cloned it:
cd /tmp/arr-media-stack-debian
git pullStage the stack without starting the containers:
chmod +x scripts/install-debian.sh
./scripts/install-debian.shUse --start only after SMB mounts and NZBGet settings are ready:
./scripts/install-debian.sh --startAfter the installer, log out and back in if it added your user to the docker group.
unrar is a must for many Usenet downloads. ffmpeg is also a must for reliable media probing and imports.
On Debian 12, make sure /etc/apt/sources.list.d/debian.sources includes:
Components: main contrib non-free non-free-firmware
If /etc/apt/sources.list.d/debian.sources is blank, use this complete Bookworm source file:
Types: deb
URIs: http://deb.debian.org/debian
Suites: bookworm bookworm-updates
Components: main contrib non-free non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
Types: deb
URIs: http://security.debian.org/debian-security
Suites: bookworm-security
Components: main contrib non-free non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
If apt update complains about duplicate Debian entries, comment out every active deb line in /etc/apt/sources.list and keep the real Debian repo definition in /etc/apt/sources.list.d/debian.sources.
Then run:
sudo apt update
sudo apt install -y unrar 7zip p7zip-full ffmpeg
which unrar
ffmpeg -version
sudo systemctl restart nzbgetCreate a credentials file for the Windows storage shares:
sudo nano /etc/samba/media-stack.credUse:
username=YOUR_WINDOWS_USER
password=YOUR_WINDOWS_PASSWORD
domain=WORKGROUP
Secure it:
sudo chmod 600 /etc/samba/media-stack.credCreate the mount points if they do not already exist:
sudo mkdir -p /mnt/media/cinema /mnt/media/adultCheck your Debian user id and group id:
idMost single-user Debian installs use uid=1000,gid=1000. If your id command shows different values, change uid=1000,gid=1000 in the example below.
Edit /etc/fstab:
sudo nano /etc/fstabPaste this at the bottom of /etc/fstab:
//192.168.137.110/cinema /mnt/media/cinema cifs credentials=/etc/samba/media-stack.cred,uid=1000,gid=1000,iocharset=utf8,vers=3.0,file_mode=0775,dir_mode=0775,noperm,nofail,_netdev,x-systemd.automount,x-systemd.idle-timeout=60 0 0
//192.168.137.110/adult /mnt/media/adult cifs credentials=/etc/samba/media-stack.cred,uid=1000,gid=1000,iocharset=utf8,vers=3.0,file_mode=0775,dir_mode=0775,noperm,nofail,_netdev,x-systemd.automount,x-systemd.idle-timeout=60 0 0
Save the file, then mount and verify:
sudo systemctl daemon-reload
sudo mount -a
findmnt /mnt/media/cinema
findmnt /mnt/media/adultYou should see:
//192.168.137.110/cinema /mnt/media/cinema
//192.168.137.110/adult /mnt/media/adult
Confirm you can list the mounted shares:
ls -la /mnt/media/cinema
ls -la /mnt/media/adultmkdir -p /mnt/media/cinema/{movies,series,music,.recyclebin}
mkdir -p /mnt/media/cinema/downloads/{intermediate,completed/{radarr,sonarr,lidarr}}
mkdir -p /mnt/media/adult/{movies,adultseries,.recyclebin}
mkdir -p /mnt/media/adult/downloads/completed/{whisparrv3,whisparrv2}
mkdir -p /mnt/media/adult/movies/importOpen NZBGet:
http://DEBIAN_SERVER_IP:6789
Change the default password immediately.
Set:
Settings -> Security -> ControlIP -> 0.0.0.0
Settings -> Unpack -> Unpack -> yes
Set paths:
MainDir: /mnt/media/cinema/downloads
InterDir: /mnt/media/cinema/downloads/intermediate
DestDir: /mnt/media/cinema/downloads/completed
Set categories:
radarr -> /mnt/media/cinema/downloads/completed/radarr
sonarr -> /mnt/media/cinema/downloads/completed/sonarr
lidarr -> /mnt/media/cinema/downloads/completed/lidarr
whisparrv3 -> /mnt/media/adult/downloads/completed/whisparrv3
whisparrv2 -> /mnt/media/adult/downloads/completed/whisparrv2
Restart and verify the listener:
sudo systemctl restart nzbget
sudo ss -ltnp | grep ':6789'You want NZBGet listening on:
0.0.0.0:6789
cd /opt/media-stack
docker compose up -d
docker compose psDirect app URLs:
Radarr: http://DEBIAN_SERVER_IP:7878
Sonarr: http://DEBIAN_SERVER_IP:8989
Lidarr: http://DEBIAN_SERVER_IP:8686
Whisparr v3: http://DEBIAN_SERVER_IP:6969
Whisparr v2: http://DEBIAN_SERVER_IP:6970
NZBGet: http://DEBIAN_SERVER_IP:6789
The working fix for this install was to use the Docker Compose network gateway:
172.18.0.1
Allow the Compose subnet to reach NZBGet:
sudo ufw allow from 172.18.0.0/16 to any port 6789 proto tcp
sudo ufw reloadConfirm the gateway:
docker network inspect media -f '{{(index .IPAM.Config 0).Gateway}}'Expected:
172.18.0.1
In every Arr app, add NZBGet:
Host: 172.18.0.1
Port: 6789
Use SSL: unchecked
Url Base: blank
Username: nzbget
Password: your NZBGet password
Use these categories:
Radarr: radarr
Sonarr: sonarr
Lidarr: lidarr
Whisparr v3: whisparrv3
Whisparr v2: whisparrv2
Do not put /radarr, /sonarr, /lidarr, /whisparr, or /nzbget in the download client Url Base field. Caddy is for browser access; Arr-to-NZBGet traffic should go directly to 172.18.0.1:6789.
Use:
Radarr: /mnt/media/cinema/movies
Sonarr: /mnt/media/cinema/series
Lidarr: /mnt/media/cinema/music
Whisparr v3: /mnt/media/adult/movies
Whisparr v2: /mnt/media/adult/adultseries
Recycle bins:
Radarr/Sonarr/Lidarr: /mnt/media/cinema/.recyclebin
Whisparr v3/Whisparr v2: /mnt/media/adult/.recyclebin
Because native NZBGet reports /mnt/media/..., the remote and local paths should match.
The Host field must exactly match the NZBGet download client host:
172.18.0.1
Mappings:
Radarr
Host: 172.18.0.1
Remote Path: /mnt/media/cinema/downloads/completed/radarr
Local Path: /mnt/media/cinema/downloads/completed/radarr
Sonarr
Host: 172.18.0.1
Remote Path: /mnt/media/cinema/downloads/completed/sonarr
Local Path: /mnt/media/cinema/downloads/completed/sonarr
Lidarr
Host: 172.18.0.1
Remote Path: /mnt/media/cinema/downloads/completed/lidarr
Local Path: /mnt/media/cinema/downloads/completed/lidarr
Whisparr v3
Host: 172.18.0.1
Remote Path: /mnt/media/adult/downloads/completed/whisparrv3
Local Path: /mnt/media/adult/downloads/completed/whisparrv3
Whisparr v2
Host: 172.18.0.1
Remote Path: /mnt/media/adult/downloads/completed/whisparrv2
Local Path: /mnt/media/adult/downloads/completed/whisparrv2
Whisparr v3 does not import existing movies as smoothly as Radarr. Use a small staging folder first:
/mnt/media/adult/movies/import
From Windows:
\\192.168.137.110\adult\movies\import
Test with a few movies before pointing Whisparr v3 at a large library.
Caddy is optional. Direct ports work without it.
Use one of these layouts:
Direct ports:
Use http://DEBIAN_SERVER_IP:7878 and friends.
Keep every Arr URL Base blank.
Internal Docker Caddy, path-based:
Use http://DEBIAN_SERVER_IP/radarr/
Requires Arr URL Base values like /radarr.
Internal Docker Caddy, hostname-based wolf.den:
Use http://radarr.wolf.den/
Keep every Arr URL Base blank.
External/native Caddy, hostname-based wolf.den:
Use http://radarr.wolf.den/
Keep every Arr URL Base blank.
For the current wolf.den setup with Docker Caddy inside this stack:
cd /opt/media-stack
mkdir -p /opt/media-stack/caddy/data /opt/media-stack/caddy/config
cp /tmp/arr-media-stack-debian/caddy/Caddyfile.internal-wolf.den.example /opt/media-stack/caddy/Caddyfile
docker compose --profile internal-caddy up -d caddy
docker compose ps caddy
docker compose logs --tail=100 caddyPoint DNS records to the Debian server IP that publishes Docker port 80:
radarr.wolf.den
sonarr.wolf.den
lidarr.wolf.den
whisparrv3.wolf.den
whisparrv2.wolf.den
nzbget.wolf.den
For an already-running external/native Caddy, use:
caddy/Caddyfile.external-wolf.den.example
Replace ARR_STACK_IP with the Debian ARR stack IP. If external Caddy is on the same Debian server as the stack, use 127.0.0.1. Do not use 172.18.0.1 in an external Caddyfile.
Open port 80 to the LAN:
sudo ufw allow from 192.168.137.0/24 to any port 80 proto tcp
sudo ufw reloadRun on Debian:
findmnt /mnt/media/cinema
findmnt /mnt/media/adult
cd /opt/media-stack
docker compose ps
docker exec radarr ls -la /mnt/media/cinema/movies
docker exec sonarr ls -la /mnt/media/cinema/series
docker exec lidarr ls -la /mnt/media/cinema/music
docker exec whisparrv3 ls -la /mnt/media/adult/movies
docker exec whisparrv2 ls -la /mnt/media/adult/adultseries
docker exec lidarr sh -lc 'wget -T 5 -S -O- http://172.18.0.1:6789/jsonrpc 2>&1 | head -80'Good NZBGet connectivity signs:
401 Unauthorized
400 Bad Request
JSON response
Bad signs:
Connection timed out
Connection refused
Could not resolve host
App configs live under:
/opt/media-stack/appdata
Back them up:
cd /opt/media-stack
docker compose stop
sudo tar -czf /opt/media-stack/backups/appdata-$(date +%F-%H%M).tgz appdata .env compose.yml
docker compose up -dSafe container recreation:
cd /opt/media-stack
docker compose up -d --force-recreate radarrAvoid:
docker compose down -v
docker system prune --volumes
rm -rf /mnt/media/cinema/*
rm -rf /mnt/media/adult/*If Docker says permission denied for /var/run/docker.sock, either run the command with sudo or log out and back in after being added to the docker group:
sudo usermod -aG docker "$USER"Then log out and back in.
Use:
Host: 172.18.0.1
Port: 6789
Url Base: blank
Also confirm the firewall rule:
sudo ufw allow from 172.18.0.0/16 to any port 6789 proto tcp
sudo ufw reloadhost.docker.internal may resolve to a 172.x.x.x address and still hang. On this install, 172.18.0.1 was the reliable fix.
Check unrar first:
which unrar
unrar
sudo apt install -y unrar 7zip p7zip-full ffmpeg
sudo systemctl restart nzbgetMissing unrar can make a good NZB fail immediately after download because NZBGet cannot unpack it. Missing ffmpeg can break media probing/import behavior later.
Do not create a new blank config. Find the active config first:
sudo systemctl show nzbget -p ExecStart --value
ps -eo user,group,args | grep '[n]zbget'
sudo find /etc /opt /var/lib /usr/local -iname 'nzbget.conf' -type f 2>/dev/nullThe preferred method is still the NZBGet Web UI:
Settings -> Security -> ControlIP -> 0.0.0.0
Use one Debian source style only. For this guide, keep the real Debian repo configuration in:
/etc/apt/sources.list.d/debian.sources
Comment out duplicate active deb lines in:
/etc/apt/sources.list
Then:
sudo apt updateStart the stack once so Compose creates the network:
cd /opt/media-stack
docker compose up -dDo not create the media network manually.
If Docker says:
network media was found but has incorrect label com.docker.compose.network set to "" (expected: "media")
remove the manually-created network and let Compose recreate it:
cd /opt/media-stack
docker compose down
docker network rm media
docker compose up -dIf this fails:
sudo systemctl restart caddywith:
Unit caddy.service could not be found.
then Caddy is not installed as a native Debian service on that machine. That is fine if you chose Docker Caddy.
Check for a Caddy container:
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}' | grep -i caddyIf there is no Caddy container and no native Caddy service, start the internal Docker Caddy profile:
cd /opt/media-stack
cp /tmp/arr-media-stack-debian/caddy/Caddyfile.internal-wolf.den.example /opt/media-stack/caddy/Caddyfile
docker compose --profile internal-caddy up -d caddy
docker compose ps caddyCheck:
sudo ss -ltnp | grep ':80'If docker-proxy owns port 80, a Caddy container is already running. If native Caddy owns it, do not also start Docker Caddy on port 80.
This means the Caddyfile has a bind line for an IP the server does not own.
Check:
ip -br addrFor this guide, the safest fix is usually to remove bind 192.168.137.x lines and let Caddy listen normally.
Check DNS:
nslookup radarr.wolf.denBypass DNS from Windows:
curl.exe -v --resolve radarr.wolf.den:80:DEBIAN_SERVER_IP http://radarr.wolf.den/If --resolve works but the browser does not, the client is using stale or wrong DNS.
This stack is internal HTTP unless you deliberately add TLS:
http://radarr.wolf.den/
Do not use:
https://radarr.wolf.den/
Use the staging folder:
/mnt/media/adult/movies/import
Then in Whisparr v3, process the import folder from the UI. Start with a few files before trying a full library.
- Debian installer
- Native NZBGet Compose
- Docker NZBGet Compose
- Path-based Docker Caddyfile
- Hostname Docker Caddy example
- Internal wolf.den Docker Caddy example
- Wolf Den external Caddy example
- External Caddy on 192.168.137.251 example
- External Caddy port-mirror example
- Env example
- SMB fstab example
- Troubleshooting notes
- ZIP bundle
