Skip to content

Install phlix-server on Kubernetes โ€‹

TL;DR โ€‹

phlix-server is a PHP 8.3+ media server with HLS streaming, WebSocket real-time sync, DLNA, and a Smarty web portal. This guide deploys it on Kubernetes via Helm in roughly 10 minutes.

Minimum requirements: Kubernetes 1.21+, Helm 3.8+, a default or named StorageClass, 2 CPU / 4 GB RAM per pod.

Quick one-liner:

bash
helm repo add phlix https://charts.phlix.media && helm repo update
helm install phlix phlix/phlix \
  --set config.database_password=SECRET \
  --set config.secret_key=YOUR_KEY \
  --set ingress.enabled=true \
  --set ingress.hosts[0].host=phlix.example.com

Then open https://phlix.example.com in your browser.

Screenshots TBD

This guide is text-first. Screenshots will be added in a follow-up.


1. Prerequisites โ€‹

ComponentMinimum versionNotes
Kubernetes1.21+
Helm3.8+
Ingress controllernginx-ingress or Traefikwith cert-manager for automated TLS
StorageClassdefault or namedRequired for PVCs
NVIDIA GPU (optional)Driver 525+For hardware transcoding
MySQL (optional)External or in-clusterOr use the chart's embedded DB

2. Add the Helm repository โ€‹

bash
helm repo add phlix https://charts.phlix.media
helm repo update
helm search repo phlix/phlix   # confirm latest chart version

3. Minimal values.yaml โ€‹

yaml
replicaCount: 1

image:
  repository: ghcr.io/detain/phlix-server
  pullPolicy: IfNotPresent
  tag: "latest"   # pin to a specific release tag in production

ingress:
  enabled: true
  className: "nginx"
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "86400"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "86400"
    nginx.ingress.kubernetes.io/upstream-hdrs: "Upgrade"
    nginx.ingress.kubernetes.io/websocket-services: "phlix-websocket"
  hosts:
    - host: phlix.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: phlix-tls
      hosts:
        - phlix.example.com

resources:
  requests:
    cpu: 500m
    memory: 512Mi
  limits:
    cpu: 2000m
    memory: 2Gi

persistence:
  media:
    enabled: true
    storageClass: ""        # uses default StorageClass; set to "nfs" or "local-path" if needed
    size: 100Gi
    readOnly: true
  data:
    enabled: true
    storageClass: ""
    size: 10Gi
  config:
    enabled: true
    storageClass: ""
    size: 1Gi

config:
  database_host: "mysql.default.svc.cluster.local"
  database_port: 3306
  database_name: phlix
  database_user: phlix
  database_password: "REPLACE_WITH_STRONG_PASSWORD"
  secret_key: "REPLACE_WITH_32_CHAR_KEY"
  log_level: info

# Optional: GPU node scheduling for hardware transcoding
nodeSelector:
  gpu: "nvidia"

tolerations:
  - key: "nvidia.com/gpu"
    operator: "Exists"
    effect: "NoSchedule"

Save as values.yaml and install with:

bash
helm install phlix phlix/phlix -f values.yaml

4. Required PersistentVolumeClaims โ€‹

The Helm chart creates three PVCs automatically:

bash
kubectl get pvc | grep phlix
PVC namePurposeDefault sizeAccess mode
phlix-mediaMedia files (read-only mount)100 GiReadWriteOnce
phlix-dataApplication data (DB, watch history)10 GiReadWriteOnce
phlix-configConfig directory1 GiReadWriteOnce

StorageClass: If your cluster has no default StorageClass, you must set persistence.media.storageClass explicitly (e.g., local-path, nfs, cephfs). Using a StorageClass that supports ReadWriteMany (e.g., NFS) is required for the media PVC to be mounted read-only by multiple pods.


5. Service type โ€‹

5a. ClusterIP (default โ€” requires Ingress) โ€‹

yaml
service:
  type: ClusterIP
  http:
    port: 80

Access via Ingress at https://phlix.example.com.

5b. LoadBalancer โ€‹

yaml
service:
  type: LoadBalancer
  http:
    port: 80

Exposes phlix directly on a cloud LB. For on-premises, MetalLB can provide this.

5c. NodePort โ€‹

yaml
service:
  type: NodePort
  http:
    port: 80
    nodePort: 32400

Access at http://<any-node-ip>:32400. Not recommended for production.


6. Ingress annotations โ€‹

yaml
ingress:
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "86400"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "86400"
    # WebSocket proxying
    nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
    nginx.ingress.kubernetes.io/upstream-hdrs: "Upgrade"
    nginx.ingress.kubernetes.io/websocket-services: "phlix-websocket"
    nginx.ingress.kubernetes.io/use-regex: "true"

