The Ultimate *Arr Stack Guide for Kubernetes: Radarr, Jackett & qBittorrent

In the previous post, we covered the architecture and how all the pieces fit together. Now it’s time to get our hands dirty.

This post is the engine room of your automated media pipeline:

  • qBittorrent: The muscle (downloads files).
  • Jackett: The translator (standardizes search results).
  • Radarr: The brain (orchestrates everything).

By the end, you’ll have a working pipeline that can search, download, and organize movies automatically.

The Ultimate *Arr Stack Guide for Kubernetes: Radarr, Jackett & qBittorrent

Prerequisites

Before we start, make sure you have:

  • A running Kubernetes cluster (in my case, K3s)
  • kubectl configured
  • A storage solution for app configs (we’ll use Longhorn)
  • NFS storage for media files (or any ReadWriteMany storage)
  • Basic understanding of Kubernetes concepts (Pods, Services, PVCs)

The Golden Rule: Shared Storage (don’t skip this)

The #1 cause of failure in *arr stacks is inconsistent Path Mapping. If Radarr and qBittorrent do not see the exact same files at the exact same internal mount paths, imports will fail or result in slow, space-wasting file copies across virtual drives.

The Goal: Atomic Moves (Instant Imports).

To achieve this, apps must share underlying storage and mount it identically.

The Strategy: We will use shared, ReadWriteMany (RWX) PVCs mounted at the same locations across containers:

  • /downloads: The staging area shared between qBittorrent (write) and Radarr (read/write).
  • /movies: The final library destination shared between Radarr (write) and Jellyfin (read).

👉 Keep these paths consistent and you will save hours of debugging.

Project Structure

The Ultimate *Arr Stack Guide for Kubernetes: Radarr, Jackett & qBittorrent

We will use Kustomize to keep manifests clean and modular. This structure makes debugging easy: if Radarr breaks, you know exactly which folder to check.

media-server/├── namespace.yaml├── storage/│   ├── nfs-downloads-pv-pvc.yaml│   ├── nfs-movies-pv-pvc.yaml├── qbitt/│   ├── kustomization.yaml│   ├── qbitt-config-pvc.yaml│   ├── qbitt-sts.yaml│   └── qbitt-svc.yaml├── jackett/│   ├── kustomization.yaml│   ├── jackett-config-pvc.yaml│   ├── jackett-deploy.yaml│   └── jackett-svc.yaml└── radarr/    ├── kustomization.yaml    ├── radarr-config-pvc.yaml    ├── radarr-sts.yaml    └── radarr-svc.yaml

Step 1: Bootstrap Namespace & Storage

First, create the namespace and the shared volumes for your media.

1. Create Namespace

# namespace.yamlapiVersion: v1kind: Namespacemetadata:  name: media
kubectl apply -f namespace.yaml

2. Create Storage (Pick ONE option)

Option A: NFS (Recommended for NAS users) Replace *192.168.1.xx* with your NAS IP.

# storage/nfs-downloads-pv-pvc.yamlapiVersion: v1kind: PersistentVolumemetadata:  name: media-downloadsspec:  capacity:    storage: 200Gi  accessModes:    - ReadWriteMany  # Multiple pods can write  nfs:    path: /volume1/downloads  # Your NFS export path    server: 192.168.1.xx       # Your NAS IP  persistentVolumeReclaimPolicy: Retain  storageClassName: ""---apiVersion: v1kind: PersistentVolumeClaimmetadata:  name: media-downloads  namespace: mediaspec:  accessModes:    - ReadWriteMany  resources:    requests:      storage: 200Gi  volumeName: media-downloads  storageClassName: ""
# storage/nfs-movies-pv-pvc.yamlapiVersion: v1kind: PersistentVolumemetadata:  name: media-moviesspec:  capacity:    storage: 500Gi  accessModes:    - ReadWriteMany  nfs:    path: /volume1/movies    server: 192.168.1.xx  persistentVolumeReclaimPolicy: Retain  storageClassName: ""---apiVersion: v1kind: PersistentVolumeClaimmetadata:  name: media-movies  namespace: mediaspec:  accessModes:    - ReadWriteMany  resources:    requests:      storage: 500Gi  volumeName: media-movies  storageClassName: ""
kubectl apply -f storage/

