This guide covers deploying PrunArr to Kubernetes using the official Helm chart.
- Kubernetes cluster (v1.19+)
- Helm 3.x installed
- kubectl configured for your cluster
- API credentials for Radarr, Sonarr, and Tautulli
Option 1: From OCI Registry (Recommended)
# Install directly from GitHub Container Registry
helm install prunarr oci://ghcr.io/haijeploeg/charts/prunarr \
--version 1.0.0 \
--set config.radarr.apiKey="your-radarr-api-key" \
--set config.radarr.url="https://radarr.example.com" \
--set config.sonarr.apiKey="your-sonarr-api-key" \
--set config.sonarr.url="https://sonarr.example.com" \
--set config.tautulli.apiKey="your-tautulli-api-key" \
--set config.tautulli.url="https://tautulli.example.com"Note: Chart versions are automatically generated from git tags in CI/CD. Browse available versions at ghcr.io/hploeg/charts/prunarr
Option 2: From Local Chart
# Clone the repository
git clone https://github.com/hploeg/prunarr.git
cd prunarr
# Install from local chart
helm install prunarr ./helm/prunarr \
--set config.radarr.apiKey="your-radarr-api-key" \
--set config.radarr.url="https://radarr.example.com" \
--set config.sonarr.apiKey="your-sonarr-api-key" \
--set config.sonarr.url="https://sonarr.example.com" \
--set config.tautulli.apiKey="your-tautulli-api-key" \
--set config.tautulli.url="https://tautulli.example.com"PrunArr supports two deployment modes:
Automatically runs cleanup tasks on a schedule.
helm install prunarr ./helm/prunarr \
--set mode=cronjob \
--set config.radarr.apiKey="..." \
--set config.sonarr.apiKey="..." \
--set config.tautulli.apiKey="..." \
# ... other configDefault Schedules:
- Movies cleanup: Daily at 2 AM (
0 2 * * *) - Series cleanup: Daily at 3 AM (
0 3 * * *) - Cache refresh: Disabled (enable with
cronjobs.cacheRefresh.enabled=true)
Long-running pod for interactive kubectl exec commands.
helm install prunarr ./helm/prunarr \
--set mode=deployment \
--set config.radarr.apiKey="..." \
# ... other config
# Run commands interactively
kubectl exec -it deployment/prunarr -- prunarr movies list --limit 10Create a values.yaml file:
# values.yaml
mode: cronjob
image:
repository: ghcr.io/haijeploeg/prunarr
tag: "1.0.0"
config:
radarr:
apiKey: "your-radarr-api-key"
url: "https://radarr.example.com"
sonarr:
apiKey: "your-sonarr-api-key"
url: "https://sonarr.example.com"
tautulli:
apiKey: "your-tautulli-api-key"
url: "https://tautulli.example.com"
cache:
enabled: true
maxSizeMb: 100
logging:
level: ERROR
cronjobs:
movies:
enabled: true
schedule: "0 2 * * *" # 2 AM daily
command: ["movies", "remove", "--watched", "--days-watched", "60", "--force"]
series:
enabled: true
schedule: "0 3 * * *" # 3 AM daily
command: ["series", "remove", "--watched", "--days-watched", "60", "--force"]
persistence:
enabled: true
size: 1Gi
resources:
limits:
cpu: 1000m
memory: 512Mi
requests:
cpu: 250m
memory: 128MiInstall with your values:
helm install prunarr ./helm/prunarr -f values.yamlFor production, use external secret management:
# values.yaml
externalSecrets:
enabled: true
secretName: "prunarr-api-keys" # Pre-existing secret
keys:
radarrApiKey: "radarr-api-key"
sonarrApiKey: "sonarr-api-key"
tautulliApiKey: "tautulli-api-key"Create the external secret:
kubectl create secret generic prunarr-api-keys \
--from-literal=radarr-api-key="your-radarr-api-key" \
--from-literal=sonarr-api-key="your-sonarr-api-key" \
--from-literal=tautulli-api-key="your-tautulli-api-key"Or use a secret operator like External Secrets Operator:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: prunarr-api-keys
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: prunarr-api-keys
data:
- secretKey: radarr-api-key
remoteRef:
key: secret/radarr
property: api_key
- secretKey: sonarr-api-key
remoteRef:
key: secret/sonarr
property: api_key
- secretKey: tautulli-api-key
remoteRef:
key: secret/tautulli
property: api_keyCustomize CronJob schedules:
cronjobs:
movies:
schedule: "0 */6 * * *" # Every 6 hours
series:
schedule: "30 2 * * 0" # Sundays at 2:30 AM
cacheRefresh:
enabled: true
schedule: "0 */4 * * *" # Every 4 hoursAdjust based on your library size:
resources:
limits:
cpu: 2000m # 2 CPU cores
memory: 1Gi # 1 GB RAM
requests:
cpu: 500m # 0.5 CPU cores
memory: 256Mi # 256 MB RAMUse specific storage class:
persistence:
enabled: true
storageClass: "fast-ssd" # Your storage class
size: 5Gi
accessMode: ReadWriteOnceOr use existing PVC:
persistence:
enabled: true
existingClaim: "prunarr-cache-pvc"Run on specific nodes:
nodeSelector:
disktype: ssd
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.kubernetes.io/worker
operator: In
values:
- "true"Run on tainted nodes:
tolerations:
- key: "dedicated"
operator: "Equal"
value: "media"
effect: "NoSchedule"# List all CronJobs
kubectl get cronjobs -l app.kubernetes.io/instance=prunarr
# View specific CronJob
kubectl describe cronjob prunarr-movies
# View CronJob logs
kubectl logs -l app.kubernetes.io/instance=prunarr,prunarr.io/job-type=movies# List recent jobs
kubectl get jobs -l app.kubernetes.io/instance=prunarr
# View specific job logs
kubectl logs job/prunarr-movies-28934567
# Get job status
kubectl describe job prunarr-movies-28934567Trigger a job manually:
# Movies cleanup
kubectl create job --from=cronjob/prunarr-movies manual-movies-$(date +%s)
# Series cleanup
kubectl create job --from=cronjob/prunarr-series manual-series-$(date +%s)
# Cache refresh
kubectl create job --from=cronjob/prunarr-cache-refresh manual-cache-$(date +%s)# List movies
kubectl exec -it deployment/prunarr -- prunarr movies list --limit 10
# Dry run removal
kubectl exec -it deployment/prunarr -- prunarr movies remove --watched --days-watched 60 --dry-run
# Cache status
kubectl exec -it deployment/prunarr -- prunarr cache statusEnable debug logging:
helm upgrade prunarr ./helm/prunarr \
--reuse-values \
--set config.logging.level=DEBUGView logs:
# CronJob logs
kubectl logs -l prunarr.io/job-type=movies --tail=100
# Deployment logs
kubectl logs deployment/prunarr -f
# Specific pod
kubectl logs prunarr-movies-28934567-abcde# Update values
helm upgrade prunarr ./helm/prunarr -f values.yaml
# Upgrade with new image tag
helm upgrade prunarr ./helm/prunarr \
--reuse-values \
--set image.tag="1.1.0"# View history
helm history prunarr
# Rollback to previous version
helm rollback prunarr
# Rollback to specific revision
helm rollback prunarr 3# Uninstall release
helm uninstall prunarr
# Also delete PVC (if needed)
kubectl delete pvc prunarr-cacheDeploy to different namespaces:
# Production
helm install prunarr-prod ./helm/prunarr \
--namespace production \
--create-namespace \
-f values-prod.yaml
# Staging
helm install prunarr-staging ./helm/prunarr \
--namespace staging \
--create-namespace \
-f values-staging.yamlAdd ServiceMonitor for metrics (if using Prometheus Operator):
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: prunarr
spec:
selector:
matchLabels:
app.kubernetes.io/name: prunarr
endpoints:
- port: metrics
interval: 30sExample PrometheusRule for failed jobs:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: prunarr-alerts
spec:
groups:
- name: prunarr
interval: 5m
rules:
- alert: PrunArrJobFailed
expr: kube_job_status_failed{job_name=~"prunarr-.*"} > 0
for: 10m
labels:
severity: warning
annotations:
summary: "PrunArr job {{ $labels.job_name }} failed"Check schedule:
kubectl get cronjob prunarr-movies -o jsonpath='{.spec.schedule}'Check suspended status:
kubectl get cronjob prunarr-movies -o jsonpath='{.spec.suspend}'
# Should be falseView failed job logs:
kubectl logs -l job-name=prunarr-movies-28934567 --tail=100Check job events:
kubectl describe job prunarr-movies-28934567Test connectivity from pod:
kubectl exec -it deployment/prunarr -- wget -O- https://radarr.example.com/api/v3/system/statusCheck PVC status:
kubectl get pvc prunarr-cache
kubectl describe pvc prunarr-cacheVerify cache directory:
kubectl exec -it deployment/prunarr -- ls -la /home/prunarr/.prunarr/cache- Use External Secrets - Don't store API keys in values.yaml
- Enable Persistence - Cache improves performance significantly
- Set Resource Limits - Prevent resource exhaustion
- Monitor Jobs - Set up alerts for failed jobs
- Test with Dry Run - Always test with
--dry-runfirst - Separate Environments - Use different namespaces for prod/staging
- Version Lock - Pin image tags, don't use
latest - Regular Backups - Backup your configuration and secrets
mode: cronjob
externalSecrets:
enabled: true
secretName: prunarr-secrets
config:
radarr:
url: "https://radarr.example.com"
sonarr:
url: "https://sonarr.example.com"
tautulli:
url: "https://tautulli.example.com"
cronjobs:
movies:
enabled: true
schedule: "0 2 * * *"
series:
enabled: true
schedule: "0 3 * * *"
persistence:
enabled: true
storageClass: "fast-ssd"
size: 5Gi
resources:
limits:
cpu: 1000m
memory: 512Mi
requests:
cpu: 250m
memory: 128Mi- Docker Guide - Docker deployment options
- Configuration Guide - Detailed configuration
- Quick Start Guide - Common workflows