Post

Jellyfin - Selfhosted Media Server

Jellyfin - Selfhosted Media Server

Jellyfin is the volunteer-built media solution that puts you in control of your media. Stream to any device from your own server, with no strings attached. Your media, your server, your way.

I also run JellyPlex to sync watch states between Plex and Jellyfin

Since this uses media from my NAS i have simply decided to run this as a Docker Container on UnRAID on my NAS instead of having it as part of my kubernetes cluster to remove the need to access the NAS media remotely

Kubernetes Manifest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: jellyfin
    app.kubernetes.io/instance: jellyfin
    app.kubernetes.io/name: jellyfin
  name: jellyfin
  namespace: jellyfin
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: jellyfin
  template:
    metadata:
      labels:
        app: jellyfin
        app.kubernetes.io/name: jellyfin
    spec:
      nodeSelector:
        nas: "true"
      containers:
      - image: jellyfin/jellyfin
        name: jellyfin
        ports:
        - containerPort: 8096
          name: web
          protocol: TCP
        env:
        - name: TZ
          value: Europe/London
        volumeMounts:
        - mountPath: "/media"
          readOnly: false
          name: smb
        - mountPath: "/config"
          readOnly: false
          name: config
        - mountPath: /dev/dri
          name: dri
        securityContext:
          privileged: true
      volumes:
        - name: smb
          persistentVolumeClaim:
            claimName: pvc-jellyfin-smb
        - name: config
          persistentVolumeClaim:
            claimName: jellyfin
        - name: dri
          hostPath:
            path: /dev/dri
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: jellyfin
  name: jellyfin
  namespace: jellyfin 
spec:
  ports:
  - name: web-tcp
    port: 8096
    protocol: TCP
    targetPort: 8096
  - name: web-udp
    port: 8096
    protocol: UDP
    targetPort: 8096
  selector:
    app: jellyfin
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: jellyfin
  namespace: jellyfin
  annotations: 
    kubernetes.io/ingress.class: traefik-external
    gethomepage.dev/href: "https://jellyfin.f9.casa"
    gethomepage.dev/enabled: "true"
    gethomepage.dev/description: Media Server
    gethomepage.dev/group: Media
    gethomepage.dev/icon: jellyfin.png
    gethomepage.dev/name: Jellyfin
    gethomepage.dev/widget.type: jellyfin
    gethomepage.dev/widget.url: "http://jellyfin.jellyfin:8096"
    gethomepage.dev/widget.key: "[REDACTED]"
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`jellyfin.f9.casa`)
      kind: Rule
      services:
        - name: jellyfin
          port: 8096
      middlewares:
        - name: default-headers
          namespace: default
  tls:
    secretName: f9-casa-tls

Docker Compose

Note that as I use Docker Swarm I am making use of https://github.com/allfro/device-mapping-manager to pass through dev’s to swarm containers by faking a volume, this container then detects that fake volume and maps it as a device

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
version: '3.9'
services:
  jellyfin:
    image: lscr.io/linuxserver/jellyfin:latest
    hostname: jellyfin
    container_name: jellyfin
    privileged: true
    networks:
      - traefik-public
    environment:
      - TZ=Europe/London
      - JELLYFIN_PublishedServerUrl=jellyfin.f9.casa
      - PUID=1000
      - PGID=1000
    volumes:
      - /srv/cephfs/docker/appdata/jellyfin:/config
      - /srv/media:/media
      - /dev/dri:/dev/dri
    ports:
      - 8096:8096

    deploy:
      labels:
        - "traefik.enable=true"
        - "traefik.http.routers.jellyfin.rule=Host(`jellyfin.f9.casa`)"
        - "traefik.http.services.jellyfin.loadbalancer.server.port=8096"
        - "traefik.http.routers.jellyfin.entrypoints=websecure"
        - "traefik.http.routers.jellyfin.tls=true"
        - "traefik.http.routers.jellyfin.tls.certresolver=letsencrypt"

        - homepage.group=Media
        - homepage.name=Jellyfin
        - homepage.icon=jellyfin.png
        - homepage.href=https://jellyfin.f9.casa
        - homepage.description=Media server
        - homepage.siteMonitor=http://jellyfin:8096
        - homepage.widget.type=jellyfin
        - homepage.widget.url=http://jellyfin:8096
        - homepage.widget.key=[REDACTED]
      mode: replicated
      placement:
        constraints:
          - node.labels.gpu == true
          
  jellyplex:
    image: luigi311/jellyplex-watched:latest
    hostname: jellyplex
    container_name: jellyplex
    privileged: false
    networks:
      - traefik-public
    environment:
      - SYNC_FROM_PLEX_TO_JELLYFIN=true
      - SYNC_FROM_JELLYFIN_TO_PLEX=true
      - DRYRUN=true
      - DEBUG=false
      - DEBUG_LEVEL=info
      - SLEEP_DURATION=3600
      - LOGFILE=/tmp/log.log
      - 'USER_MAPPING={ "[REDACTED]": "Scott" }'
      - PLEX_BASEURL=http://plex:32400
      - PLEX_TOKEN=[REDACTED]
      - PLEX_SERVERNAME=F9
      - SSL_BYPASS=true
      - JELLYFIN_BASEURL=http://jellyfin:8096
      - JELLYFIN_TOKEN=[REDACTED]
    deploy:      
      mode: replicated
      
networks:
  traefik-public:
    external: true

Reset Jellyfin Password

sqlite3 jellyfin.db

1
UPDATE Users SET Password = '$PBKDF2-SHA512$iterations=120000$C8B9B58B72BC7B864DE42F8819FBA024$7B6BC38F2E7635C4E1B93FA9C997479548497EAD1F298FF47C3398D369F47874C6B2504D1F59F84A88116954AD0795DBAB06AB3DC7DB22FCE470B82E183A48DA' WHERE Username = 'XXX';
This post is licensed under CC BY 4.0 by the author.