Skip to content

Jellyfin - Setting up the entire stack

This post will guide you to setting up your own Jellyfin *arr stack with VPN. That means an automated pipeline and the privacy of proton vpn. If you want the full Proton experience, check out Proton Pass.

Step 1: Setting up Jellyfin

The easy part. Let's go ahead and set up Jellyfin. If you have a movie file already, this as great time to go ahead and test the indexing of Jellyfin.

docker-compose.yml
jellyfin:
  image: jellyfin/jellyfin
  container_name: jellyfin
  restart: unless-stopped
  volumes:
    - ./jellyfin-config:/config
    - /mnt/md0/apps/jellyfin/media:/media
  ports:
    - 8700:8096
  devices:
    - /dev/dri:/dev/dri  # For hardware acceleration (Intel/AMD)
  environment:
    - TZ=America/Denver

I only changed the mapped port because I'm already using 8096.

Note

When creating your jellyfin library, I created a separate media and downloads folder. Inside of media, go ahead and create folders for audiobooks, movies, music, tv, etc. Whatever types of media you have.

In the Dashboard settings in JellyFin, add whatever libraries you want Jellyfin to pickup, i.e. Movies, Shows. The folder should be /media/movies, or /media/tv.

Setting up the Stack and VPN

Now that we understand the Jellyfin bind mounts, let's go ahead and add the entire *arr stack along with gluetun and qBitTorrent.

docker-compose.yml
services:
  jellyfin:
    image: jellyfin/jellyfin
    container_name: jellyfin
    user: 1000:1000
    ports:
      - 8096:8096/tcp
      # - 7359:7359/udp 
    volumes:
      - ./jellyfin-config:/config
      - ./jellyfin-cache:/cache
      - ${DATA_LOCATION}:/data
    restart: 'unless-stopped'
    environment:
      - TZ=${TIMEZONE}
      - /dev/dri:/dev/dri 

  gluetun:
    image: qmcgaw/gluetun
    container_name: gluetun
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun
    ports: # Expose qBittorrent's web UI and torrent ports through VPN
      # - 8700:8096   # Jellyfin
      - 8701:8701   # qBittorrent Web UI
      - 6881:6881   # torrent port
      - 6881:6881/udp
      - 7878:7878   # Radarr
      - 8989:8989   # Sonarr
      - 8686:8686   # Lidarr
      - 6767:6767   # Bazarr
      - 9696:9696   # Prowlarr
      - 8191:8191   # FlareSolverr
      # - 5055:5055   # Jellyseerr
    volumes:
      - ./gluetun:/gluetun
      # - ./gluetun/us-den.conf:/gluetun/wireguard/wg0.conf
    environment:
      - VPN_TYPE=openvpn
      - VPN_SERVICE_PROVIDER=protonvpn
      - OPENVPN_USER=${OPENVPN_USER}
      - OPENVPN_PASSWORD=${OPENVPN_PASSWORD}
      - VPN_PORT_FORWARDING=on
      - SERVER_COUNTRIES=United States
      - TZ=${TIMEZONE}
  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent
    container_name: qbittorrent
    network_mode: "service:gluetun"  # Routes all traffic through Gluetun
    depends_on:
      - gluetun
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=${TIMEZONE}
      - WEBUI_PORT=8701
      - WEBUI_ADDRESS=0.0.0.0
      - WEBUI_EXTERNAL_ACCESS=true
    volumes:
      - ./qbittorrent:/config
      - ${DATA_LOCATION}:/data
    restart: unless-stopped

  radarr:
    image: lscr.io/linuxserver/radarr
    container_name: radarr
    restart: unless-stopped
    network_mode: "service:gluetun"  # Routes all traffic through Gluetun
    depends_on:
      - gluetun
    # ports:
    #   - 7878:7878
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=${TIMEZONE}
    volumes:
      - ./radarr:/config
      - ${DATA_LOCATION}:/data

  sonarr:
    image: lscr.io/linuxserver/sonarr
    container_name: sonarr
    restart: unless-stopped
    network_mode: "service:gluetun"  # Routes all traffic through Gluetun
    depends_on:
      - gluetun
    # ports:
    #   - 8989:8989
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=${TIMEZONE}
    volumes:
      - ./sonarr:/config
      - ${DATA_LOCATION}:/data

  lidarr:
    image: lscr.io/linuxserver/lidarr:latest
    container_name: lidarr
    network_mode: "service:gluetun"  # Routes all traffic through Gluetun
    depends_on:
      - gluetun
    # ports:
    #   - 8686:8686
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=${TIMEZONE}
    volumes:
      - ./lidarr:/config
      - ${DATA_LOCATION}:/data
    restart: unless-stopped

  bazarr:
    image: lscr.io/linuxserver/bazarr
    container_name: bazarr
    restart: unless-stopped
    network_mode: "service:gluetun"  # Routes all traffic through Gluetun
    depends_on:
      - gluetun
    # ports:
    #   - 6767:6767
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=${TIMEZONE}
    volumes:
      - ./bazarr:/config
      - ${DATA_LOCATION}:/data

  prowlarr:
    image: lscr.io/linuxserver/prowlarr
    container_name: prowlarr
    restart: unless-stopped
    network_mode: "service:gluetun"  # Routes all traffic through Gluetun
    depends_on:
      - gluetun
    # ports:
    #   - 9696:9696
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=${TIMEZONE}
    volumes:
      - ./prowlarr:/config

  flaresolverr:
    image: ghcr.io/flaresolverr/flaresolverr:latest
    container_name: flaresolverr
    network_mode: "service:gluetun"  # Routes all traffic through Gluetun
    environment:
      - TZ=${TIMEZONE}
    depends_on:
      - gluetun
    # ports:
    #   - 8191:8191
    restart: unless-stopped

  jellyseerr:
    image: fallenbagel/jellyseerr:latest
    container_name: jellyseerr
    environment:
      - TZ=${TIMEZONE}   
      - LOG_LEVEL=info
    ports:
      - "5055:5055"
    volumes:
      - ./jellyseerr:/app/config
    restart: unless-stopped
    depends_on:
      - gluetun
      - jellyfin
      - sonarr
      - radarr

