⚡ Want the easy, full version? → SparkBox
This is one of my original one-shot scripts. It still works — but these days everything I build goes into **[SparkBox](https://tomsparkbo>
👉 Get it free at tomsparkbox.com
Built by Tom Spark Reviews.
Tom Spark's ARR Stack
One-command automated media server with VPN protection. Sonarr, Radarr, Prowlarr, qBittorrent, Gluetun, Jellyfin, and more.
Full video tutorial: [YouTube Link Coming Soon]
What You Get
| Service | Port | Purpose |
|---|---|---|
| Gluetun | — | VPN tunnel with kill switch |
| qBittorrent | 8080 | Torrent client (VPN protected) |
| Prowlarr | 9696 | Indexer manager (VPN protected) |
| FlareSolverr | 8191 | Cloudflare bypass (VPN protected) |
| Radarr | 7878 | Movie automation |
| Sonarr | 8989 | TV show automation |
| Lidarr | 8686 | Music automation |
| Bazarr | 6767 | Subtitle automation |
| Jellyfin | 8096 | Media server / streaming |
| Seerr | 5055 | Netflix-like request UI (Overseerr/Jellyseerr successor) |
All download traffic routes through Gluetun's VPN tunnel. If the VPN drops, all traffic stops — zero leaks. The deunhealth container auto-r>
Quick Start
1. Install Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Log out and back in for group change to take effect
2. Clone this repo
git clone https://github.com/loponai/arrstack.git
cd arrstack
3. Create folder structure
sudo bash setup-folders.sh
This creates:
/data/
├── torrents/ ← qBittorrent downloads here
│ ├── movies/
│ ├── tv/
│ └── music/
└── media/ ← Radarr/Sonarr organize files here (Jellyfin reads from here)
├── movies/
├── tv/
└── music/
Hard links: Both folders MUST be on the same drive/filesystem. Radarr and Sonarr create hard links (not copies) — the file appears in>
4. Configure your VPN
cp .env.example .env
nano .env
Fill in your VPN provider and credentials. See VPN Setup Guides below.
5. Launch
docker compose up -d
6. Verify everything is working
bash test-stack.sh
This runs a full health check — Docker status, VPN connection, IP leak test, service accessibility, hard link support, and folder permissio>
You can also check manually:
# Check Gluetun's IP (should be VPN, not your real IP)
docker exec gluetun wget -qO- ifconfig.me
# qBittorrent shares Gluetun's network, so the above proves both are tunneled.
docker exec qbittorrent wget -qO- ifconfig.me
# Check health status of all containers
docker ps --format "table {{.Names}}\t{{.Status}}"
7. Configure services
Open each service in your browser at http://YOUR-SERVER-IP:PORT and follow the video tutorial for step-by-step configuration.
Quick reference:
- qBittorrent (
:8080) — Get temp password:docker logs qbittorrent 2>&1 | grep "temporary password" - Prowlarr (
:9696) — Add indexers, connect to Radarr/Sonarr. If an indexer is blocked by Cloudflare, set up FlareSolverr as a proxy: > - Radarr (
:7878) — Root folder:/data/media/movies, download client category:movies - Sonarr (
:8989) — Root folder:/data/media/tv, download client category:tv - Jellyfin (
:8096) — Add libraries:/data/media/movies,/data/media/tv,/data/media/music. To watch, open `http://YOUR-SERVER-I> - Seerr (
:5055) — Connect to Jellyfin, Radarr, and Sonarr. Seerr is the unified successor to Overseerr/Jellyseerr. If you previously >
Internal Docker IPs — use these when connecting services to each other (NOT localhost):
| IP | Service |
|---|---|
172.39.0.2 |
Gluetun (also qBittorrent, Prowlarr, FlareSolverr) |
172.39.0.3 |
Radarr |
172.39.0.4 |
Sonarr |
172.39.0.5 |
Lidarr |
172.39.0.6 |
Bazarr |
172.39.0.7 |
Jellyfin |
172.39.0.8 |
Seerr |
These IPs are the same for everyone — they're hardcoded in the docker-compose file. Common connections:
- Radarr/Sonarr → Download Client → qBittorrent: host
172.39.0.2, port8080 - Prowlarr → Apps → Radarr: server
http://172.39.0.3:7878 - Prowlarr → Apps → Sonarr: server
http://172.39.0.4:8989 - Prowlarr → Apps → Prowlarr Server:
http://172.39.0.2:9696 - Seerr → Radarr: host
172.39.0.3, port7878 - Seerr → Sonarr: host
172.39.0.4, port8989 - Seerr → Jellyfin: host
172.39.0.7, port8096
Important Radarr/Sonarr settings:
- Media Management → Show Advanced → Use Hardlinks instead of Copy → must be ON
- Media Management → Rename Movies/Episodes → recommended ON
Recommended quality profile (1080p baseline, 4K preferred):
Go to Settings → Profiles and edit or create a profile:
- Uncheck everything below 1080p (720p, 480p, etc.)
- Check/enable everything from HDTV-1080p up through Bluray-2160p
- Set Cutoff to
Bluray-1080p— this is the minimum quality Radarr/Sonarr will be happy with - Set Upgrade Until to
Bluray-2160p— it will automatically upgrade to 4K if one becomes available
This means it grabs a 1080p release right away so you can start watching, then silently upgrades to 4K later if it finds one.
VPN Setup Guides
Surfshark (Recommended)
The best value for torrenting — cheapest long-term plans, fast WireGuard speeds, and easy setup with Gluetun. [Get Surfshark](https://get.s>
- Go to Surfshark Manual Setup
- Select WireGuard and get your credentials (private key + address)
- In your
.env:
VPN_SERVICE_PROVIDER=surfshark
VPN_TYPE=wireguard
WIREGUARD_PRIVATE_KEY=your_key_here
WIREGUARD_ADDRESSES=10.14.0.2/16
SERVER_COUNTRIES=United States
The
.env.examplefile is pre-configured for Surfshark. Just paste your private key and you're good to go.
NordVPN
- Go to NordVPN Manual Setup
- Select NordLynx (WireGuard) and generate a private key
- In your
.env:
VPN_SERVICE_PROVIDER=nordvpn
VPN_TYPE=wireguard
WIREGUARD_PRIVATE_KEY=your_key_here
WIREGUARD_ADDRESSES=10.5.0.2/16
SERVER_COUNTRIES=United States
ProtonVPN
- Go to ProtonVPN WireGuard Config → Downloads → WireGuard
- Generate a config file, open it, copy
PrivateKeyandAddress - In your
.env:
VPN_SERVICE_PROVIDER=protonvpn
VPN_TYPE=wireguard
WIREGUARD_PRIVATE_KEY=your_key_here
WIREGUARD_ADDRESSES=10.2.0.2/32
SERVER_COUNTRIES=United States
VPN_PORT_FORWARDING=on
AirVPN
- Go to AirVPN Config Generator → Client Area → Config Generator
- Select Linux → WireGuard → pick server → Generate
- In your
.env:
VPN_SERVICE_PROVIDER=airvpn
VPN_TYPE=wireguard
WIREGUARD_PRIVATE_KEY=your_key_here
WIREGUARD_PUBLIC_KEY=server_public_key
WIREGUARD_PRESHARED_KEY=your_preshared_key
WIREGUARD_ADDRESSES=your_ip/32
FIREWALL_VPN_INPUT_PORTS=your_port
VPN_PORT_FORWARDING=on
Other Providers
Gluetun supports 30+ providers. Check the full provider list.
Starting and Stopping
Start the stack:
cd arrstack
docker compose up -d
Stop the stack:
cd arrstack
docker compose down
Check status:
docker ps --format "table {{.Names}}\t{{.Status}}"
Auto-Start After Reboot
All containers are set to restart: unless-stopped, which means they automatically come back once Docker is running. You just need to make>
Linux (dedicated server or VM):
Run this once and you're done:
sudo systemctl enable docker
Windows (running Docker inside WSL):
WSL (Windows Subsystem for Linux) doesn't start Docker automatically when your PC boots. Here's how to fix that:
Step 1: Open your WSL terminal and run this command to edit the WSL config file:
sudo nano /etc/wsl.conf
Step 2: Your file might already have some lines in it (like [boot] or [user]). Look for a [boot] section. If it exists, add the `>
[boot]
command=service docker start
Step 3: Save the file by pressing Ctrl+X, then Y, then Enter.
Step 4 (optional): By default, WSL only starts when you open a terminal. If you want it to start automatically when Windows boots (so y>
- Press
Win+Ron your keyboard - Type
shell:startupand press Enter — this opens your Windows Startup folder - Right-click in the folder → New → Text Document
- Name it
wsl.vbs(make sure it ends in.vbs, not.vbs.txt— if you can't see file extensions, go to View → Show → File name extensi> - Right-click the file → Edit (or Open with Notepad) and paste this:
Set ws = CreateObject("Wscript.Shell")
ws.Run "wsl -d Ubuntu -u root -- service docker start", 0
- Save and close
That's it — next time your PC restarts, WSL starts Docker automatically and all your containers come back up on their own. No commands need>
Updating
cd arrstack
docker compose pull
docker compose up -d
Remote Access with Tailscale (Optional)
Want to access Jellyfin, Seerr, or any service from outside your home? Tailscale creates a private network betwee>
On your server:
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
On your phone/laptop/TV:
- Install Tailscale from your app store
- Sign in with the same account
Access your services from anywhere:
http://YOUR-TAILSCALE-IP:8096 ← Jellyfin
http://YOUR-TAILSCALE-IP:5055 ← Seerr
http://YOUR-TAILSCALE-IP:7878 ← Radarr
http://YOUR-TAILSCALE-IP:8989 ← Sonarr
Find your Tailscale IP with tailscale ip -4 on the server.
Tailscale is free for personal use (up to 100 devices). Everything is encrypted with WireGuard — nobody can see your traffic, not even Tail>
Do NOT expose Jellyfin directly to the internet (no port forwarding on your router). Use Tailscale or a reverse proxy instead. Direct>
Sharing with Family and Friends
Your family and friends only need two things — Seerr to request movies/shows and Jellyfin to watch them. They never see Radarr, Son>
Step 1: Invite them to your Tailscale network
- Go to the Tailscale admin console
- Click Share on your server's machine
- Enter their email — they'll get an invite link
Step 2: They install Tailscale
- Download Tailscale on their phone, laptop, or TV from tailscale.com/download
- Accept your invite and sign in Step 3: They access your services
Give them these two links (replace with your Tailscale IP):
http://YOUR-TAILSCALE-IP:5055 ← Seerr (request movies and shows)
http://YOUR-TAILSCALE-IP:8096 ← Jellyfin (watch everything)
For TVs and phones, they can install the Jellyfin app (available on Roku, Fire TV, Apple TV, Android TV, iOS, Android) and enter yo>
That's it — they request, you automatically download, they watch. No technical knowledge needed on their end.
Troubleshooting
Gluetun unhealthy / won't connect:
- Double-check VPN credentials in
.env— these are NOT your login email/password - Try removing the gluetun folder and restarting:
rm -rf gluetun && docker compose up -d - Check logs:
docker logs gluetun
qBittorrent can't connect:
- Make sure Gluetun is healthy:
docker ps(should show "healthy") - Check qBit is using VPN:
docker exec gluetun wget -qO- ifconfig.me - In qBittorrent settings → Advanced → set Network Interface to
tun0
Movie or show not downloading:
- Quality profile too strict — If Radarr/Sonarr can't find a release matching your quality profile, it won't download anything. Go to t>
- Not enough indexers — Public indexers have limited catalogs. If you only have one or two indexers in Prowlarr, add more (1337x, The P>
- Not enough seeders — Some torrents just don't have anyone sharing them, especially older or niche content. Check qBittorrent — if the>
- Indexer blocked by Cloudflare — See the Prowlarr setup note above about setting up FlareSolverr with tags.
Hard links not working (files copying instead):
- Both
/data/torrentsand/data/mediamust be on the same filesystem - Check Radarr/Sonarr → Settings → Media Management → "Use Hardlinks" is checked
- Verify with:
ls -i /data/torrents/movies/yourfileandls -i /data/media/movies/YourMovie/yourfile— inode numbers should match
Permission errors:
- Run
idand make sure PUID/PGID in.envmatch your user - Re-run:
sudo chown -R $(id -u):$(id -g) /data
Service won't start — "port already in use":
Another program on your system might be using the same port. This is common with port 5055 (Seerr) but can happen with any service.
-
Find what's using the port (replace
5055with the port number from the error):Linux:
sudo ss -tlnp | grep 5055Windows (WSL users) — run in PowerShell:
netstat -ano | findstr :5055This gives you a PID (process ID). Find the program name:
Get-Process -Id <PID> | Select-Object ProcessName, Id, Path -
Either stop/disable that program, or change the port in
docker-compose.ymlto an unused one (e.g.5056:5055). -
If it's a Windows service hogging the port, disable it in an admin PowerShell:
Stop-Service <ServiceName> -Force Set-Service <ServiceName> -StartupType Disabled -
Then recreate the container:
docker compose up -d --force-recreate <service-name>
Seerr stuck restarting / crash-looping:
The current docker-compose.yml uses a named volume (seerr_config) for Seerr's config, which fixes both causes of the crash loop. If you>
Root cause (for reference):
- Linux/macOS: Seerr runs as the
nodeuser (UID 1000). A bind-mounted./seerrfolder is created root-owned on first `docker compose> - Windows/WSL: Bind mounts go through an SMB share inside Docker Desktop's VM, which doesn't support file locking. Seerr's SQLite DB co>
Named volumes sidestep both problems because Docker creates them with correct ownership inside its own managed storage.
If you still see it after pulling:
- Check logs:
docker logs seerr | tail -30 - Nuke the volume and start fresh (you'll lose Seerr settings, not media):
docker compose down seerr && docker volume rm arrstack_seerr_config && docker compose up -d seerr
Migrating Seerr config to a named volume (existing installs):
If you had Seerr working on an older version of this repo with a bind-mounted ./jellyseerr or ./seerr folder and want to keep your sett>
docker compose down seerr
# pick whichever folder you actually have
SRC=./seerr
[ -d ./jellyseerr ] && SRC=./jellyseerr
docker volume create arrstack_seerr_config
docker run --rm -v "$(pwd)/${SRC#./}":/src -v arrstack_seerr_config:/dest alpine sh -c "cp -a /src/. /dest/ && chown -R 1000:1000 /dest"
docker compose up -d seerr
If you don't care about preserving settings, just docker compose up -d seerr — Seerr will start fresh and walk you through setup again.
Can't log into qBittorrent:
- qBittorrent generates a temporary password every time it starts. Get it with:
docker logs qbittorrent 2>&1 | grep "temporary password" - Default username is
admin. Once logged in, go to Tools → Options → Web UI and set a permanent password.
Services can't connect to each other (connection refused, timeout):
- Don't use
localhostwhen connecting services together — that won't work across Docker containers. - Use the internal Docker IPs instead:
- qBittorrent/Prowlarr/FlareSolverr:
172.39.0.2 - Radarr:
172.39.0.3 - Sonarr:
172.39.0.4 - Jellyfin:
172.39.0.7
- qBittorrent/Prowlarr/FlareSolverr:
- The one exception: Prowlarr → FlareSolverr can use
localhost:8191because they both run through Gluetun and share the same network.
"Root folder does not exist" in Radarr/Sonarr:
- Make sure you ran
sudo bash setup-folders.shto create the/datadirectory structure. - Double-check the root folder path — it should be
/data/media/moviesfor Radarr and/data/media/tvfor Sonarr (not/moviesor `/data>
Downloads stuck at "importing" or "waiting to import":
- This is almost always a permissions issue. Fix it with:
sudo chown -R $(id -u):$(id -g) /data sudo chmod -R 775 /data - Make sure PUID/PGID in your
.envmatch your user (check withid). Jellyfin library is empty after downloads finish: - Make sure your Jellyfin libraries point to the correct paths:
/data/media/movies,/data/media/tv,/data/media/music - Jellyfin doesn't scan instantly. Go to Dashboard → Libraries → click the
...menu → Scan Library to force a refresh. - You can also set up scheduled scans in Dashboard → Scheduled Tasks.
Subtitles not downloading (Bazarr):
- Bazarr needs to be connected to Radarr and Sonarr: Settings → Radarr / Sonarr → enter the IP (
172.39.0.3/172.39.0.4) and API key. - You also need at least one subtitle provider: Settings → Providers → Add → OpenSubtitles.com is the most popular (free account requir>
Everything works but downloads are slow:
- Your VPN server might be too far away. Change
SERVER_COUNTRIESin your.envto a country closer to you, then restart:docker compose down && docker compose up -d - Check your VPN speed:
docker exec gluetun wget -qO- https://speed.cloudflare.com/__down?measId=10000000 > /dev/null— if it's very slow>
Disk space filling up:
- By default, qBittorrent keeps torrents after Radarr/Sonarr imports them. To auto-clean:
- In Radarr/Sonarr → Settings → Download Clients → click on qBittorrent → enable Remove Completed
- This deletes the torrent from qBittorrent after the file has been imported (the hard link in your media folder is kept, so you don't lo>
VPN IP leak — want to make sure your real IP isn't exposed:
# Check the VPN container's IP (should NOT be your real IP)
docker exec gluetun wget -qO- ifconfig.me
# Compare with your real IP (run this outside Docker)
curl -s ifconfig.me
If both IPs are the same, your VPN isn't working — check Gluetun logs with docker logs gluetun.
Credits
Built by Tom Spark following Trash Guides and [Servarr Wiki](https://wiki.se>
Uses Gluetun for VPN, LinuxServer.io container images, and [Seerr](https://gi>