Notes on deploying Immich on k3s Kubernetes. I use the community Immich Helm chart with Cloudnative Postgres .

Pre-requisites

Set up Postgres

When using the community Immich helm chart, the postgres subchart is deprecated, so we will need to set up our own postgres instances for Immich. In this example, I the install CloudnativePG operator to manage postgres clusters.

Set up CloudnativePG

helm repo add cnpg https://cloudnative-pg.github.io/charts
helm repo update
helm install cnpg \
  --namespace cnpg-system \
  --create-namespace \
  cnpg/cloudnative-pg

Create the immich namespace.

kubectl create namespace immich

Create postgres secret for initial postgres username/password.

apiVersion: v1
stringData:
  DB_USERNAME: immich
  DB_DATABASE_NAME: immich
  DB_PASSWORD: immich
  username: immich
  password: immich
kind: Secret
metadata:
  name: immich-postgres-user
  namespace: immich
type: kubernetes.io/basic-auth

Create postgres instance for immich using cnpg. Here I use cloudnative-pgvecto.rs and add initilization statements for the extensions required by Immich. Note that you may need to check which versions of pgvecto.rs are compatible with the Immich version being deployed.

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: immich-postgres
  namespace: immich
spec:.
  imageName: ghcr.io/tensorchord/cloudnative-pgvecto.rs:16.5-v0.3.0@sha256:be3f025d79aa1b747817f478e07e71be43236e14d00d8a9eb3914146245035ba
  instances: 1
  postgresql:
    shared_preload_libraries:
      - "vectors.so"
  managed:
    roles:
      - name: immich
        superuser: true
        login: true
  bootstrap:
    initdb:
      database: immich
      owner: immich
      secret:
        name: immich-postgres-user
      postInitSQL:
        - CREATE EXTENSION IF NOT EXISTS "vectors";
        - CREATE EXTENSION IF NOT EXISTS "cube" CASCADE;
        - CREATE EXTENSION IF NOT EXISTS "earthdistance" CASCADE;
 
  storage:
    size: 4Gi
    storageClass: local-path

Set up volumes

Create PVs and PVCs for immich library and immich ML cache.

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: immich-ml-pv
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/var/k8-volumes/immich-ml"
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: immich-library-pv
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 350Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/usr/share/synology/immich"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: immich-ml-pvc
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: immich-library-pvc
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 250Gi

If using a NFS volume, this can be mounted on the local filesystem.

$ sudo mkdir -p /usr/share/synology/immich
 
$ cat ~/.smbcreds
username=changeme
password=changeme
 
$ sudo mount -t cifs -o credentials=/home/frank/.smbcreds //thunderbolt.juju.net/shared/immich /usr/share/synology/immich

Install Immich

We can use the community Helm chart.

Create values.yaml file for immich, reference the created PVCs. If you are using Ingress certificates with private CA, you can also add the cert-manager annotations on server.ingress.annotations.

env:
  REDIS_HOSTNAME: '{{ printf "%s-redis-master" .Release.Name }}'
  DB_HOSTNAME: "{{ .Release.Name }}-postgres-rw.immich.svc.cluster.local"
  DB_USERNAME: "{{ .Values.postgresql.global.postgresql.auth.username }}"
  DB_DATABASE_NAME: "{{ .Values.postgresql.global.postgresql.auth.database }}"
  DB_PASSWORD: "{{ .Values.postgresql.global.postgresql.auth.password }}"
  IMMICH_MACHINE_LEARNING_URL: '{{ printf "http://%s-machine-learning:3003" .Release.Name }}'
 
image:
  tag: v1.124.2
 
immich:
  metrics:
    enabled: false
  persistence:
    library:
      existingClaim: immich-library-pvc
  configuration: {}
  
# Dependencies
redis:
  enabled: true
  architecture: standalone
  auth:
    enabled: false
 
# Immich components
server:
  enabled: true
  image:
    repository: ghcr.io/immich-app/immich-server
    pullPolicy: IfNotPresent
  ingress:
    main:
      enabled: true
      annotations:
        traefik.ingress.kubernetes.io/router.entrypoints: websecure
        traefik.ingress.kubernetes.io/service.serversscheme: https
        traefik.ingress.kubernetes.io/service.nativelb: "true"
	    traefik.ingress.kubernetes.io/service.sticky.cookie: "true"
      hosts:
        - host: immich.kube
          paths:
            - path: "/"
      tls: []
 
machine-learning:
  enabled: true
  image:
    repository: ghcr.io/immich-app/immich-machine-learning
    pullPolicy: IfNotPresent
  env:
    TRANSFORMERS_CACHE: /cache
  persistence:
    cache:
      enabled: true
      size: 10Gi
      existingClaim: immich-ml-pvc

Install the chart.

helm repo add immich https://immich-app.github.io/immich-charts
helm repo update
helm install --create-namespace --namespace immich immich immich/immich -f values.yaml

If using external libraries, edit the immich-server deployment to add any additional mounts for external libraries. Note: external libraries must be outside the /usr/src/app path.

$ kubectl -n immich get deployment immich-server -o yaml
...
volumeMounts:
- mountPath: /usr/src/app/upload
  name: library
- mountPath: /usr/src/external/synology
  name: library

If using Traefik, we need to enable sticky sessions on the service for websockets to work nicely.

$ kubectl -n immich get service immich-server -o yaml 
apiVersion: v1
kind: Service
metadata:
  annotations:
    ...
    traefik.ingress.kubernetes.io/service.nativelb: "true"
    traefik.ingress.kubernetes.io/service.sticky.cookie: "true"

Importing

For batch importing of images from Google Takeout or iCloud data export, you can use immich-go. This will import assets into the Immich library itself, rather than linking to an external library. There are several benefits with this:

  • You can manage all the original files with Immich.
  • Have server stats reporting (External libraries are not reflected in server stats.)
  • Consolidate Google Photos metadata, since these are exported as sidecar JSON files from Google Takeout.

Example of importing with immich-go

First configure an API key in Immich using Account Settings > API Keys.

Google Takeout

./immich-go -server=https://immich.kube -key=$(cat /Users/frank/dev/immich-key) -skip-verify-ssl upload -google-photos /Volumes/shared/immich/google-photos

iCloud

./immich-go -server=https://immich.kube.juju.net -key=$(cat /Users/frank/dev/immich-key) -skip-verify-ssl upload /Volumes/shared/immich/icloud-photos

Resource usage profile during external library scanning.