Purpose of this guide

While there are many Linux based guides for implementing the *Arr stack, some of us are stuck with Windows either by choice or by circumstance.

This guide is intended to help you implement a performant and reliable *arr stack on a Windows 11 PC using:

  • WSL2 with Ubuntu 24.04.1 LTS
  • Docker Desktop for Windows (to run the linux based *Arr stack):
    • gluetun
    • qbittorrent
    • lidarr
    • bazarr
    • radarr
    • sonarr
    • flaresolverr
    • prowlarr
    • sabnzbd
    • jellyseerr
  • Plex Server and/or Jellyfin Server to serve Media (you’ll want the Windows versions of these)
  • Tailscale (for simple and secure remote access to all Windows and Docker apps) (Windows version)

I’m not gonna pretend this is a super beginner friendly guide - some level of computer literacy is assumed and you will need to read specific guides for setting up the various apps to your liking. E.g., https://trash-guides.info/. This guide is more of an overview of setting up your system in a way that takes advantage of WSL2 while hopefully avoiding potential pitfalls.

Prerequisites and initial setup

  1. Install Windows 11, preferably on an NVME or SSD. You will also be using this drive as a download cache*, and to run your Docker containers. (*Or you can optionally use a dedicated SSD as the download cache, so it’s cheap and easy to replace if/when it wears out).

  2. Install/enable WSL2 on Windows. There are many guides online on how to do this. E.g. this one.

  3. Install Ubuntu 22.04.1 LTS (or go wild and pick another version, ymmv) from the Microsoft Store. Once installed, open a Ubuntu terminal and you’ll be prompted to setup an account. This account is separate from your Windows account. Remember the username and password. You should also be sure to check for any Ubuntu updates after installing. I recommend downloading the Windows Terminal app from the app store so you can access CMD/Powershell/Ubuntu terminal tabs in the one app.

  4. Enable systemd support in WSL2.

  5. Install Docker Desktop for Windows with the following options:

  • Settings > General:

    • Start Docker Desktop when you sign in to your computer;
    • Use the WSL 2 based engine;
    • Use containerd for pulling and storing images
  • Resources > Network:

    • Enable host networking;
    • Also note you can see your Docker subnet details here.
  • Resources > WSL Integration:

    • Enable integration with default WSL distro;
    • Enable integration with additional distros (select Ubuntu-24.04)
  1. If you don’t like the WSL2 defaults (see below), you can configure memory limits and swap size by creating a file called .wslconfig in your windows %userprofile% folder. I set these settings as follows, but it will depend on your available hardware.
# Note: Microsoft sets a default maximum RAM available to 50% of the physical memory and a swap-space that is 1/4 of the maximum WSL RAM.  You can scale those numbers up or down to allocate more or less RAM to the Linux instance.

# Settings apply across all Linux distros running on WSL 2
[wsl2]

# Limits VM memory to use no more than 8 GB, this can be set as whole numbers using GB or MB
memory=8GB

# Sets amount of swap storage space to 4GB
swap=4GB
  1. VERY IMPORTANT DOCKER SETTING - Edit %APPDATA%\Docker\settings.json and change vpnKitMaxPortIdleTime=300 to vpnKitMaxPortIdleTime=0 to prevent network connections to your docker apps being forcibly closed after 5 minutes, which can cause a lot of laggy behaviour and connection errors (ask me how I know!). See https://emerle.dev/2022/05/06/the-nasty-gotcha-in-docker/ for a more detailed explanation.

  2. Make sure you reboot before proceeding to ensure docker & wsl2 config changes are applied.

Folder structure (Windows file system)

The data folder/drive has sub-folders for torrents and usenet and each of these have sub-folders for tv, movie, books, software and music downloads to keep things neat.

Typically your data folder/drive will be a large capacity HDD, a NAS share, or some other for of cheap bulk storage. While this is usually fine for streaming video and other media content, if you have a fast gigabit internet connection, the drive write speeds will potentially be a significant bottleneck when downloading. Ideally, you want a decent NVME or SSD for the OS and also to act as a cache for incomplete downloads. Or you can optionally assign a dedicated ssd/nvme for the download cache since it will potentially have a LOT of reads and writes.

