Skip to main content

Immich Deployment Notes

This guide summarizes key configuration details for running Immich with GitOps.

Prerequisites​

  • Kubernetes cluster with the CloudNativePG operator (postgresql.cnpg.io/v1) installed
  • immich namespace created
  • External Secrets Operator available
  • Helm CLI and kubectl configured

Configuration Overview​

PostgreSQL with VectorChord Extension​

The Immich database uses CloudNativePG with a custom image that includes vector extensions:

# k8s/applications/media/immich/immich-server/database.yaml
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: immich-postgresql
namespace: immich
spec:
instances: 2
imageName: ghcr.io/tensorchord/cloudnative-vectorchord:17.7
bootstrap:
initdb:
database: immich
owner: immich
postInitApplicationSQL:
- CREATE EXTENSION IF NOT EXISTS "vector";
- CREATE EXTENSION IF NOT EXISTS "earthdistance" CASCADE;

Database Connection​

CloudNativePG automatically generates the immich-postgresql-app secret containing all connection details. The StatefulSet references this secret directly:

# k8s/applications/media/immich/immich-server/statefulset.yaml
env:
- name: DB_DATABASE_NAME
value: immich
- name: DB_HOSTNAME
value: immich-postgresql-rw
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
key: password
name: immich-postgresql-app
- name: DB_URL
valueFrom:
secretKeyRef:
key: uri
name: immich-postgresql-app
- name: DB_USERNAME
valueFrom:
secretKeyRef:
key: username
name: immich-postgresql-app

Kustomization Layout​

Use a root kustomization.yaml to track all resources:

resources:
- immich-config-external-secret.yaml
- minio-externalsecret.yaml
- database.yaml
- database-scheduled-backup.yaml
- podmonitor.yaml
- serviceaccount.yaml
- service.yaml
- servicemonitor.yaml
- statefulset.yaml

OAuth Configuration via ExternalSecret​

Immich expects the OAuth client details inside its config file. The immich-config-external-secret.yaml resource pulls these values from Bitwarden and renders the full configuration as a Secret:

# k8s/applications/media/immich/immich-server/immich-config-external-secret.yaml
spec:
template:
data:
immich-config.yaml: |
...
server:
externalDomain: https://photo.peekoff.com
oauth:
enabled: true
issuerUrl: "https://sso.peekoff.com/application/o/immich/"
scope: "openid email profile"
autoLaunch: true
autoRegister: true
buttonText: "Login with SSO"
clientId: "{{ .clientId }}"
clientSecret: "{{ .clientSecret }}"
passwordLogin:
enabled: false

This Secret is mounted by the StatefulSet at /config/immich-config.yaml, allowing the application to start without additional environment variables. The machine learning deployment mounts the same Secret so both components read identical settings.

Resource Requests​

Both pods need enough memory to process photos without crashing. A good starting point is:

ComponentCPU RequestMemory RequestCPU LimitMemory Limit
immich-server500m512Mi2000m2Gi
immich-machine-learning200m1Gi1000m4Gi

Library Storage​

Immich stores uploaded files on a Persistent Volume Claim named library. The claim requests 50Gi from Longhorn:

# k8s/applications/media/immich/immich-server/statefulset.yaml
spec:
volumeClaimTemplates:
- metadata:
name: library
spec:
storageClassName: longhorn
accessModes: ['ReadWriteOnce']
resources:
requests:
storage: 50Gi

Backups​

Database backups use MinIO credentials from an ExternalSecret and are configured through CloudNativePG's native backup integration:

# k8s/applications/media/immich/immich-server/database.yaml
plugins:
- name: barman-cloud.cloudnative-pg.io
isWALArchiver: true
parameters:
barmanObjectName: immich-minio-store

Scheduled backups are configured via database-scheduled-backup.yaml using the ScheduledBackup CRD.