Plex Media Server runs in the cluster as the upstream Helm chart plex-media-server, reconciled by Argo CD Application plex. The Application is multi-source: companion manifests from homelabs/plex/manifests, Helm values from $homelabs/plex/values.yaml and $private/clusters/<cluster>/overlays/plex/values.yaml, and the chart itself. The workload is a Helm StatefulSet named plex.
Access is on the home network. Public values leave the Service as ClusterIP so the shared tree stays generic; the private overlay turns it into a MetalLB LoadBalancer and sets the advertise URL. Cloudflare is not part of this path. Jellyfin on the same cluster keeps ClusterIP and uses that tunnel instead; do not copy this LoadBalancer overlay onto Application jellyfin.
Site values#
Public homelabs/plex/values.yaml stays generic: Service type ClusterIP, the claim Secret name only, a default PLEX_SERVER_NAME, and the extra volume that mounts the media claim.
Site values belong in clusters/<cluster>/overlays/plex/values.yaml. The overlay sets the Service to LoadBalancer on port 32400, pins service.loadBalancerIP to "<loadBalancerIP>", and sets extraEnv.ADVERTISE_IP to http://<loadBalancerIP>:32400. Repeat PLEX_SERVER_NAME in the overlay so a Helm map merge cannot drop the public extraEnv key. Other site facts (domain, inventory, NAS coordinates) stay in homelabs-private/clusters/<cluster>/config/cluster.yaml or a local runbook.
Claim token#
The Plex claim token lives in Vault at secret/homelab/plex (property PLEX_CLAIM). External Secrets materializes it as Secret plex-env in namespace plex. Tokens from https://www.plex.tv/claim/ expire in a few minutes, so write the value when you are ready to sync:
vault kv put secret/homelab/plex PLEX_CLAIM=your_claim_tokenConfirm the ExternalSecret and the target Secret exist:
kubectl get externalsecret -n plex
kubectl get secret plex-env -n plexDo not commit the token or a filled *secret*.yaml. Cluster token setup is on the Vault and External Secrets pages.
GitOps#
Application plex already points at the public values file plus the private overlay. Sync order uses waves: hashicorp-vault, then external-secrets-operator, then plex. Register the stack once, then let Argo create and reconcile the namespace, the Longhorn config claim, the ExternalSecret, and the Helm release.
kubectl apply -k argocd
kubectl apply -f clusters/<cluster>/argocd/app-of-apps.yamlApply only the Plex Application if you need to register it on its own:
kubectl apply -f clusters/<cluster>/argocd/applications/plex.yamlCheck that the Application is present and healthy:
kubectl -n argocd get applications
kubectl -n argocd get application plexDay-to-day changes go through Git and an Argo sync. The public tree no longer ships plex/deployment.yaml. Do not recreate a Deployment or Service named plex-k3s.
Storage#
Config state lives on claim plex-config-pvc-lh (longhorn-ssd), GitOps-managed via homelabs/plex/manifests/pvc-config.yaml and Helm configExistingClaim. Media libraries stay on plex-media-pvc (nfs-csi). That media claim already exists on the cluster and is not in Git, so Argo must not recreate or prune it; there is no plex/pvc.yaml to apply. Keep the library on NFS; do not migrate media to Longhorn.