Option B: Longhorn (Local Cluster Storage)

If you don’t have a NAS, you can use Longhorn with ReadWriteMany (requires Longhorn RWX support):

# storage/longhorn-downloads-pvc.yamlapiVersion: v1kind: PersistentVolumeClaimmetadata:  name: media-downloads  namespace: mediaspec:  accessModes:    - ReadWriteMany  storageClassName: longhorn  resources:    requests:      storage: 200Gi---# storage/longhorn-movies-pvc.yamlapiVersion: v1kind: PersistentVolumeClaimmetadata:  name: media-movies  namespace: mediaspec:  accessModes:    - ReadWriteMany  storageClassName: longhorn  resources:    requests:      storage: 500Gi
kubectl apply -f storage/

Step 2: Deploy qBittorrent

qBittorrent is the download client in this stack. It is the “muscle” that handles the actual downloading of files based on instructions from Radarr. It manages the torrent queue, seeding, and overall download process.

qBittorrent needs a dedicated PVC for its configuration and access to the shared media-downloads PVC.

The Ultimate *Arr Stack Guide for Kubernetes: Radarr, Jackett & qBittorrent

The Manifests

1. PVC & Service

# qbitt/qbitt-config-pvc.yamlapiVersion: v1kind: PersistentVolumeClaimmetadata:  name: qbitt-config  namespace: mediaspec:  accessModes:    - ReadWriteOnce  storageClassName: longhorn # or your default class  resources:    requests:      storage: 1Gi---# qbitt/qbitt-svc.yamlapiVersion: v1kind: Servicemetadata:  name: qbitt  namespace: mediaspec:  type: ClusterIP  selector:    app: qbitt  ports:    - name: web      port: 80      targetPort: 8080    - name: torrent-tcp      port: 6881      targetPort: 6881

2. StatefulSet

# qbitt/qbitt-sts.yamlapiVersion: apps/v1kind: StatefulSetmetadata:  name: qbitt  namespace: mediaspec:  serviceName: qbitt  replicas: 1  selector:    matchLabels:      app: qbitt  template:    metadata:      labels:        app: qbitt    spec:      containers:        - name: qbitt          image: linuxserver/qbittorrent:latest          env:            - name: PUID              value: "1000"            - name: PGID              value: "1000"            - name: WEBUI_PORT              value: "8080"          ports:            - containerPort: 8080              name: web            - containerPort: 6881              name: torrent-tcp            - containerPort: 6881              protocol: UDP              name: torrent-udp          volumeMounts:            - name: config              mountPath: /config            - name: downloads              mountPath: /downloads          resources:            requests:              memory: "512Mi"              cpu: "100m"            limits:              memory: "2Gi"              cpu: "1000m"          livenessProbe:            httpGet:              path: /              port: 8080            initialDelaySeconds: 60            periodSeconds: 30          readinessProbe:            httpGet:              path: /              port: 8080            initialDelaySeconds: 30            periodSeconds: 10      volumes:        - name: config          persistentVolumeClaim:            claimName: qbitt-config        - name: downloads          persistentVolumeClaim:            claimName: media-downloads

Deploy: kubectl apply -k qbitt/

Configuration

  • Forward port: kubectl port-forward -n media svc/qbitt 8080:80
  • Log in: “admin” is the username and check logs for temp password (kubectl logs -n media sts/qbitt).
  • Critical: Set “Default Save Path” to /downloads.
  • Keep incomplete torrents in: /dowloads/incomplete

The Ultimate *Arr Stack Guide for Kubernetes: Radarr, Jackett & qBittorrent

Step 3: Deploy Jackett

Jackett acts as the “translator” in our pipeline. It functions as a proxy between Radarr and your torrent indexers. It translates Radarr’s standardized queries into searches across multiple different torrent sites and presents the results back to Radarr in a unified format.

The Ultimate *Arr Stack Guide for Kubernetes: Radarr, Jackett & qBittorrent

The Manifests

1. PVC & Service

# jackett/jackett-config-pvc.yamlapiVersion: v1kind: PersistentVolumeClaimmetadata:  name: jackett-config  namespace: mediaspec:  accessModes:    - ReadWriteOnce  storageClassName: longhorn  resources:    requests:      storage: 1Gi---# jackett/jackett-svc.yamlapiVersion: v1kind: Servicemetadata:  name: jackett  namespace: mediaspec:  selector:    app: jackett  ports:    - port: 80      targetPort: 9117