For this setup, you’ll want to keep all your completed Media on a windows NTFS drive/folder. We’ll be running Windows binaries for Jellyfin/Plex and they work best with NTFS. By running windows binaries for the Media servers, you’ll have no problems enabling hardware transcoding. Example setup below:

data (D:) [NTFS formatted Windows partition]
├── torrents
│   ├── books
│   ├── movies
│   ├── music
│   ├── software
│   └── tv
├── usenet
│   └── complete
│       ├── books
│       ├── movies
│       ├── music
│       ├── software
│       └── tv
└── media
    ├── books
    ├── movies
    ├── music
    ├── software
    └── tv

Folder structure (WSL2 file system)

You’ll want to keep all the application data from the Docker stack in the WSL2 filesystem, to eliminate the chance of data corruption and to improve disk r/w performance.

  1. Open an Ubuntu terminal which will land you in your home (~) folder. Note that Ubuntu uses an ext4 based filesystem in the WSL2 VM. It’s important to use this filesystem for your docker stack for best compatibility.
  2. Create a folder to store your docker stack data with mkdir arrstack or a name of your choice, and then setup a folder structure similar to the one below. Note that you may wish to locate the usenetcache and torrentcache folders on a discrete SSD/NVME if you have one available, to extend the lifepan of your boot drive.
~/arrstack [ext4 formatted Ubuntu partition]
├── config [used to store config folders for each of the docker apps]
│   ├── bazarr
│   ├── jellyseerr
│   ├── lidarr
│   ├── prowlarr
│   ├── qbittorrent
│   ├── radarr
│   ├── sabnzbd
│   └── sonarr
├── usenetcache
└── torrentcache

Docker compose

In an Ubuntu terminal:

  1. cd ~/arrstack
  2. Create a new docker-compose.yaml file in your ~/arrstack folder using the command notepad.exe docker-compose.yaml. Here’s my docker compose contents for reference. I’m not going to go through how to setup each app as there are many other guides for that:
services:
  gluetun: # Used to provide wireguard VPN for qbittorrent
    image: qmcgaw/gluetun:v3
    cap_add:
      - NET_ADMIN
    environment:
      - VPN_SERVICE_PROVIDER=protonvpn
      - VPN_PORT_FORWARDING_PROVIDER=protonvpn
      - VPN_TYPE=wireguard
      - WIREGUARD_PRIVATE_KEY=****************   # you'll need to lookup the relevant details for your VPN provider of choice
      - SERVER_COUNTRIES=Canada
      - PORT_FORWARD_ONLY=on
      - VPN_PORT_FORWARDING=on
      - TZ=America/Halifax
    ports:
      - 8090:8090
      - 6881:6881
      - 6881:6881/udp
    restart: unless-stopped
    volumes:
      - /etc/localtime:/etc/localtime:ro
  jellyseerr:
    image: fallenbagel/jellyseerr:latest
    container_name: jellyseerr
    environment:
      - LOG_LEVEL=debug
      - TZ=America/Halifax
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ~/arrstack/config/jellyseerr:/app/config   # all config data saved persistently in ext4 filesystem
    restart: unless-stopped
    ports:
      - 5055:5055
    depends_on:
      radarr:
        condition: service_started
      sonarr:
        condition: service_started
  radarr:
    container_name: radarr
    image: ghcr.io/hotio/radarr:latest
    restart: unless-stopped
    logging:
      driver: json-file
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/Halifax
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ~/arrstack/config/radarr:/config   # all config data saved persistently in ext4 filesystem
      - /mnt/d:/data                       # provides access to NTFS filesystem for completed media downloads
    ports:
      - 7878:7878
    depends_on:
      qbittorrent:
        condition: service_started
      sabnzbd:
        condition: service_started
  sonarr:
    container_name: sonarr
    image: ghcr.io/hotio/sonarr:latest
    restart: unless-stopped
    logging:
      driver: json-file
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/Halifax
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ~/arrstack/config/sonarr:/config   # all config data saved persistently in ext4 filesystem
      - /mnt/d:/data                       # provides access to NTFS filesystem for completed media downloads
    ports:
      - 8989:8989
    depends_on:
      qbittorrent:
        condition: service_started
      sabnzbd:
        condition: service_started
  bazarr:
    container_name: bazarr
    image: ghcr.io/hotio/bazarr:latest
    restart: unless-stopped
    logging:
      driver: json-file
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/Halifax
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ~/arrstack/config/bazarr:/config   # all config data saved persistently in ext4 filesystem
      - /mnt/d:/data                       # provides access to NTFS filesystem for completed media downloads
    ports:
      - 6767:6767
    depends_on:
      qbittorrent:
        condition: service_started
      sabnzbd:
        condition: service_started
  sabnzbd:
    container_name: sabnzbd
    image: ghcr.io/hotio/sabnzbd:latest
    restart: unless-stopped
    logging:
      driver: json-file
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/Halifax
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ~/arrstack/config/sabnzbd:/config   # all config data saved persistently in ext4 filesystem
      - ~/arrstack/usenetcache:/usenetcache # cache folder (or drive) for incomplete usenet downloads
      - /mnt/d/usenet:/data/usenet:rw       # provides access to NTFS filesystem for completed usenet downloads
    ports:
      - 8080:8080
      - 9090:9090
  lidarr:
    container_name: lidarr
    image: lscr.io/linuxserver/lidarr:latest
    restart: unless-stopped
    logging:
      driver: json-file
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/Halifax
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ~/arrstack/config/lidarr:/config   # all config data saved persistently in ext4 filesystem
      - /mnt/d:/data
    ports:
      - 8686:8686
    depends_on:
      qbittorrent:
        condition: service_started
      sabnzbd:
        condition: service_started
  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent
    restart: unless-stopped
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/Halifax
      - WEBUI_PORT=8090
      - TORRENTING_PORT=6881   # for example
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ~/arrstack/config/qbittorrent:/config   # all config data saved persistently in ext4 filesystem
      - ~/arrstack/torrentcache:/torrentcache   # cache folder (or drive) for incomplete torrent downloads
      - /mnt/d/torrents:/data/torrents:rw       # provides access to NTFS filesystem for completed torrent downloads
    network_mode: "service:gluetun"   # torrents go through VPN network connection - also set the network interface in qbittorrent's advanced settings to "tun0" when you configure the app
    depends_on:
      gluetun:
        condition: service_healthy
  prowlarr:
    image: lscr.io/linuxserver/prowlarr:latest
    container_name: prowlarr
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/Halifax
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ~/arrstack/config/prowlarr:/config   # all config data saved persistently in ext4 filesystem
    ports:
      - 9696:9696
    restart: unless-stopped
    depends_on:
      flaresolverr:
        condition: service_started
      qbittorrent:
        condition: service_started
      sabnzbd:
        condition: service_started
  flaresolverr:
    image: ghcr.io/flaresolverr/flaresolverr:latest
    container_name: flaresolverr
    environment:
      - LOG_LEVEL=${LOG_LEVEL:-info}
      - LOG_HTML=${LOG_HTML:-false}
      - CAPTCHA_SOLVER=${CAPTCHA_SOLVER:-none}
      - TZ=America/Halifax
      - HEADLESS=true
    volumes:
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "${PORT:-8191}:8191"
    restart: unless-stopped
  1. Once you have saved the docker-compose.yaml file, run docker compose pull to download all the relevant docker images, then docker compose up -d to spin them up. Once you’ve done this the first time, you can open Docker Desktop in Windows to manage your containers & images, access logs etc, using the GUI interface.

Completing your setup

  1. Install the Windows versions of Jellyfin and/or Plex, using the data (D:) [NTFS formatted Windows partition] for all your media libraries. They don’t need any access to your WSL ext4 partitions. If you wish to use Plex remote access you will need to port forward port 32400 from your router to your PC. However you don’t need to open any other ports since we will be using Tailscale for simplicity.

  2. Setup a free Tailscale account if you haven’t already got one. Install Tailscale on your media server PC and also on any other devices you wish to use to access your media server remotely. Once tailscale is installed, you will be able to use your machine name (in my case media-pc) to connect to your apps remotely using the following links, without needing to do any port forwarding:

Tailscale remote links:

