Hybrid cloud cluster monorepo with Ansible, K8s, NixOS, and Terraform. cute.haus
ansible terraform nix k8s
0

Configure Feed

Select the types of activity you want to include in your feed.

move plex to k8s

Aly Raffauf (Jun 24, 2026, 11:37 PM EDT) 115b1b7f 0ad289cf

+254 -10
+1 -5
k8s/charts/external-routes/values.yaml
··· 2 2 3 3 # Each route gets a Service + Endpoints + Ingress. 4 4 # Service name == route name. Tls secret must exist in the same namespace as the Ingress (default). 5 - routes: 6 - - name: plex 7 - host: plex.cute.haus 8 - target: { ip: 100.81.61.31, port: 32400 } # jubilife 9 - tls: { secretName: cute-haus-tls } 5 + routes: []
+5
k8s/charts/plex/Chart.yaml
··· 1 + apiVersion: v2 2 + name: plex 3 + description: Plex media server 4 + type: application 5 + version: 0.1.0
+100
k8s/charts/plex/templates/deployment.yaml
··· 1 + apiVersion: apps/v1 2 + kind: Deployment 3 + metadata: 4 + name: {{ .Chart.Name }} 5 + labels: 6 + app: {{ .Chart.Name }} 7 + spec: 8 + replicas: 1 9 + strategy: 10 + type: Recreate 11 + selector: 12 + matchLabels: 13 + app: {{ .Chart.Name }} 14 + template: 15 + metadata: 16 + labels: 17 + app: {{ .Chart.Name }} 18 + spec: 19 + terminationGracePeriodSeconds: 30 20 + enableServiceLinks: false 21 + # hostNetwork so GDM auto-discovery, DLNA and the router-forwarded 22 + # remote-access port (32400) all reach Plex directly on jubilife. 23 + hostNetwork: true 24 + dnsPolicy: ClusterFirstWithHostNet 25 + nodeSelector: 26 + kubernetes.io/hostname: jubilife 27 + tolerations: 28 + - key: node.kubernetes.io/not-ready 29 + operator: Exists 30 + effect: NoExecute 31 + tolerationSeconds: 60 32 + - key: node.kubernetes.io/unreachable 33 + operator: Exists 34 + effect: NoExecute 35 + tolerationSeconds: 60 36 + containers: 37 + - name: {{ .Chart.Name }} 38 + image: docker.io/plexinc/pms-docker:1.43.2.10687-563d026ea@sha256:c37106c57fed7a6624f5dee5a3ce460ff011f09a2aa7f4ee9e8dbbd08ae1b87e 39 + imagePullPolicy: IfNotPresent 40 + env: 41 + - name: TZ 42 + value: "America/New_York" 43 + # Must match the owner of /mnt/Data/plex (chown during cutover). 44 + - name: PLEX_UID 45 + value: "1000" 46 + - name: PLEX_GID 47 + value: "1000" 48 + - name: ADVERTISE_IP 49 + value: "https://plex.cute.haus:443" 50 + ports: 51 + - name: pms 52 + containerPort: 32400 53 + resources: 54 + requests: 55 + cpu: 200m 56 + memory: 512Mi 57 + gpu.intel.com/i915: "1" # HW transcode — requires Plex Pass 58 + limits: 59 + cpu: "4" 60 + memory: 4Gi 61 + gpu.intel.com/i915: "1" 62 + volumeMounts: 63 + - name: config 64 + mountPath: /config 65 + - name: media 66 + mountPath: /mnt/Media 67 + readOnly: true 68 + - name: backblaze 69 + mountPath: /mnt/Backblaze 70 + readOnly: true 71 + mountPropagation: HostToContainer 72 + startupProbe: 73 + httpGet: 74 + path: /identity 75 + port: pms 76 + periodSeconds: 10 77 + failureThreshold: 30 78 + livenessProbe: 79 + httpGet: 80 + path: /identity 81 + port: pms 82 + periodSeconds: 30 83 + readinessProbe: 84 + httpGet: 85 + path: /identity 86 + port: pms 87 + periodSeconds: 10 88 + failureThreshold: 3 89 + volumes: 90 + - name: config 91 + persistentVolumeClaim: 92 + claimName: {{ .Chart.Name }}-config 93 + - name: media 94 + persistentVolumeClaim: 95 + claimName: {{ .Chart.Name }}-media 96 + readOnly: true 97 + - name: backblaze 98 + hostPath: 99 + path: /mnt/Backblaze 100 + type: Directory
+23
k8s/charts/plex/templates/ingress.yaml
··· 1 + apiVersion: networking.k8s.io/v1 2 + kind: Ingress 3 + metadata: 4 + name: {{ .Chart.Name }} 5 + labels: 6 + app: {{ .Chart.Name }} 7 + spec: 8 + ingressClassName: traefik 9 + tls: 10 + - hosts: 11 + - "plex.cute.haus" 12 + secretName: cute-haus-tls 13 + rules: 14 + - host: "plex.cute.haus" 15 + http: 16 + paths: 17 + - path: / 18 + pathType: Prefix 19 + backend: 20 + service: 21 + name: {{ .Chart.Name }} 22 + port: 23 + number: 80
+83
k8s/charts/plex/templates/pvc.yaml
··· 1 + apiVersion: v1 2 + kind: PersistentVolume 3 + metadata: 4 + name: {{ .Chart.Name }}-config 5 + labels: 6 + app: {{ .Chart.Name }} 7 + spec: 8 + capacity: 9 + storage: 50Gi 10 + accessModes: 11 + - ReadWriteOnce 12 + persistentVolumeReclaimPolicy: Retain 13 + storageClassName: "" 14 + nodeAffinity: 15 + required: 16 + nodeSelectorTerms: 17 + - matchExpressions: 18 + - key: kubernetes.io/hostname 19 + operator: In 20 + values: 21 + - jubilife 22 + hostPath: 23 + path: /mnt/Data/plex 24 + type: Directory 25 + --- 26 + apiVersion: v1 27 + kind: PersistentVolumeClaim 28 + metadata: 29 + name: {{ .Chart.Name }}-config 30 + labels: 31 + app: {{ .Chart.Name }} 32 + annotations: 33 + helm.sh/resource-policy: keep 34 + spec: 35 + accessModes: 36 + - ReadWriteOnce 37 + storageClassName: "" 38 + volumeName: {{ .Chart.Name }}-config 39 + resources: 40 + requests: 41 + storage: 50Gi 42 + --- 43 + apiVersion: v1 44 + kind: PersistentVolume 45 + metadata: 46 + name: {{ .Chart.Name }}-media 47 + labels: 48 + app: {{ .Chart.Name }} 49 + spec: 50 + capacity: 51 + storage: 14Ti 52 + accessModes: 53 + - ReadOnlyMany 54 + persistentVolumeReclaimPolicy: Retain 55 + storageClassName: "" 56 + nodeAffinity: 57 + required: 58 + nodeSelectorTerms: 59 + - matchExpressions: 60 + - key: kubernetes.io/hostname 61 + operator: In 62 + values: 63 + - jubilife 64 + hostPath: 65 + path: /mnt/Media 66 + type: Directory 67 + --- 68 + apiVersion: v1 69 + kind: PersistentVolumeClaim 70 + metadata: 71 + name: {{ .Chart.Name }}-media 72 + labels: 73 + app: {{ .Chart.Name }} 74 + annotations: 75 + helm.sh/resource-policy: keep 76 + spec: 77 + accessModes: 78 + - ReadOnlyMany 79 + storageClassName: "" 80 + volumeName: {{ .Chart.Name }}-media 81 + resources: 82 + requests: 83 + storage: 14Ti
+14
k8s/charts/plex/templates/service.yaml
··· 1 + apiVersion: v1 2 + kind: Service 3 + metadata: 4 + name: {{ .Chart.Name }} 5 + labels: 6 + app: {{ .Chart.Name }} 7 + spec: 8 + type: ClusterIP 9 + selector: 10 + app: {{ .Chart.Name }} 11 + ports: 12 + - name: http 13 + port: 80 14 + targetPort: 32400
+6
k8s/helmfile.yaml
··· 288 288 needs: 289 289 - default/cluster-tls 290 290 291 + - name: plex 292 + namespace: default 293 + chart: ./charts/plex 294 + needs: 295 + - default/cluster-tls 296 + 291 297 - name: navidrome 292 298 namespace: default 293 299 chart: ./charts/navidrome
+22 -4
nix/hosts/jubilife.nix
··· 24 24 k3s-node 25 25 lanzaboote 26 26 locale-en-us 27 - plex 28 27 podman 29 28 prometheus-node 30 29 qbittorrent ··· 132 131 zone = "home"; 133 132 }; 134 133 135 - myPlex.dataDir = "/mnt/Data"; 136 - 137 134 mySyncthing = { 138 135 certFile = config.sops.secrets.syncthingCert.path; 139 136 keyFile = config.sops.secrets.syncthingKey.path; ··· 167 164 "d /mnt/Data/arm/config 0755 1000 1000 - -" 168 165 "d /mnt/Data/arm 0755 1000 1000 - -" 169 166 "d /mnt/Data/jellyfin 0700 1000 1000 - -" 167 + "d /mnt/Data/plex 0755 1000 1000 - -" 170 168 ]; 171 169 172 170 virtualisation.oci-containers.containers = { ··· 288 286 jellyfin = { 289 287 paths = ["${dataDirectory}/jellyfin"]; 290 288 }; 289 + 290 + plex = { 291 + exclude = ["${dataDirectory}/plex/Library/Application Support/Plex Media Server/Plug-in Support/Databases"]; 292 + paths = ["${dataDirectory}/plex"]; 293 + }; 291 294 }; 292 295 293 296 sops = { ··· 300 303 }; 301 304 302 305 networking.firewall = { 303 - allowedTCPPorts = [6881]; 306 + allowedTCPPorts = 307 + [6881] 308 + ++ [ 309 + # Plex (hostNetwork): PMS, Companion, HTTPS 310 + 32400 311 + 8324 312 + 32443 313 + ]; 314 + allowedUDPPorts = [ 315 + # Plex (hostNetwork): DLNA + GDM auto-discovery 316 + 1900 317 + 32410 318 + 32412 319 + 32413 320 + 32414 321 + ]; 304 322 extraInputRules = '' 305 323 -s ${k3sPodCidr} -p tcp --dport 2049 -j ACCEPT 306 324 -s ${k3sPodCidr} -p udp --dport 2049 -j ACCEPT
-1
nix/hosts/snowpoint.nix
··· 15 15 k3s-node 16 16 locale-en-us 17 17 media-share 18 - plex 19 18 prometheus-node 20 19 swap 21 20 syncthing