Today, we will convert a Docker Compose file into Kubernetes objects using Jellyfin as an example.

Initially, the Docker Compose file looked like this:

version: '3.5'
services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    hostname: jellyfin
    user: '1000:1000'
    volumes:
      - /storage/config:/config
      - /storage/cache:/cache
      - /storage/media:/media
    restart: 'unless-stopped'
    ports:
      - 8096:8096
    extra_hosts:
      - "host.docker.internal:host-gateway"

Based on the Compose file, we will need the following Kubernetes objects: • Namespace • Deployment: For managing the pod with the application. • ClusterIP Service: To make the application accessible within the cluster. • Ingress: To make the application accessible via a domain name.

Namespace and Deployment

Namespace can be created by one command:

$ kubectl create namespace jellyfin

Next we need to create the core manifest for deployment:

$ kubectl -n jellyfin create deploy jellyfin --image=jellyfin/jellyfin:10.10.3

Now we have the deployment.yaml file

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: jellyfin
  name: jellyfin
  namespace: jellyfin
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jellyfin
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: jellyfin
    spec:
      containers:
      - image: jellyfin/jellyfin:10.10.3
        name: jellyfin
        resources: {}
status: {}

Ports, Environment Variables, Volumes and Volume Mounts need to be added to the deployment in container section like this:

containers:
- name: jellyfin
  image: jellyfin/jellyfin:10.10.3
  ports:
  - containerPort: 8096 # port for accessing webui
    name: http-tcp
    protocol: TCP

  env:
  - name: JELLYFIN_PublishedServerUrl
    value: "http://<domain>:80" # change to your domain
  - name: PGID
    value: "1000"
  - name: PUID
    value: "1000"
  - name: TZ
    value: "Asia/Almaty" # change to your timezone

  volumeMounts:
  - mountPath: "/config" # path to config 
    name: jellyfin-config
  - mountPath: "/cache" # path to cache
    name: jellyfin-cache
  - mountPath: "/media" # path to media
    name: media
  
  resources: {}

Also volumes need to be added under spec

spec:
...
  volumes:
	- name: jellyfin-config
	  hostPath:
		path: /storage/jellyfin/config # path on the server
	- name: jellyfin-cache
	  hostPath:
		path: /storage/jellyfin/cache # path on the server
	- name: media
	  hostPath:
		path: /storage/media # path on the server

For this example, I am using hostPath, which utilizes the host node’s filesystem for storing data and accessing media. If you need to specify the exact path to the media files, don’t forget to include it in the media volume path.

If you have more than one node and your media files are attached to a specific machine, you can add affinity properties to the pod spec:

spec:
  ...
  affinity:
	nodeAffinity:
	  requiredDuringSchedulingIgnoredDuringExecution:
		nodeSelectorTerms:
		  - matchExpressions:
			- key: kubernetes.io/hostname
			  operator: In
			  values:
				- <node name> # specify the node name where you want pod to run
  ...

So, the complete deployment manifest will look like this:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: jellyfin
  name: jellyfin
  namespace: jellyfin
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jellyfin
  strategy: {}
  template:
    metadata:
      labels:
        app: jellyfin
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          nodeSelectorTerms:
            - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
              - <node name> # specify the node name where you want pod to run
      containers:
      - image: jellyfin/jellyfin:10.10.3 # version can be changed 
        name: jellyfin
        resources: {}
        ports:
        - containerPort: 8096 # port for accessing webui
          name: http-tcp
          protocol: TCP

        env:
        - name: JELLYFIN_PublishedServerUrl
          value: "http://<domain>:80" # change to your domain
        - name: PGID
          value: "1000"
        - name: PUID
          value: "1000"
        - name: TZ
          value: "Asia/Almaty" # change to your timezone

        volumeMounts:
        - mountPath: "/config" # path to config 
          name: jellyfin-config
        - mountPath: "/cache" # path to cache
          name: jellyfin-cache
        - mountPath: "/media" # path to media
          name: media
      volumes:
      - name: jellyfin-config
        hostPath:
          path: /storage/jellyfin/config # path on the server
      - name: jellyfin-cache
        hostPath:
          path: /storage/jellyfin/cache # path on the server
      - name: media
        hostPath:
          path: /storage/media # path on the server

Service

For making the app available in the cluster we need to expose the deployment using ClusterIP service. Core manifest can be create using this command:

$ kubectl k -n jellyfin expose deployment jellyfin \
	--name jellyfin-svc \
	--target-port 8096 --port 8096 \
	--dry-run=client -o yaml > service.yaml

It will create the following manifest:

apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: jellyfin
  name: jellyfin-svc
  namespace: jellyfin
spec:
  ports:
  - port: 8096
    protocol: TCP
    targetPort: 8096
  selector:
    app.kubernetes.io/name: jellyfin
status:
  loadBalancer: {}

Ingress

The last part is Ingress that can be created using the following command

 $ kubectl create ingress jellyfin-ingress --class=nginx \
	  --rule="<your domain>/*=jellyfin-svc:80" \
	  --dry-run=client -o yaml > ingress.yaml# do not forget to specify the domain

As a result we got the following manifest:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: jellyfin-ingress
spec:
  ingressClassName: nginx
  rules:
  - host: <your domain>
    http:
      paths:
      - backend:
          service:
            name: jellyfin-svc
            port:
              number: 80
        path: /
        pathType: Prefix
status:
  loadBalancer: {}

Deploying the resources

Finally, we have two options: either run the manifests one by one or save all the manifests in a single file, separated by three dashes (—).

If you choose to run the manifests individually, follow the same order in which the manifests were created.

Conclusion

By following these steps, you can successfully convert a Docker Compose file into Kubernetes objects for Jellyfin. This allows you to leverage Kubernetes’ powerful orchestration capabilities for managing your media server.


<
Previous Post
CKA Lab Setup with K3s
>
Blog Archive
Archive of all previous blog posts