Traefik โ€‹

yaml
ingress:
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    traefik.ingress.kubernetes.io/router.entrypoints: "websecure"
    traefik.ingress.kubernetes.io/router.http-services: "phlix-http"
    traefik.ingress.kubernetes.io/router.headers.customrequestheaders: "Upgrade: websocket"

If using Traefik's IngressRoute CRD instead of plain Ingress, see the Traefik docs.


7. Environment variables โ€‹

The chart passes these to the pod automatically via PHLIX_* env vars:

Env varDescriptionExample
PHLIX_DATABASE_HOSTMySQL hostmysql.default.svc.cluster.local
PHLIX_DATABASE_PORTMySQL port3306
PHLIX_DATABASE_NAMEDatabase namephlix
PHLIX_DATABASE_USERDatabase userphlix
PHLIX_DATABASE_PASSWORDDatabase passwordfrom Kubernetes Secret
PHLIX_SECRET_KEYJWT/signing keyfrom Kubernetes Secret
PHLIX_LOG_LEVELLog verbosityinfo, debug
PHLIX_HTTP_PORTInternal HTTP port80

Set passwords/keys via the chart's secrets mechanism (required):

bash
helm install phlix phlix/phlix \
  --set config.database_password=STRONG_PASSWORD \
  --set config.secret_key=YOUR_32_CHAR_SECRET

Or pre-create a Kubernetes Secret and reference it in values.yaml.


8. GPU node scheduling (NVIDIA) โ€‹

For hardware-accelerated transcoding on NVIDIA GPUs:

bash
# Install the NVIDIA device plugin (one-time per cluster)
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.5/nvidia-device-plugin.yml

Then in values.yaml:

yaml
nodeSelector:
  nvidia.com/gpu: "true"

tolerations:
  - key: "nvidia.com/gpu"
    operator: "Exists"
    effect: "NoSchedule"

The container automatically detects and uses NVENC/NVDEC when available.


9. Helm upgrade process โ€‹

When a new chart or image version is released:

bash
# Update chart repo
helm repo update

# Check what would change
helm diff upgrade phlix phlix/phlix -f values.yaml

# Apply the upgrade
helm upgrade phlix phlix/phlix -f values.yaml

# Roll back if needed
helm rollback phlix

For zero-downtime upgrades, the chart uses RollingUpdate strategy with maxSurge: 1 and maxUnavailable: 0. Ensure readinessProbe is properly configured (it is by default).

To update only the Docker image tag:

bash
helm upgrade phlix phlix/phlix --set image.tag=v1.2.3

What can go wrong โ€‹

PVC pending โ€” storage class not found โ€‹

  • Symptom: kubectl get pvc shows all PVCs Pending
  • Cause: Cluster has no default StorageClass, or the named StorageClass (nfs, cephfs, etc.) does not exist
  • Fix: Check available StorageClasses: kubectl get storageclass. Then set it explicitly in values.yaml:
    yaml
    persistence:
      media:
        storageClass: "local-path"
  • Verify: kubectl describe pvc <name> shows Waiting for a volume to be created either by the external provisioner

OOMKilled โ€” memory limit too low โ€‹

  • Symptom: Pod is OOMKilled shortly after starting, especially during first-run metadata fetch or FFmpeg probe
  • Cause: Default memory limit of 2Gi may be insufficient for libraries with large watch histories or concurrent transcoding
  • Fix: Increase memory limits in values.yaml:
    yaml
    resources:
      limits:
        memory: 4Gi
      requests:
        memory: 1Gi
  • Verify: kubectl top pod phlix-xxxxxxxxx (requires metrics-server) or check kubectl describe pod for Last State: Terminated, Reason: OOMKilled

Ingress 502 โ€” ingress controller not found or WebSocket misconfiguration โ€‹

  • Symptom: HTTP requests return 502, or WebSocket connections fail immediately
  • Cause 1: No ingress controller is installed in the cluster
    • Fix: Install nginx-ingress: helm install ingress-nginx ingress-nginx/ingress-nginx --namespace ingress-nginx --create-namespace
  • Cause 2: WebSocket annotations missing from Ingress (required for the WebSocket port 3473)
    • Fix: Ensure the ingress annotations include the WebSocket proxy directives listed in ยง6
  • Verify: kubectl describe ingress phlix-xxxx shows backend services correctly; check nginx-ingress logs: kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx

Next steps โ€‹

BSD-3-Clause