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

Configure Feed

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

move immich to k8s

Aly Raffauf (Jun 24, 2026, 12:31 PM EDT) b524c29c 85b9e7ad

+456 -61
-5
k8s/charts/external-routes/values.yaml
··· 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 5 routes: 6 - - name: immich 7 - host: immich.cute.haus 8 - target: { ip: 100.81.61.31, port: 2283 } # jubilife 9 - tls: { secretName: cute-haus-tls } 10 - 11 6 - name: jellyfin 12 7 host: jellyfin.cute.haus 13 8 target: { ip: 100.81.61.31, port: 8096 } # jubilife
+6
k8s/charts/immich/Chart.yaml
··· 1 + apiVersion: v2 2 + name: immich 3 + description: Immich photo library 4 + type: application 5 + version: 0.1.0 6 + appVersion: "2.7.5"
+74
k8s/charts/immich/templates/deployment-ml.yaml
··· 1 + apiVersion: apps/v1 2 + kind: Deployment 3 + metadata: 4 + name: immich-ml 5 + labels: 6 + app: immich-ml 7 + spec: 8 + replicas: 1 9 + strategy: 10 + type: Recreate 11 + selector: 12 + matchLabels: 13 + app: immich-ml 14 + template: 15 + metadata: 16 + labels: 17 + app: immich-ml 18 + spec: 19 + terminationGracePeriodSeconds: 30 20 + enableServiceLinks: false 21 + nodeSelector: 22 + kubernetes.io/hostname: jubilife 23 + tolerations: 24 + - key: node.kubernetes.io/not-ready 25 + operator: Exists 26 + effect: NoExecute 27 + tolerationSeconds: 60 28 + - key: node.kubernetes.io/unreachable 29 + operator: Exists 30 + effect: NoExecute 31 + tolerationSeconds: 60 32 + containers: 33 + - name: immich-ml 34 + image: ghcr.io/immich-app/immich-machine-learning:v2.7.5-openvino@sha256:619b43773d4f050d7504a236061034c06ef653f28693813ebccb1ab5d1e77512 35 + imagePullPolicy: IfNotPresent 36 + env: 37 + - name: MACHINE_LEARNING_CACHE_FOLDER 38 + value: "/cache" 39 + ports: 40 + - name: http 41 + containerPort: 3003 42 + resources: 43 + requests: 44 + cpu: 200m 45 + memory: 1Gi 46 + gpu.intel.com/i915: "1" 47 + limits: 48 + cpu: "4" 49 + memory: 4Gi 50 + gpu.intel.com/i915: "1" 51 + volumeMounts: 52 + - name: model-cache 53 + mountPath: /cache 54 + startupProbe: 55 + httpGet: 56 + path: /ping 57 + port: http 58 + periodSeconds: 10 59 + failureThreshold: 60 60 + livenessProbe: 61 + httpGet: 62 + path: /ping 63 + port: http 64 + periodSeconds: 30 65 + readinessProbe: 66 + httpGet: 67 + path: /ping 68 + port: http 69 + periodSeconds: 10 70 + failureThreshold: 3 71 + volumes: 72 + - name: model-cache 73 + persistentVolumeClaim: 74 + claimName: immich-ml-cache
+97
k8s/charts/immich/templates/deployment.yaml
··· 1 + apiVersion: apps/v1 2 + kind: Deployment 3 + metadata: 4 + name: immich 5 + labels: 6 + app: immich 7 + spec: 8 + replicas: 1 9 + strategy: 10 + type: Recreate 11 + selector: 12 + matchLabels: 13 + app: immich 14 + template: 15 + metadata: 16 + labels: 17 + app: immich 18 + spec: 19 + terminationGracePeriodSeconds: 30 20 + enableServiceLinks: false 21 + nodeSelector: 22 + kubernetes.io/hostname: jubilife 23 + tolerations: 24 + - key: node.kubernetes.io/not-ready 25 + operator: Exists 26 + effect: NoExecute 27 + tolerationSeconds: 60 28 + - key: node.kubernetes.io/unreachable 29 + operator: Exists 30 + effect: NoExecute 31 + tolerationSeconds: 60 32 + containers: 33 + - name: immich 34 + image: ghcr.io/immich-app/immich-server:v2.7.5@sha256:cd4aaf5d917fe19b942bba0df4e50eed8e6766190b381e85687cc7880255674e 35 + imagePullPolicy: IfNotPresent 36 + env: 37 + - name: DB_HOSTNAME 38 + value: "immich-postgres" 39 + - name: DB_PORT 40 + value: "5432" 41 + - name: DB_DATABASE_NAME 42 + value: "immich" 43 + - name: DB_USERNAME 44 + value: "immich" 45 + - name: REDIS_HOSTNAME 46 + value: "valkey.default.svc" 47 + - name: REDIS_DBINDEX 48 + value: "4" 49 + - name: IMMICH_MACHINE_LEARNING_URL 50 + value: "http://immich-ml:3003" 51 + - name: IMMICH_CONFIG_FILE 52 + value: "/etc/immich/config.json" 53 + - name: TZ 54 + value: "America/New_York" 55 + envFrom: 56 + - secretRef: 57 + name: immich-env 58 + ports: 59 + - name: http 60 + containerPort: 2283 61 + resources: 62 + requests: 63 + cpu: 200m 64 + memory: 512Mi 65 + limits: 66 + cpu: "4" 67 + memory: 4Gi 68 + volumeMounts: 69 + - name: uploads 70 + mountPath: /data 71 + - name: config 72 + mountPath: /etc/immich 73 + readOnly: true 74 + startupProbe: 75 + httpGet: 76 + path: /api/server/ping 77 + port: http 78 + periodSeconds: 10 79 + failureThreshold: 30 80 + livenessProbe: 81 + httpGet: 82 + path: /api/server/ping 83 + port: http 84 + periodSeconds: 30 85 + readinessProbe: 86 + httpGet: 87 + path: /api/server/ping 88 + port: http 89 + periodSeconds: 10 90 + failureThreshold: 3 91 + volumes: 92 + - name: uploads 93 + persistentVolumeClaim: 94 + claimName: immich-uploads 95 + - name: config 96 + secret: 97 + secretName: immich-config
+23
k8s/charts/immich/templates/ingress.yaml
··· 1 + apiVersion: networking.k8s.io/v1 2 + kind: Ingress 3 + metadata: 4 + name: immich 5 + labels: 6 + app: immich 7 + spec: 8 + ingressClassName: traefik 9 + tls: 10 + - hosts: 11 + - "immich.cute.haus" 12 + secretName: cute-haus-tls 13 + rules: 14 + - host: "immich.cute.haus" 15 + http: 16 + paths: 17 + - path: / 18 + pathType: Prefix 19 + backend: 20 + service: 21 + name: immich 22 + port: 23 + number: 80
+73
k8s/charts/immich/templates/pvc.yaml
··· 1 + apiVersion: v1 2 + kind: PersistentVolume 3 + metadata: 4 + name: immich-uploads 5 + labels: 6 + app: immich 7 + spec: 8 + capacity: 9 + storage: 10Ti 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/immich 24 + type: Directory 25 + --- 26 + apiVersion: v1 27 + kind: PersistentVolumeClaim 28 + metadata: 29 + name: immich-uploads 30 + labels: 31 + app: immich 32 + annotations: 33 + helm.sh/resource-policy: keep 34 + spec: 35 + accessModes: 36 + - ReadWriteOnce 37 + storageClassName: "" 38 + volumeName: immich-uploads 39 + resources: 40 + requests: 41 + storage: 10Ti 42 + --- 43 + apiVersion: v1 44 + kind: PersistentVolumeClaim 45 + metadata: 46 + name: immich-ml-cache 47 + labels: 48 + app: immich 49 + annotations: 50 + helm.sh/resource-policy: keep 51 + spec: 52 + accessModes: 53 + - ReadWriteOnce 54 + storageClassName: longhorn 55 + resources: 56 + requests: 57 + storage: 10Gi 58 + --- 59 + apiVersion: v1 60 + kind: PersistentVolumeClaim 61 + metadata: 62 + name: immich-db-data 63 + labels: 64 + app: immich 65 + annotations: 66 + helm.sh/resource-policy: keep 67 + spec: 68 + accessModes: 69 + - ReadWriteOnce 70 + storageClassName: longhorn 71 + resources: 72 + requests: 73 + storage: 20Gi
+38
k8s/charts/immich/templates/secret.yaml
··· 1 + {{- if .Values.secret }} 2 + apiVersion: v1 3 + kind: Secret 4 + metadata: 5 + name: immich-env 6 + labels: 7 + app: immich 8 + type: Opaque 9 + stringData: 10 + DB_PASSWORD: {{ .Values.secret.dbPassword | quote }} 11 + --- 12 + apiVersion: v1 13 + kind: Secret 14 + metadata: 15 + name: immich-config 16 + labels: 17 + app: immich 18 + type: Opaque 19 + stringData: 20 + config.json: | 21 + { 22 + "oauth": { 23 + "enabled": true, 24 + "issuerUrl": "https://id.cute.haus", 25 + "clientId": {{ .Values.secret.oauthClientId | toJson }}, 26 + "clientSecret": {{ .Values.secret.oauthClientSecret | toJson }}, 27 + "scope": "openid email profile", 28 + "buttonText": "Sign in with cute.haus", 29 + "autoRegister": true, 30 + "autoLaunch": false, 31 + "mobileOverrideEnabled": false, 32 + "mobileRedirectUri": "" 33 + }, 34 + "passwordLogin": { 35 + "enabled": false 36 + } 37 + } 38 + {{- end }}
+44
k8s/charts/immich/templates/service.yaml
··· 1 + apiVersion: v1 2 + kind: Service 3 + metadata: 4 + name: immich 5 + labels: 6 + app: immich 7 + spec: 8 + type: ClusterIP 9 + selector: 10 + app: immich 11 + ports: 12 + - name: http 13 + port: 80 14 + targetPort: 2283 15 + --- 16 + apiVersion: v1 17 + kind: Service 18 + metadata: 19 + name: immich-ml 20 + labels: 21 + app: immich-ml 22 + spec: 23 + type: ClusterIP 24 + selector: 25 + app: immich-ml 26 + ports: 27 + - name: http 28 + port: 3003 29 + targetPort: 3003 30 + --- 31 + apiVersion: v1 32 + kind: Service 33 + metadata: 34 + name: immich-postgres 35 + labels: 36 + app: immich-postgres 37 + spec: 38 + type: ClusterIP 39 + selector: 40 + app: immich-postgres 41 + ports: 42 + - name: postgres 43 + port: 5432 44 + targetPort: 5432
+80
k8s/charts/immich/templates/statefulset-postgres.yaml
··· 1 + apiVersion: apps/v1 2 + kind: StatefulSet 3 + metadata: 4 + name: immich-postgres 5 + labels: 6 + app: immich-postgres 7 + spec: 8 + serviceName: immich-postgres 9 + replicas: 1 10 + selector: 11 + matchLabels: 12 + app: immich-postgres 13 + template: 14 + metadata: 15 + labels: 16 + app: immich-postgres 17 + spec: 18 + terminationGracePeriodSeconds: 30 19 + nodeSelector: 20 + kubernetes.io/hostname: jubilife 21 + tolerations: 22 + - key: node.kubernetes.io/not-ready 23 + operator: Exists 24 + effect: NoExecute 25 + tolerationSeconds: 60 26 + - key: node.kubernetes.io/unreachable 27 + operator: Exists 28 + effect: NoExecute 29 + tolerationSeconds: 60 30 + containers: 31 + - name: postgres 32 + image: ghcr.io/immich-app/postgres:17-vectorchord0.4.3-pgvector0.8.0@sha256:0baf4cde9b54d8d7dc6a6ad8d8c43c3c6b884f82e1c2a023d571414820e39336 33 + imagePullPolicy: IfNotPresent 34 + env: 35 + - name: POSTGRES_USER 36 + value: "immich" 37 + - name: POSTGRES_DB 38 + value: "immich" 39 + - name: POSTGRES_INITDB_ARGS 40 + value: "--data-checksums" 41 + - name: PGDATA 42 + value: "/var/lib/postgresql/data/pgdata" 43 + - name: POSTGRES_PASSWORD 44 + valueFrom: 45 + secretKeyRef: 46 + name: immich-env 47 + key: DB_PASSWORD 48 + ports: 49 + - name: postgres 50 + containerPort: 5432 51 + resources: 52 + requests: 53 + cpu: 100m 54 + memory: 512Mi 55 + limits: 56 + cpu: "2" 57 + memory: 2Gi 58 + volumeMounts: 59 + - name: data 60 + mountPath: /var/lib/postgresql/data 61 + - name: dshm 62 + mountPath: /dev/shm 63 + livenessProbe: 64 + exec: 65 + command: ["pg_isready", "-U", "immich"] 66 + periodSeconds: 30 67 + failureThreshold: 5 68 + readinessProbe: 69 + exec: 70 + command: ["pg_isready", "-U", "immich"] 71 + periodSeconds: 10 72 + failureThreshold: 3 73 + volumes: 74 + - name: data 75 + persistentVolumeClaim: 76 + claimName: immich-db-data 77 + - name: dshm 78 + emptyDir: 79 + medium: Memory 80 + sizeLimit: 128Mi
+9
k8s/helmfile.yaml
··· 270 270 values: 271 271 - values/secrets/forward-auth.yaml 272 272 273 + - name: immich 274 + namespace: default 275 + chart: ./charts/immich 276 + needs: 277 + - default/cluster-tls 278 + - default/valkey 279 + values: 280 + - values/secrets/immich.yaml 281 + 273 282 - name: navidrome 274 283 namespace: default 275 284 chart: ./charts/navidrome
+4
k8s/values/secrets/immich.yaml
··· 1 + secret: 2 + dbPassword: ref+sops://../secrets/immich.yaml#/db/password 3 + oauthClientId: ref+sops://../secrets/immich.yaml#/oauth/client_id 4 + oauthClientSecret: ref+sops://../secrets/immich.yaml#/oauth/client_secret
+4 -54
nix/hosts/jubilife.nix
··· 282 282 in { 283 283 myBackups.jobs = { 284 284 immich = { 285 - backupCleanupCommand = start "immich-server"; 286 - backupPrepareCommand = stop "immich-server"; 287 285 paths = [ 288 - "${config.services.immich.mediaLocation}/library" 289 - "${config.services.immich.mediaLocation}/profile" 290 - "${config.services.immich.mediaLocation}/upload" 291 - "${config.services.immich.mediaLocation}/backups" 286 + "${dataDirectory}/immich/library" 287 + "${dataDirectory}/immich/profile" 288 + "${dataDirectory}/immich/upload" 289 + "${dataDirectory}/immich/backups" 292 290 ]; 293 291 }; 294 292 ··· 297 295 backupPrepareCommand = stop "jellyfin"; 298 296 paths = [config.services.jellyfin.dataDir]; 299 297 }; 300 - 301 - postgresql = { 302 - backupCleanupCommand = '' 303 - ${start "postgresql"} 304 - ${start "immich-server"} 305 - ''; 306 - backupPrepareCommand = stop "postgresql"; 307 - paths = [config.services.postgresql.dataDir]; 308 - }; 309 298 }; 310 299 311 300 sops = { 312 - templates."immich-config.json" = { 313 - owner = "immich"; 314 - content = '' 315 - { 316 - "oauth": { 317 - "enabled": true, 318 - "issuerUrl": "https://id.cute.haus", 319 - "clientId": "${config.sops.placeholder.immichOauthClientId}", 320 - "clientSecret": "${config.sops.placeholder.immichOauthClientSecret}", 321 - "scope": "openid email profile", 322 - "buttonText": "Sign in with cute.haus", 323 - "autoRegister": true, 324 - "autoLaunch": false, 325 - "mobileOverrideEnabled": false, 326 - "mobileRedirectUri": "" 327 - } 328 - } 329 - ''; 330 - }; 331 - 332 301 secrets = { 333 - immichOauthClientId = { 334 - sopsFile = ../../secrets/immich.yaml; 335 - key = "oauth/client_id"; 336 - owner = "immich"; 337 - }; 338 - immichOauthClientSecret = { 339 - sopsFile = ../../secrets/immich.yaml; 340 - key = "oauth/client_secret"; 341 - owner = "immich"; 342 - }; 343 302 photoprismAdminPass = { 344 303 sopsFile = ../../secrets/photoprism.yaml; 345 304 key = "ADMIN_PASSWORD"; ··· 412 371 encode zstd gzip 413 372 reverse_proxy jubilife:8181 414 373 ''; 415 - }; 416 - 417 - immich = { 418 - enable = true; 419 - host = "0.0.0.0"; 420 - mediaLocation = "${dataDirectory}/immich"; 421 - openFirewall = true; 422 - port = 2283; 423 - environment.IMMICH_CONFIG_FILE = config.sops.templates."immich-config.json".path; 424 374 }; 425 375 426 376 jellyfin = {
+4 -2
secrets/immich.yaml
··· 1 1 oauth: 2 2 client_id: ENC[AES256_GCM,data:Xd1gtlOKPg7cOHN2mrtGLeR/NSoFgEY/e5qHhWAYcLKK+Npr,iv:AZdiQFmVUG8Wu8Up2nKFdE4oKolOU7xM9r/1BoINZRg=,tag:WqZ+rcxZvwL907SWIzISCw==,type:str] 3 3 client_secret: ENC[AES256_GCM,data:7wVr06zvrnZ4XmfEV/YpwbKyltXObE4yZD4nutKGGqo=,iv:ktb9py1fbWo6E0NZou71I+ea2+1mA4xhw50ySbmACWo=,tag:PuJD6kSLtqKfmU96SdBd+Q==,type:str] 4 + db: 5 + password: ENC[AES256_GCM,data:RGgPZMvPMtMpG9KyoLyKpPRZSQ9h6M8rIChaYqHO/wZCQJAHqFp7sjj6xQ==,iv:2KF43CXAe0KOng7iqGq/ZL7aZ+vHHVlRZG/ydUL99Eo=,tag:6qKd1SdAqbZ2FOZV1x1oJQ==,type:str] 4 6 sops: 5 7 age: 6 8 - enc: | ··· 201 203 XBe/VzSNl9GRdudcBCHIjHW49L+cbcIHimxMeQipyKHPeCqX6rvtBQ== 202 204 -----END AGE ENCRYPTED FILE----- 203 205 recipient: age1s7ecrfntus8rvyprzhffjpqk2tmrc259knf9xqajlrqvewfp8d6svwp9pp 204 - lastmodified: "2026-05-13T19:15:32Z" 205 - mac: ENC[AES256_GCM,data:dSiLyCh4vzNpq6bMMiYGS61xzc+MGnv6+YlA9Hew2TDDYXEC5g2DP7z70nNoLiPcT1Fcdvp63jqCSNfN4YylaRV4uZgpax8vqeFsRQwkur15+1odQykrSO2A6hLHShafdOIoppUCHFEP54GZQ+qpeq4cvRF0xMxs/eVWZR8Zm4E=,iv:utore+o5L0kl670akewoh7Rbh9OhG3/XNXCpZvBd+KI=,tag:kCU4aLT/jmrwonWeIQum/w==,type:str] 206 + lastmodified: "2026-06-24T15:30:54Z" 207 + mac: ENC[AES256_GCM,data:EsPZOApGXhT5ZobNFQ3AitLgEFzQuRXLo38ZhpDHYqDF5e+6W1leGuOqMA4cMHoTA2Q4zQwhIGs+OlKLbXjr/obplmXAHasOwIFDRb1FcFFpzEPdlW2ogOFyX0ywmIKWZcjO+Jr9yyhmJSYwGQw0Or7NXok1xUlU76L8QVvNqG8=,iv:gb2p83t6Ek25MvJrCZvCujwifeGs+W8X3iRelyS0674=,tag:bhjsY9kSJYUuZ6bZQ/j0yA==,type:str] 206 208 unencrypted_suffix: _unencrypted 207 209 version: 3.12.1