2. Deployment

# jackett/jackett-deploy.yamlapiVersion: apps/v1kind: Deploymentmetadata:  name: jackett  namespace: mediaspec:  replicas: 1  selector:    matchLabels:      app: jackett  template:    metadata:      labels:        app: jackett    spec:      containers:        - name: jackett          image: linuxserver/jackett:latest          env:            - name: PUID              value: "1000"            - name: PGID              value: "1000"            - name: TZ              value: "Europe/Madrid"          ports:            - containerPort: 9117          volumeMounts:            - name: config              mountPath: /config          resources:            requests:              memory: "128Mi"              cpu: "50m"            limits:              memory: "512Mi"              cpu: "500m"          livenessProbe:            tcpSocket:              port: 9117            initialDelaySeconds: 60            periodSeconds: 30          readinessProbe:            tcpSocket:              port: 9117            initialDelaySeconds: 30            periodSeconds: 10      volumes:        - name: config          persistentVolumeClaim:            claimName: jackett-config

Deploy: kubectl apply -k jackett/

Configuration

  1. Access UI: kubectl port-forward -n media svc/jackett 9117:80
  2. Add a few indexers (e.g., 1337x, RARBG).
  3. Copy the API Key from the top right corner. You need this for Radarr.

💡 Tip: Start with 2–3 indexers. More isn’t always better and can slow down searches.

Step 4: Deploy Radarr

Radarr acts as the “brain” and orchestrator of the entire media pipeline. It manages your movie watchlist, monitors for upcoming releases, and instructs Jackett to find sources. Once found, Radarr sends download instructions to qBittorrent and finally imports, renames, and organizes the completed file into your permanent library.

👉 Note: Radarr mounts both shared volumes so it can perform atomic moves of files from the staging area (/downloads) to the final library (/movies).

The Ultimate *Arr Stack Guide for Kubernetes: Radarr, Jackett & qBittorrent

The Manifests

1. PVC & Service

# radarr/radarr-config-pvc.yamlapiVersion: v1kind: PersistentVolumeClaimmetadata:  name: radarr-config  namespace: mediaspec:  accessModes:    - ReadWriteOnce  resources:    requests:      storage: 2Gi---# radarr/radarr-svc.yamlapiVersion: v1kind: Servicemetadata:  name: radarr  namespace: mediaspec:  selector:    app: radarr  ports:    - name: web      port: 80      targetPort: 7878

2. StatefulSet

# radarr/radarr-sts.yamlapiVersion: apps/v1kind: StatefulSetmetadata:  name: radarr  namespace: mediaspec:  serviceName: radarr  replicas: 1  selector:    matchLabels:      app: radarr  template:    metadata:      labels:        app: radarr    spec:      containers:        - name: radarr          image: linuxserver/radarr:latest          env:            - name: PUID              value: "1000"            - name: PGID              value: "1000"            - name: TZ              value: "Europe/Madrid"          ports:            - containerPort: 7878          volumeMounts:            - name: config              mountPath: /config            - name: movies              mountPath: /movies            - name: downloads              mountPath: /downloads          resources:            requests:              memory: "256Mi"              cpu: "100m"            limits:              memory: "1Gi"              cpu: "1000m"          startupProbe:            httpGet:              path: /ping              port: 7878            initialDelaySeconds: 30            periodSeconds: 10            failureThreshold: 30          livenessProbe:            httpGet:              path: /ping              port: 7878            periodSeconds: 30          readinessProbe:            httpGet:              path: /ping              port: 7878            periodSeconds: 10      volumes:        - name: config          persistentVolumeClaim:            claimName: radarr-config        - name: movies          persistentVolumeClaim:            claimName: media-movies        - name: downloads          persistentVolumeClaim:            claimName: media-downloads

Deploy: kubectl apply -k radarr/

Step 5: Wire Everything Together

Now we connect the components using internal Kubernetes DNS.

