Add Argo-managed Immich stack

This commit is contained in:
2026-10-22 10:35:11 +02:00
parent 0d294321e2
commit adf449d6ba
13 changed files with 391 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: immich-postgresql
namespace: immich
spec:
instances: 3
imageName: docker.io/tensorchord/cloudnative-vectorchord:18.6
enableSuperuserAccess: true
affinity:
podAntiAffinityType: required
bootstrap:
initdb:
database: immich
owner: feedc0de
encoding: UTF8
localeCollate: C
localeCType: C
dataChecksums: true
postInitApplicationSQL:
- ALTER ROLE feedc0de WITH SUPERUSER
storage:
size: 50Gi
storageClass: rook-ceph-block-ec
monitoring:
enablePodMonitor: true
@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- cluster.yaml
+33
View File
@@ -0,0 +1,33 @@
# Immich on arschrock
Argo CD installs the upstream Immich chart directly; there is no homelab
application/chart repository. The chart's upstream application release tag is
selected in `values.yaml`. The Immich server and machine-learning workload are
pinned to `arschrock`; only the existing `/mnt/Bilder` bcachefs library is
host-local. Immich PostgreSQL and Valkey run separately in this namespace with
Ceph-backed storage.
The local PV is retained and bound to `/mnt/Bilder`. It does not copy or move
the photo library. The server has a read-only startup check for the bcachefs
filesystem magic and expected `upload` directory, in addition to a node label
that prevents scheduling until the host helper confirms the mount.
Before unmounting `/mnt`, run `./immich-storage-offline.sh` from the workspace
root and wait for it to report that the server and machine-learning Pods have
stopped. Do not unmount if that helper fails. After mounting `/mnt` again, run
`./immich-storage-online.sh`; it validates bcachefs and the Immich upload
directory, forces fresh Pod mounts, and then restores the readiness label.
These helpers assume the default context `kubernetes-admin@kubernetes` and
refuse other contexts. No systemd hook controlling `/mnt` was found during
migration setup, so a future systemd-based mount change must explicitly order
these helpers before unmount and after mount.
The database operator creates its application password Secret automatically;
the cleartext credential is not stored in Git. The initial restore preserves
the source database's PostgreSQL 18.6 and pgvector extension versions. The
shared Valkey instance is not copied wholesale: only keys identified as
Immich-owned are imported, leaving all other applications' keys untouched.
The old Podman stack and host PostgreSQL must remain available as rollback
sources until the new application, database, monitoring, and routes have been
verified. Never run both Immich servers against `/mnt/Bilder` concurrently.
+4
View File
@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- library-storage.yaml
@@ -0,0 +1,45 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: immich-library
annotations:
argocd.argoproj.io/sync-wave: "-2"
labels:
immich.brunner.ninja/library: "true"
spec:
capacity:
storage: 10Ti
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
local:
path: /mnt/Bilder
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- arschrock
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: immich-library
namespace: immich
annotations:
argocd.argoproj.io/sync-wave: "-1"
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Ti
storageClassName: ""
volumeName: immich-library
selector:
matchLabels:
immich.brunner.ninja/library: "true"
+116
View File
@@ -0,0 +1,116 @@
controllers:
main:
containers:
main:
image:
# Upstream chart 0.13.4 still defaults to v3.2.0; keep the running
# application at v3.3.1 until the chart default catches up.
tag: v3.3.1
immich:
metrics:
enabled: true
persistence:
library:
existingClaim: immich-library
server:
controllers:
main:
pod:
nodeSelector:
kubernetes.io/hostname: arschrock
immich.brunner.ninja/library-ready: "true"
initContainers:
check-bcachefs-library:
image:
repository: docker.io/library/alpine
tag: 3.22.2
pullPolicy: IfNotPresent
command:
- /bin/sh
- -ec
- test "$(stat -f -c %t /usr/src/app/upload)" = ca451a4e && test -d /usr/src/app/upload/upload
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 65534
runAsGroup: 65534
containers:
main:
env:
DB_HOSTNAME: immich-postgresql-rw
DB_DATABASE_NAME: immich
DB_VECTOR_EXTENSION: pgvector
DB_USERNAME:
valueFrom:
secretKeyRef:
name: immich-postgresql-app
key: username
DB_PASSWORD:
valueFrom:
secretKeyRef:
name: immich-postgresql-app
key: password
ingress:
main:
enabled: false
persistence:
data:
existingClaim: '{{ .Values.immich.persistence.library.existingClaim }}'
advancedMounts:
main:
main:
- path: /usr/src/app/upload
check-bcachefs-library:
- path: /usr/src/app/upload
readOnly: true
machine-learning:
controllers:
main:
pod:
nodeSelector:
kubernetes.io/hostname: arschrock
immich.brunner.ninja/library-ready: "true"
initContainers:
check-bcachefs-library:
image:
repository: docker.io/library/alpine
tag: 3.22.2
pullPolicy: IfNotPresent
command:
- /bin/sh
- -ec
- test "$(stat -f -c %t /usr/src/app/upload)" = ca451a4e && test -d /usr/src/app/upload/upload
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 65534
runAsGroup: 65534
persistence:
library:
existingClaim: '{{ .Values.immich.persistence.library.existingClaim }}'
advancedMounts:
main:
check-bcachefs-library:
- path: /usr/src/app/upload
readOnly: true
valkey:
enabled: true
persistence:
data:
enabled: true
type: persistentVolumeClaim
accessMode: ReadWriteOnce
size: 2Gi
storageClass: rook-ceph-block-ec
+40
View File
@@ -0,0 +1,40 @@
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: cnpg-operator
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "-3"
spec:
description: CloudNativePG operator and its required cluster-scoped resources
sourceRepos:
- ghcr.io/cloudnative-pg/charts
destinations:
- namespace: cnpg-system
server: https://kubernetes.default.svc
clusterResourceWhitelist:
- group: apiextensions.k8s.io
kind: CustomResourceDefinition
- group: rbac.authorization.k8s.io
kind: ClusterRole
- group: rbac.authorization.k8s.io
kind: ClusterRoleBinding
- group: admissionregistration.k8s.io
kind: MutatingWebhookConfiguration
- group: admissionregistration.k8s.io
kind: ValidatingWebhookConfiguration
namespaceResourceWhitelist:
- group: ""
kind: ConfigMap
- group: ""
kind: Secret
- group: ""
kind: Service
- group: ""
kind: ServiceAccount
- group: apps
kind: Deployment
- group: rbac.authorization.k8s.io
kind: Role
- group: rbac.authorization.k8s.io
kind: RoleBinding
+26
View File
@@ -0,0 +1,26 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: cnpg-operator
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "-2"
spec:
project: cnpg-operator
source:
repoURL: ghcr.io/cloudnative-pg/charts
chart: cloudnative-pg
targetRevision: 0.29.1
helm:
releaseName: cnpg-operator
destination:
server: https://kubernetes.default.svc
namespace: cnpg-system
syncPolicy:
automated:
enabled: true
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+23
View File
@@ -0,0 +1,23 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: immich-postgresql
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "-1"
spec:
project: immich
source:
repoURL: https://code.brunner.ninja/feedc0de/homelab-gitops.git
targetRevision: main
path: apps/immich-postgresql
destination:
server: https://kubernetes.default.svc
namespace: immich
syncPolicy:
automated:
enabled: true
prune: false
selfHeal: true
syncOptions:
- CreateNamespace=true
+37
View File
@@ -0,0 +1,37 @@
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: immich
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "-3"
spec:
description: Immich, its database, and host-local library binding
sourceRepos:
- https://code.brunner.ninja/feedc0de/homelab-gitops.git
- ghcr.io/immich-app/immich-charts
destinations:
- namespace: immich
server: https://kubernetes.default.svc
clusterResourceWhitelist:
- group: ""
kind: PersistentVolume
namespaceResourceWhitelist:
- group: ""
kind: ConfigMap
- group: ""
kind: PersistentVolumeClaim
- group: ""
kind: Service
- group: ""
kind: ServiceAccount
- group: apps
kind: Deployment
- group: networking.k8s.io
kind: Ingress
- group: monitoring.coreos.com
kind: ServiceMonitor
- group: postgresql.cnpg.io
kind: Cluster
- group: traefik.io
kind: IngressRoute
+31
View File
@@ -0,0 +1,31 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: immich
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "0"
spec:
project: immich
sources:
- repoURL: ghcr.io/immich-app/immich-charts
chart: immich
targetRevision: 0.13.4
helm:
releaseName: immich
valueFiles:
- $values/apps/immich/values.yaml
- repoURL: https://code.brunner.ninja/feedc0de/homelab-gitops.git
targetRevision: main
ref: values
path: apps/immich/resources
destination:
server: https://kubernetes.default.svc
namespace: immich
syncPolicy:
automated:
enabled: true
prune: false
selfHeal: true
syncOptions:
- CreateNamespace=true
+5
View File
@@ -7,6 +7,11 @@ resources:
- grafana-project.yaml
- homeassistant-postgresql-project.yaml
- platform-project.yaml
- cnpg-operator-project.yaml
- cnpg-operator.yaml
- immich-project.yaml
- immich-postgresql.yaml
- immich.yaml
- sealed-secrets.yaml
- immich-sync.yaml
- immichframe.yaml
+1
View File
@@ -7,6 +7,7 @@ spec:
description: Cluster services installed from maintained upstream charts
sourceRepos:
- https://bitnami.github.io/sealed-secrets
- https://cloudnative-pg.github.io/charts
destinations:
- namespace: sealed-secrets
server: https://kubernetes.default.svc