Skip to content
Coming soon

Kubernetes

On Kubernetes, every sensitive setting lives in a Secret resource and reaches the Deployment through secretKeyRef, so no sensitive values appear in the Deployment manifest itself.


Create the sovereign-secrets Secret before the Deployment. Keep this file outside of version control, or use a secrets management tool (see the tip below).

apiVersion: v1
kind: Secret
metadata:
name: sovereign-secrets
type: Opaque
stringData:
admin-username: admin
admin-password: Admin1!
asset-security-key: <32-char-secret>
radarr-api-key: <radarr-api-key>
sonarr-api-key: <sonarr-api-key>

Or create it from the command line, without a manifest file:

Terminal window
kubectl create secret generic sovereign-secrets \
--from-literal=admin-username=admin \
--from-literal=admin-password=Admin1! \
--from-literal=asset-security-key=<32-char-secret> \
--from-literal=radarr-api-key=<radarr-api-key> \
--from-literal=sonarr-api-key=<sonarr-api-key>

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: sovereign-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 256Gi # transcodes are packaged here well ahead of playback; see System Requirements
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sovereign
labels:
app: sovereign
spec:
replicas: 1
strategy:
type: Recreate # one SQLite database on one volume: never run two pods at once
selector:
matchLabels:
app: sovereign
template:
metadata:
labels:
app: sovereign
spec:
securityContext:
fsGroup: 1654 # the non-root user Sovereign runs as
containers:
- name: sovereign
image: <sovereign-image>:<version>
ports:
- containerPort: 5000
readinessProbe:
httpGet:
path: /health
port: 5000
env:
# Non-sensitive configuration - declared inline
# The public HTTPS address your ingress serves, as users type it
- name: Sovereign__Authentication__Issuer
value: 'https://media.example.com/'
# Your cluster's pod network, where the ingress controller's requests come from
- name: Sovereign__Network__TrustedProxyNetwork__0
value: '10.244.0.0/16'
- name: Sovereign__DataSources__Radarr__0__Url
value: 'http://radarr:7878'
- name: Sovereign__DataSources__Sonarr__0__Url
value: 'http://sonarr:8989'
# Sensitive values - injected from the sovereign-secrets Secret
- name: Sovereign__Authentication__AdminUsername
valueFrom:
secretKeyRef:
name: sovereign-secrets
key: admin-username
- name: Sovereign__Authentication__AdminPassword
valueFrom:
secretKeyRef:
name: sovereign-secrets
key: admin-password
- name: Sovereign__Obfuscation__AssetSecuritySecretKey
valueFrom:
secretKeyRef:
name: sovereign-secrets
key: asset-security-key
- name: Sovereign__DataSources__Radarr__0__ApiKey
valueFrom:
secretKeyRef:
name: sovereign-secrets
key: radarr-api-key
- name: Sovereign__DataSources__Sonarr__0__ApiKey
valueFrom:
secretKeyRef:
name: sovereign-secrets
key: sonarr-api-key
volumeMounts:
- name: sovereign-data
mountPath: /sovereign-data
- name: media
mountPath: /media
readOnly: true
volumes:
- name: sovereign-data
persistentVolumeClaim:
claimName: sovereign-data
- name: media
hostPath: # single-node/dev only - use NFS CSI driver for multi-node clusters
path: /path/to/media
---
apiVersion: v1
kind: Service
metadata:
name: sovereign
spec:
selector:
app: sovereign
ports:
- port: 5000
targetPort: 5000

Apply both files:

Terminal window
kubectl apply -f sovereign-secrets.yaml
kubectl apply -f sovereign.yaml

Replace <sovereign-image> and <version> with the image and the release you’re deploying, as the Docker page describes. Sovereign doesn’t terminate TLS, so route your ingress to the sovereign Service on port 5000 and set the issuer to the address the ingress serves. Left at a localhost address, nobody outside the pod can sign in. The ingress controller reaches the pod from an address in your cluster’s pod network, so set the trusted proxy network to that range (10.244.0.0/16 is a common default, and k3s uses 10.42.0.0/16). Without it, every user is recorded as the ingress controller and they all share one sign-in rate limit; see Trusted Proxy Networks. /health answers without authentication once startup has finished, which is what the readiness probe waits for.

The container runs as a non-root user (UID 1654), and Sovereign creates its directories on the data volume when it starts. fsGroup makes the volume writable by that user on storage that supports it. Without it, a volume that mounts owned by root stops the server at startup with an access-denied error.


Kubernetes can’t provision NFS volumes dynamically on its own, so install the NFS CSI Driver for Kubernetes in your cluster before using an NFS share as a PersistentVolume. The NFS Subdir External Provisioner is an alternative if the CSI driver doesn’t suit your environment.

Once the CSI driver is running, replace the hostPath volume in the Deployment with a PersistentVolumeClaim backed by an NFS StorageClass:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-media
provisioner: nfs.csi.k8s.io
parameters:
server: <nfs-server-ip>
share: /path/to/media
reclaimPolicy: Retain
volumeBindingMode: Immediate
mountOptions:
- nfsvers=4.1
- ro
- soft
- timeo=100
- retrans=2
- rsize=1048576
- wsize=1048576
- nconnect=4
- actimeo=60
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: sovereign-media
spec:
storageClassName: nfs-media
accessModes:
- ReadOnlyMany
resources:
requests:
storage: 1Ti

Then update the volumes section of the Deployment:

volumes:
- name: sovereign-data
persistentVolumeClaim:
claimName: sovereign-data
- name: media
persistentVolumeClaim:
claimName: sovereign-media

The mountOptions above are explained option by option on the NFS Media Storage page, along with why the share should be mounted read-only and made to fail fast.


See Hardware Acceleration for the resource limit snippets that expose an NVIDIA or Intel GPU to the container in Kubernetes.

© 2026 Sovereign Media LLCdocs/v1.0.5@98b57ac305fc

Titles, artwork, people and descriptions shown in screenshots are fictional and were created for illustration. Sovereign doesn't include or supply any media: it plays the library you bring.