Plex        http://media-pc:32400/web/index.html#!/ or you can use https://app.plex.tv/ if you have forwarded port 32400
Jellyfin    http://media-pc:8096/web/#/home.html
Jellyseerr  http://media-pc:5055/
Sonarr      http://media-pc:8989/
Radarr      http://media-pc:7878/
Lidarr      http://media-pc:8686/
Bazarr      http://media-pc:6767/
SABnzbd     http://media-pc:8080/
qBittorrent http://media-pc:8090/
Prowlarr    http://media-pc:9696/

localhost links:

Plex        localhost:32400/web/index.html#!/
Jellyfin    localhost:8096/web/#/home.html
Jellyseerr  localhost:5055/
Sonarr      localhost:8989/
Radarr      localhost:7878/
Lidarr      localhost:8686/
Bazarr      localhost:6767/
SABnzbd     localhost:8080/
qBittorrent localhost:8090/
Prowlarr    localhost:9696/

Note that if you are accessing your media using tailscale, your browser may complain about the lack of a secure connection, however behind the scenes your tailscale connection is in fact fully secured with TLS and a wireguard VPN connection. On Firefox, you can click the padlock icon and set automatically upgrade this site to a secure connection to off for each link so you don’t get a security warning each time you connect.

  1. If you wish to share your media library with other people, you can either add them to your tailscale network (beyond the scope of this guide) or simply give them the https://app.plex.tv/ link and relevant login details if you have forwarded port 32400.

Auto-starting Docker containers on windows boot

I like to have this automated so that on reboot all the docker containers/images spin up. Unfortunately there is no option to do this automatically in Docker Desktop. There are a couple of ways to do this, but this is the way I have got working reliably:

  1. Create folder C:\Users\windows-username\startup-scripts, substituting windows-username with your Windows username.
  2. Create a powershell script docker_compose_up.ps1 in this folder with contents as per below, substituting ubuntu-username below with your Ubuntu username. This script will load the WSL2 VM, then spin up your docker container using your previously created docker-compose.yaml configuration file:
wsl ~ -u ubuntu-username -d Ubuntu-24.04 -e sh -c "docker compose -f ~/arrstack/docker-compose.yaml up -d"
  1. Create a .vbs script called launcher.vbs in this folder with content as per below. The only purpose of this file is to allow you to silently launch your powershell script created in step 2 using a scheduled task, without having to install any 3rd party apps. It’s a bit hacky but it works great.
On Error Resume Next

ReDim args(WScript.Arguments.Count-1)

For i = 0 To WScript.Arguments.Count-1
    If InStr(WScript.Arguments(i), " ") > 0 Then
        args(i) = Chr(34) & WScript.Arguments(i) & Chr(34)
    Else
        args(i) = WScript.Arguments(i)
        End If

Next

CreateObject("WScript.Shell").Run Join(args, " "), 0, False
  1. Open windows task scheduler and create a new task called start docker containers
  2. Set the task to trigger at log on of your windows user
  3. Set the action to Start a program with program/script of Wscript.exe with arguments //B "launcher.vbs" powershell.exe -ExecutionPolicy ByPass -file "docker-compose-up.ps1" and start in C:\Users\windows-username\startup-scripts

Some reflections on this setup

While obviously this setup isn’t for everyone, and is by no means a recommended way to setup your *arr stack, if you enjoy working with Windows or have Windows-specific hardware/drivers you want to use then this is a viable alternative to running a Linux server. My personal motivation for doing this was to get 5.1 surround sound audio working reliably - something I’ve tried and failed to do on Ubuntu several times due to my own incompetence linux driver issues with my particular Soundblaster S/PDIF audio configuration (and the fact my TV doesn’t support 5.1 HDMI ARC passthrough).

If anyone can suggest any improvements or errors in this guide, then I’d be very happy to hear from you! I’m not pretending to be an authority on any of this - but this setup works well for me and hopefully there’s some info that other folks will find useful.

Finally, I hope I haven’t missed any important steps (as I’m writing this guide from memory, rather than as I was setting things up) but if you run into any issues or notice a missing step then please let me know so I can edit the guide.

  • PerogiBoi
    link
    fedilink
    English
    arrow-up
    2
    ·
    19 days ago

    I commend ya for the write up though. Would have been insanely helpful back when I was using WSL2 to emulate a raspberry pi to run Pihole 🤣