So in the .env we have the media location, download location, and timezone like so

.env
DATA_LOCATION=/mnt/md0/apps/jellyfin/data
TIMEZONE=America/Denver
OPENVPN_USER=
OPENVPN_PASSWORD=

The port forwarding only works using the OpenVPN method. You can find the values for your OPENVPN_USER and OPENVPN_PASSWORD by navigating to https://account.proton.me/u/0/vpn/OpenVpnIKEv2.

You can now take the entire stack up.

docker compose up -d

Go ahead and check the health of your stack, particularly the Gluetun container. In the logs, you should see something like the following:

2025-10-30T15:42:36-06:00 INFO [ip getter] Public IP address is <IP_ADDRESS>

At this point, you can also try a quick curl command from within the qbittorrent container to make sure the IP address of that container is indeed the same.

docker exec -it qbittorrent curl ifconfig.me

Setting up qBitTorrent

Before Radarr or Sonarr can send any jobs to qBitTorrent, we need to configure qBitTorrent.

qBitTorrent seems to be the one service that does need to be connected to with https when connecting from outside of your server. Set up a reverse proxy and DNS entry to access qBitTorrent over https.

To login the first time, check the docker logs which should provide a username and password. Otherwise, try admin and adminadmin.

Now is another time for a sanity check to make sure qBitTorrent is indeed using the VPN. Head to https://ipleak.net/ and go to Torrent Address detection. Click on Activate and right click and copy the Magnent Link. Then in qBitTorrent, add a Torrent from a link and paste that in. Back on ipleak.net it should show a connection from your VPN IP address. If that's the case, you're good to go.

Set up download directories

Go to Tools -> Options -> Downloads

Make sure that the Default save path is /downloads.

Whatever sub folders you created in your jellyfin bind mount, let's create categories for each of these. This way each of our services that send jobs to qBitTorrent can include a category and that way jellyfin indexes it properly. On the left menu, expand Categories, right click on All, and choose Add category...

For movies, set the save path to downloads/movies. Go ahead and do this music, tv, audiobooks.

Setting up Prowlarr

Prowlarr is a tracking container, it's needs by each of the *arr services to find an appropriate tracker for media and send it to qBitTorrent.

Before we add an Indexer, let's go ahead and set up a connection to flaresolverr. This will be needed for some of the Indexers that area behind Cloudflare bot detection. Go to Settings -> Indexers. Click the + button and choose FlareSolverr. You shold be able to set the host to http://localhost:8191/. Set the tag to cloudflare. Test the connection and save.

Let's go ahead and add an Indexer. 1337x tends to be quite reliable. If the indexer requires the flaresolverr just make sure to add the cloudflare tag to the indexer, then test the connection.

Note that the other *arr apps will want an api key to talk to Prowlarr. This will be located in Settings -> General -> Security -> API Key. Likewise, for reverse communication, you can generate keys in each of the *arr apps and add each app to Settings -> Apps. If you do the latter, than Indexers will automatically be added to your *arr apps which I prefer.

Setting up Radarr

First set the root folder in Media Management. Click the add root folder, and set it to /media/movies. I'd go ahead and set it to rename movies too so that it's standardized.

Connecting to Prowlar for Auto-Sync

Head over to your Radarr web app. We need to set up the connection to both qBitTorrent and Prowlarr. Following up on the previous section, make sure you go get Radarr's api key and add Radarr as an app in Prowlarr. So inside Prowlarr, hit the plus button to add an app. Call it Radarr, sync level Full Sync, Prowlarr server should be http://localhost:9696, Radarr server should be set to http://localhost:7878. Add the api key from Radarr and make sure the sync categories are set to Movies and whatever subcategories. This way only movie trackers will get synced to Radarr.

In Radarr we should now see any Indexers we added in Prowlarr.

Connecting to qBitTorrent

Now go to Settings -> Download Clients, click the plus sign and add qBittorrent. The host can be set to localhost and port set to 8080 (remember, these are all internally connected on the same network). Use your qBittorrent Username and Password. Set the category to movies.

You are now ready to download a movie in the Movies section! When you download a movie, check your qBittorrent and make sure the donwloading is working. If you see the status as Halted, then it's likely that the port forwarding configuration did not work with your VPN.

Sonarr Setup

Ok, this will be pretty identical to setting up Radaar. Let's go to Media Management, allow it to rename episodes, and set the root folder to /media/tv.

Add the qBitTorrent client. Set the category to tv.

Go get the API key (Settings -> General -> API Key), then go to Prowlaar and add Sonarr as an app in Settings -> Apps.

Comments