Access Radarr (http://localhost:7878 via port-forward).

5.1 Add Download Client (qBittorrent)

  • SettingsDownload Clients+qBittorrent
  • Name: qBittorrent
  • Host: qbitt.media.svc.cluster.local (This is the K8s Service name)
  • Port: 80 (Service port)
  • Credentials: Admin / (Your Password)
  • Test & Save.

The Ultimate *Arr Stack Guide for Kubernetes: Radarr, Jackett & qBittorrent

💡 We use the Kubernetes DNS name `qbitt.media.svc.cluster.local` so Radarr can reach qBittorrent within the cluster.

5.2 Add Indexers (via Jackett)

  • SettingsIndexers+Torznab
  • URL: [http://jackett.media.svc.cluster.local/api/v2.0/indexers/YOUR\_INDEXER/results/torznab/](http://jackett.media.svc.cluster.local/api/v2.0/indexers/YOUR_INDEXER/results/torznab/)
  • API Key: Paste from Jackett.
  • Test & Save.

The Ultimate *Arr Stack Guide for Kubernetes: Radarr, Jackett & qBittorrent

💡 Finding the Torznab URL: In Jackett, click the ”Copy Torznab Feed” button next to each indexer. Replace the host with the Kubernetes service name.

5.3 Set Root Folder

The Ultimate *Arr Stack Guide for Kubernetes: Radarr, Jackett & qBittorrent

  • SettingsMedia ManagementAdd Root Folder
  • Select /movies.

5.4 Set Up Quality Profiles (Optional but Recommended)

The Ultimate *Arr Stack Guide for Kubernetes: Radarr, Jackett & qBittorrent

  • Go to Settings → Profiles
  • Edit the default profile or create a new one.
  • Select which qualities you want (1080p, 4K, etc.).
  • Set upgrade rules.

Step 6: Test the Pipeline

Let’s verify everything works:

  1. Add a Movie: Go to Radarr, search for a movie, set the path to /movies, and click "Add".
  2. Verify Search: Check Radarr’s “History” or “Search” tab to see it querying Jackett.
  3. Verify Download: Check qBittorrent; the torrent should appear in the /downloads folder.
  4. Verify Import: Once finished, the file should disappear from the qBittorrent queue and appear in your /movies folder (and Radarr library).

Troubleshooting

“No indexers available”

  • Verify Jackett is running: `kubectl get pods -n media -l app=jackett`
  • Check the Jackett URL in Radarr uses the Kubernetes service name
  • Verify the API key is correct

“Download client unavailable”

  • Verify qBittorrent is running: `kubectl get pods -n media -l app=qbitt`
  • Check the host is `qbitt.media.svc.cluster.local` (not localhost)
  • Verify username/password

“Import failed: file not found”

This is the path mismatch problem. Verify:

  • qBittorrent saves to `/downloads`
  • Radarr has `/downloads` mounted
  • Both use the same PVC for downloads

Check with:

# In qBittorrent podkubectl exec -n media sts/qbitt - ls -la /downloads# In Radarr podkubectl exec -n media sts/radarr - ls -la /downloads

Both should show the same files.

“Connection refused” errors

Check if pods are in different nodes and can reach each other:

# From Radarr, test connection to qBittorrentkubectl exec -n media sts/radarr - curl -v http://qbitt.media.svc.cluster.local

Verify Your Deployment

# Check all pods are runningkubectl get pods -n media# Expected output:# NAME                      READY   STATUS    RESTARTS   AGE# qbitt-0                   1/1     Running   0          10m# jackett-xxxxxxxxx-xxxxx   1/1     Running   0          10m# radarr-0                  1/1     Running   0          10m# Check serviceskubectl get svc -n media# Expected output:# NAME      TYPE        CLUSTER-IP      PORT(S)# qbitt     ClusterIP   10.43.x.x       80/TCP# jackett   ClusterIP   10.43.x.x       80/TCP# radarr    ClusterIP   10.43.x.x       80/TCP

What’s Next

In Part 2, we’ll add the user-facing components:

  • Jellyfin: to stream your movies
  • Jellyseerr: so users can request content
  • Bazarr: for automatic subtitles

See you in the next post! 🚀

Thanks for reading!

💡 Want more hands‑on tips about Kubernetes, Cloud, and DevOps?
👉 Follow me here on Medium and let’s connect on LinkedIn!