Skip to content

Instantly share code, notes, and snippets.

@vdboor
Last active September 15, 2026 08:43
Show Gist options
  • Select an option

  • Save vdboor/4a596a4541e98318801461556a295ee7 to your computer and use it in GitHub Desktop.

Select an option

Save vdboor/4a596a4541e98318801461556a295ee7 to your computer and use it in GitHub Desktop.
Immich installation on K3S / Kubenetes with: background workers - CloudNativePG - external storage.
# https://github.com/immich-app/immich-charts/blob/main/charts/immich/values.yaml
# helm install --create-namespace --namespace immich immich oci://ghcr.io/immich-app/immich-charts/immich -f values.yaml
# helm upgrade --namespace immich immich oci://ghcr.io/immich-app/immich-charts/immich -f values.yml
controllers:
main:
containers:
main:
image:
tag: v3.2.1
server:
controllers:
# Main worker serving web/api requests:
main:
containers:
main:
env:
IMMICH_WORKERS_INCLUDE: 'api' # Only serve HTTP requests
TZ: "Europe/Amsterdam"
REDIS_HOSTNAME: 'redis-master.infra' # Simple shared redis pod, nothing special
REDIS_PASSWORD: '<snip>'
REDIS_DBINDEX: 13
DB_HOSTNAME: 'immich-postgres-rw' # Deployed by CloudNativePG
DB_DATABASE_NAME: 'immich'
DB_USERNAME:
valueFrom:
secretKeyRef:
name: immich-db-user
key: username
DB_PASSWORD:
valueFrom:
secretKeyRef:
name: immich-db-user
key: password
# Background worker in a separate pod.
# This avoids a hanging main api process during background processing.
microservices:
containers:
microservices:
image:
repository: ghcr.io/immich-app/immich-server
tag: v3.2.1
env:
IMMICH_WORKERS_EXCLUDE: 'api' # Background workers, no HTTP interface.
# Rest need to be the same as api container:
TZ: "Europe/Amsterdam"
REDIS_HOSTNAME: 'redis-master.infra'
REDIS_PASSWORD: '<snip>'
REDIS_DBINDEX: 13
DB_HOSTNAME: 'immich-postgres-rw'
DB_DATABASE_NAME: 'immich'
DB_USERNAME:
valueFrom:
secretKeyRef:
name: immich-db-user
key: username
DB_PASSWORD:
valueFrom:
secretKeyRef:
name: immich-db-user
key: password
# Webserver configuration
ingress:
main:
enabled: true
className: nginx
hosts:
- host: photos.example.com
paths:
- path: "/"
service:
identifier: main
- host: immich
paths:
- path: "/"
service:
identifier: main
- host: immich.home
paths:
- path: "/"
service:
identifier: main
valkey:
enabled: true
#persistence:
# data:
# size: 100Mi
# type: persistentVolumeClaim
persistence:
# Defining persistence in other controlers didn't work
# Placing here globally, it will be added to all pods however
external:
enabled: true
type: hostPath
hostPath: /mnt/host-fotos
globalMounts:
- path: /external/
readOnly: true
# Persistent volumes are defined below, not using the yaml options here.
# This allowed using extra options for the PVC objects that helm doesn't expose.
immich:
configurationKind: Secret
persistence:
library:
existingClaim: "immich-library"
microservices:
persistence:
library:
existingClaim: "immich-library"
machine-learning:
persistence:
cache:
type: persistentVolumeClaim
existingClaim: "immich-machine-learning"
#accessMode: ReadWriteOnce
#size: 2Gi # not 10
# CloudNativePG database on the cluster
# This performs the "CREATE DATABASE" and "CREATE EXTENSION" on the cluster.
apiVersion: postgresql.cnpg.io/v1
kind: Database
metadata:
name: immich-database
namespace: immich
spec:
name: immich
owner: immich
cluster:
name: immich-postgres # Cluster created below
extensions:
- name: vector
ensure: present
- name: vchord
ensure: present
- name: cube
ensure: present
- name: earthdistance
ensure: present
# PostgreSQL user for CloudNativePG
# Used in various other yaml files.
apiVersion: v1
kind: Secret
type: kubernetes.io/basic-auth
metadata:
name: immich-db-user
namespace: immich
labels:
cnpg.io/reload: "true"
data:
username: <base64-encoded-snip>
password: <base64-encoded-snip>
# CloudNativePG Postgres cluster.
# This spawns the whole PostgreSQL database container.
# It uses a custom image, as mounting the vchord.so via another image didn't work.
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: immich-postgres
namespace: immich
spec:
instances: 1
# Custom image here, that has the vchord.so included.
# default: ghcr.io/cloudnative-pg/postgresql:18-standard-trixie
imageName: MYREGISTRY.example.com/cnpg-postgresql:18-trixie-vchord
storage:
size: 5Gi
storageClass: local-storage
resizeInUseVolumes: False
pvcTemplate:
dataSource:
apiGroup: v1
kind: PersistentVolumeClaim
name: immich-postgres-0-pvc
postgresql:
# Make sure the extension .so file is available at the server.
# The Database CRD will activate it for the database (CREATE EXTENSION).
shared_preload_libraries:
- "vchord.so"
# This didn't work for me:
#extensions:
# - name: vchord
# image:
# reference: ghcr.io/tensorchord/vchord-scratch:pg18-v1.1.1
# dynamic_library_path:
# - /usr/lib/postgresql/18/lib
# extension_control_path:
# - /usr/share/postgresql/18/
managed:
roles:
- name: immich
comment: Custom application user
login: true
passwordSecret:
name: immich-db-user
# Persistent volumes and claims
#
# For various reasons, I'm using the storage from the host OS here
# (which is mounted as virtiofs passthrough):
# - The database files of various posts are stored on /mnt/immich/
# - The external photo library is found on /mnt/host-photos.
# It avoiding needing network access to the external photo library.
# Hence, the volumes are forced to the K3S node that has this mount point.
#
# The volume claims are included here too, to make sure the PV and PVC match.
# This way 'values.yml' only needs to link to existing claims.
#
# In larger clusters, you'd probably use the PVC alone
# (or even let values.yaml generate the claims resources),
# and let Kubernetes allocate the storage on a network share.
#
kind: PersistentVolume
apiVersion: v1
metadata:
name: immich-library
namespace: immich
labels:
component: immich-library
spec:
storageClassName: local-storage
persistentVolumeReclaimPolicy: Retain
capacity:
storage: 30Gi
accessModes:
- ReadWriteOnce
local:
path: "/mnt/immich/library"
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- <snip-MY_K3S_IMMICH_NODE_NAME>
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: immich-library
namespace: immich
spec:
storageClassName: local-storage
selector:
matchLabels:
component: immich-library
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 30Gi
---
kind: PersistentVolume
apiVersion: v1
metadata:
name: immich-machine-learning
namespace: immich
labels:
component: immich-machine-learning
spec:
storageClassName: local-storage
persistentVolumeReclaimPolicy: Retain
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
local:
path: "/mnt/immich/machine-learning"
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- <snip-MY_K3S_IMMICH_NODE_NAME>
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: immich-machine-learning
namespace: immich
spec:
storageClassName: local-storage
selector:
matchLabels:
component: immich-machine-learning
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
kind: PersistentVolume
apiVersion: v1
metadata:
name: immich-postgres-0
namespace: immich
labels:
component: immich-postgres
spec:
storageClassName: local-storage
persistentVolumeReclaimPolicy: Retain
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
local:
path: "/mnt/immich/postgres-0"
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- <snip-MY_K3S_IMMICH_NODE_NAME>
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: immich-postgres-0
namespace: immich
spec:
storageClassName: local-storage
selector:
matchLabels:
component: immich-postgres
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
# Local storage provider settings.
# This will provision storage volumes on the node where the pod starts.
#
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
# Building the custom CloudNativePG image that has the vchord extension.
#
# docker build --progress=plain --pull -t MYREGISTRY.example.com/cnpg-postgresql:18-trixie-vchord .
# docker push MYREGISTRY.example.com/cnpg-postgresql:18-trixie-vchord
FROM ghcr.io/tensorchord/vchord-scratch:pg18-v1.1.1 AS ext
FROM ghcr.io/cloudnative-pg/postgresql:18-standard-trixie
COPY --from=ext ./usr/lib/postgresql/18/lib/vchord.so /usr/lib/postgresql/18/lib/
COPY --from=ext /usr/share/postgresql/18/extension/vchord* /usr/share/postgresql/18/extension/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment