From 64200a55c5e19d5880ee83b8f50ef09306c12d06 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sat, 4 Apr 2026 09:42:25 -0700 Subject: [PATCH] =?UTF-8?q?Migrate=20Immich=20from=20Helm=20chart=20to=20k?= =?UTF-8?q?ustomize=20manifests=20(v2.5.6=20=E2=86=92=20v2.6.3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the Helm chart deployment with plain kustomize manifests following the Authentik pattern (separate deployments per component). Consolidate the immich-storage ArgoCD app into the main immich app. Add no-helm-policy doc establishing kustomize as the standard deployment mechanism. Co-Authored-By: Claude Opus 4.6 (1M context) --- argocd/apps/immich-storage.yaml | 25 ----- argocd/apps/immich.yaml | 29 +++--- argocd/manifests/immich/README.md | 46 ++++++---- argocd/manifests/immich/deployment-ml.yaml | 60 ++++++++++++ .../manifests/immich/deployment-server.yaml | 71 +++++++++++++++ .../manifests/immich/deployment-valkey.yaml | 39 ++++++++ argocd/manifests/immich/kustomization.yaml | 19 +++- argocd/manifests/immich/pvc-ml-cache.yaml | 12 +++ argocd/manifests/immich/service-ml.yaml | 14 +++ argocd/manifests/immich/service-valkey.yaml | 14 +++ argocd/manifests/immich/service.yaml | 14 +++ argocd/manifests/immich/values.yaml | 91 ------------------- .../immich-kustomize-v2.6.3.infra.md | 1 + docs/explanation/no-helm-policy.md | 46 ++++++++++ docs/how-to/knowledgebase/review-services.md | 5 + docs/reference/kubernetes/apps.md | 4 +- docs/reference/services/immich.md | 4 +- mise-tasks/service-review | 2 +- service-versions.yaml | 6 +- 19 files changed, 340 insertions(+), 162 deletions(-) delete mode 100644 argocd/apps/immich-storage.yaml create mode 100644 argocd/manifests/immich/deployment-ml.yaml create mode 100644 argocd/manifests/immich/deployment-server.yaml create mode 100644 argocd/manifests/immich/deployment-valkey.yaml create mode 100644 argocd/manifests/immich/pvc-ml-cache.yaml create mode 100644 argocd/manifests/immich/service-ml.yaml create mode 100644 argocd/manifests/immich/service-valkey.yaml create mode 100644 argocd/manifests/immich/service.yaml delete mode 100644 argocd/manifests/immich/values.yaml create mode 100644 docs/changelog.d/immich-kustomize-v2.6.3.infra.md create mode 100644 docs/explanation/no-helm-policy.md diff --git a/argocd/apps/immich-storage.yaml b/argocd/apps/immich-storage.yaml deleted file mode 100644 index 7227681..0000000 --- a/argocd/apps/immich-storage.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Immich Storage - PersistentVolume and PVC for photo library -# Must be synced BEFORE the main immich app -# -# Prerequisites: -# 1. NFS share on sifaka at /volume1/photos with permissions for indri -apiVersion: argoproj.io/v1alpha1 -kind: Application -metadata: - name: immich-storage - namespace: argocd -spec: - project: default - source: - repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git - targetRevision: main - path: argocd/manifests/immich - # Only deploy storage resources (PV/PVC/Ingress), not Helm values.yaml - directory: - include: "{pv-nfs.yaml,pvc.yaml,ingress-tailscale.yaml}" - destination: - server: https://kubernetes.default.svc - namespace: immich - syncPolicy: - syncOptions: - - CreateNamespace=true diff --git a/argocd/apps/immich.yaml b/argocd/apps/immich.yaml index 22b95cc..7efd263 100644 --- a/argocd/apps/immich.yaml +++ b/argocd/apps/immich.yaml @@ -1,15 +1,16 @@ # Immich - Self-hosted photo and video management # High-performance Google Photos/iCloud alternative with AI features # -# Chart mirrored from https://github.com/immich-app/immich-charts to forge +# Kustomize manifests in argocd/manifests/immich/ +# Components: server, machine-learning, valkey (Redis) # # Prerequisites: -# 1. Mirror immich-charts to forge: https://github.com/immich-app/immich-charts -# 2. Create immich namespace and secrets: +# 1. Create immich namespace and secrets: # kubectl create namespace immich -# op inject -i argocd/manifests/immich/secret-db.yaml.tpl | kubectl apply -f - -# 3. Create immich-pg database and user (see immich-pg app) -# 4. Mount photos directory from indri to minikube +# kubectl --context=minikube-indri create secret generic immich-db -n immich \ +# --from-literal=password="$(kubectl --context=minikube-indri -n databases get secret immich-pg-app -o jsonpath='{.data.password}' | base64 -d)" +# 2. Create immich-pg database and user (see immich-pg app) +# 3. NFS share on sifaka at /volume1/photos with read/write for indri apiVersion: argoproj.io/v1alpha1 kind: Application metadata: @@ -17,18 +18,10 @@ metadata: namespace: argocd spec: project: default - sources: - # Helm chart from forge mirror (SSH via egress) - - repoURL: ssh://forgejo@forge.ops.eblu.me:2222/mirrors/immich-charts.git - targetRevision: immich-0.10.3 - path: charts/immich - helm: - releaseName: immich - valueFiles: - - $values/argocd/manifests/immich/values.yaml - - repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git - targetRevision: main - ref: values + source: + repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git + targetRevision: main + path: argocd/manifests/immich destination: server: https://kubernetes.default.svc namespace: immich diff --git a/argocd/manifests/immich/README.md b/argocd/manifests/immich/README.md index 76e8ac9..a82a856 100644 --- a/argocd/manifests/immich/README.md +++ b/argocd/manifests/immich/README.md @@ -11,11 +11,18 @@ Self-hosted photo and video management solution with AI-powered search and face ## Deployment Order 1. Sync `blumeops-pg` (to get CloudNativePG operator if not already running) -2. Sync `immich-storage` (creates PV, PVC, and Tailscale Ingress) -3. Wait for `immich-pg` cluster to be healthy -4. Create secrets (see below) -5. Sync `immich` (deploys the Helm chart) -6. Run `mise run provision-indri -- --tags caddy` to update Caddy config +2. Wait for `immich-pg` cluster to be healthy +3. Create secrets (see below) +4. Sync `immich` (deploys all resources: storage, services, deployments) +5. Run `mise run provision-indri -- --tags caddy` to update Caddy config + +## Components + +| Component | Deployment | Service | Port | +|-----------|------------|---------|------| +| Server (web/API) | `immich-server` | `immich-server` | 2283 | +| Machine Learning | `immich-machine-learning` | `immich-machine-learning` | 3003 | +| Valkey (Redis) | `immich-valkey` | `immich-valkey` | 6379 | ## Secret Setup @@ -72,30 +79,37 @@ To import existing photos from iCloud sync on indri: └─────────────────┘ ``` -## Helm Values +## Version Management -The Helm chart is configured via `values.yaml`. Key settings: +Image versions are controlled via `kustomization.yaml`: -- `image.tag`: Immich version (update manually) -- `immich.persistence.library.existingClaim`: Points to `immich-library` PVC -- `machine-learning.enabled`: AI features for face/object recognition -- `valkey.enabled`: Redis cache included in chart +```yaml +images: + - name: ghcr.io/immich-app/immich-server + newTag: v2.6.3 + - name: ghcr.io/immich-app/immich-machine-learning + newTag: v2.6.3 + - name: docker.io/valkey/valkey + newTag: "8.1-alpine" +``` + +To upgrade, update `newTag` values and sync via ArgoCD. ## Troubleshooting ```bash # Check pods -kubectl -n immich get pods +kubectl --context=minikube-indri -n immich get pods # Check immich-pg cluster -kubectl -n databases get cluster immich-pg +kubectl --context=minikube-indri -n databases get cluster immich-pg # View server logs -kubectl -n immich logs -l app.kubernetes.io/name=immich-server +kubectl --context=minikube-indri -n immich logs -l app=immich,component=server # View ML logs -kubectl -n immich logs -l app.kubernetes.io/name=immich-machine-learning +kubectl --context=minikube-indri -n immich logs -l app=immich,component=machine-learning # Check PVC binding -kubectl -n immich get pvc +kubectl --context=minikube-indri -n immich get pvc ``` diff --git a/argocd/manifests/immich/deployment-ml.yaml b/argocd/manifests/immich/deployment-ml.yaml new file mode 100644 index 0000000..d55898d --- /dev/null +++ b/argocd/manifests/immich/deployment-ml.yaml @@ -0,0 +1,60 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: immich-machine-learning + namespace: immich +spec: + replicas: 1 + selector: + matchLabels: + app: immich + component: machine-learning + template: + metadata: + labels: + app: immich + component: machine-learning + spec: + containers: + - name: machine-learning + image: ghcr.io/immich-app/immich-machine-learning:kustomized + ports: + - name: http + containerPort: 3003 + env: + - name: TZ + value: "America/Los_Angeles" + - name: TRANSFORMERS_CACHE + value: /cache + - name: HF_XET_CACHE + value: /cache/huggingface-xet + - name: MPLCONFIGDIR + value: /cache/matplotlib-config + volumeMounts: + - name: cache + mountPath: /cache + livenessProbe: + httpGet: + path: /ping + port: 3003 + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: /ping + port: 3003 + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 5 + resources: + requests: + memory: "512Mi" + cpu: "100m" + limits: + memory: "4Gi" + volumes: + - name: cache + persistentVolumeClaim: + claimName: immich-ml-cache diff --git a/argocd/manifests/immich/deployment-server.yaml b/argocd/manifests/immich/deployment-server.yaml new file mode 100644 index 0000000..56e920a --- /dev/null +++ b/argocd/manifests/immich/deployment-server.yaml @@ -0,0 +1,71 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: immich-server + namespace: immich +spec: + replicas: 1 + selector: + matchLabels: + app: immich + component: server + template: + metadata: + labels: + app: immich + component: server + spec: + containers: + - name: server + image: ghcr.io/immich-app/immich-server:kustomized + ports: + - name: http + containerPort: 2283 + env: + - name: TZ + value: "America/Los_Angeles" + - name: DB_HOSTNAME + value: "immich-pg-rw.databases.svc.cluster.local" + - name: DB_PORT + value: "5432" + - name: DB_DATABASE_NAME + value: "immich" + - name: DB_USERNAME + value: "immich" + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: immich-db + key: password + - name: REDIS_HOSTNAME + value: immich-valkey + - name: IMMICH_MACHINE_LEARNING_URL + value: "http://immich-machine-learning:3003" + volumeMounts: + - name: library + mountPath: /usr/src/app/upload + livenessProbe: + httpGet: + path: /api/server/ping + port: 2283 + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: /api/server/ping + port: 2283 + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 5 + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "2Gi" + volumes: + - name: library + persistentVolumeClaim: + claimName: immich-library diff --git a/argocd/manifests/immich/deployment-valkey.yaml b/argocd/manifests/immich/deployment-valkey.yaml new file mode 100644 index 0000000..4034f94 --- /dev/null +++ b/argocd/manifests/immich/deployment-valkey.yaml @@ -0,0 +1,39 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: immich-valkey + namespace: immich +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: immich + component: valkey + template: + metadata: + labels: + app: immich + component: valkey + spec: + containers: + - name: valkey + image: docker.io/valkey/valkey:kustomized + ports: + - name: redis + containerPort: 6379 + volumeMounts: + - name: data + mountPath: /data + resources: + requests: + memory: "64Mi" + cpu: "25m" + limits: + memory: "256Mi" + volumes: + - name: data + emptyDir: + sizeLimit: 1Gi diff --git a/argocd/manifests/immich/kustomization.yaml b/argocd/manifests/immich/kustomization.yaml index 1c1c6d8..c7c54e1 100644 --- a/argocd/manifests/immich/kustomization.yaml +++ b/argocd/manifests/immich/kustomization.yaml @@ -1,11 +1,22 @@ -# Immich non-Helm resources (storage) -# These must be deployed before the Helm chart +--- apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization - namespace: immich - resources: + - deployment-server.yaml + - deployment-ml.yaml + - deployment-valkey.yaml + - service.yaml + - service-ml.yaml + - service-valkey.yaml + - pvc-ml-cache.yaml - pv-nfs.yaml - pvc.yaml - ingress-tailscale.yaml +images: + - name: ghcr.io/immich-app/immich-server + newTag: v2.6.3 + - name: ghcr.io/immich-app/immich-machine-learning + newTag: v2.6.3 + - name: docker.io/valkey/valkey + newTag: "8.1-alpine" diff --git a/argocd/manifests/immich/pvc-ml-cache.yaml b/argocd/manifests/immich/pvc-ml-cache.yaml new file mode 100644 index 0000000..1e5a3d6 --- /dev/null +++ b/argocd/manifests/immich/pvc-ml-cache.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: immich-ml-cache + namespace: immich +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi diff --git a/argocd/manifests/immich/service-ml.yaml b/argocd/manifests/immich/service-ml.yaml new file mode 100644 index 0000000..9bb935a --- /dev/null +++ b/argocd/manifests/immich/service-ml.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: immich-machine-learning + namespace: immich +spec: + selector: + app: immich + component: machine-learning + ports: + - name: http + port: 3003 + targetPort: 3003 diff --git a/argocd/manifests/immich/service-valkey.yaml b/argocd/manifests/immich/service-valkey.yaml new file mode 100644 index 0000000..eb42d3b --- /dev/null +++ b/argocd/manifests/immich/service-valkey.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: immich-valkey + namespace: immich +spec: + selector: + app: immich + component: valkey + ports: + - name: redis + port: 6379 + targetPort: 6379 diff --git a/argocd/manifests/immich/service.yaml b/argocd/manifests/immich/service.yaml new file mode 100644 index 0000000..d35410f --- /dev/null +++ b/argocd/manifests/immich/service.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: immich-server + namespace: immich +spec: + selector: + app: immich + component: server + ports: + - name: http + port: 2283 + targetPort: 2283 diff --git a/argocd/manifests/immich/values.yaml b/argocd/manifests/immich/values.yaml deleted file mode 100644 index 7d23cb8..0000000 --- a/argocd/manifests/immich/values.yaml +++ /dev/null @@ -1,91 +0,0 @@ -# Immich Helm values for blumeops -# Chart: https://github.com/immich-app/immich-charts (v0.10.3) -# -# Immich requires: -# - PostgreSQL with VectorChord extension (separate immich-pg cluster) -# - Redis/Valkey (included in chart) -# - Library storage PVC (photos directory from sifaka NFS) - -# Shared environment variables -env: - TZ: "America/Los_Angeles" - -# Shared controller settings - image tag and DB connection -controllers: - main: - containers: - main: - image: - tag: v2.5.6 - env: - DB_HOSTNAME: "immich-pg-rw.databases.svc.cluster.local" - DB_PORT: "5432" - DB_DATABASE_NAME: "immich" - DB_USERNAME: "immich" - DB_PASSWORD: - valueFrom: - secretKeyRef: - name: immich-db - key: password - -# Immich server configuration -immich: - persistence: - library: - existingClaim: immich-library - -# Machine Learning service -machine-learning: - enabled: true - controllers: - main: - containers: - main: - resources: - requests: - memory: "512Mi" - cpu: "100m" - limits: - memory: "4Gi" - probes: - liveness: - spec: - timeoutSeconds: 5 - readiness: - spec: - timeoutSeconds: 5 - persistence: - cache: - enabled: true - type: persistentVolumeClaim - accessMode: ReadWriteOnce - size: 10Gi - -# Valkey (Redis fork) - included in chart -valkey: - enabled: true - persistence: - data: - enabled: true - type: emptyDir - size: 1Gi - -# Server configuration -server: - controllers: - main: - containers: - main: - resources: - requests: - memory: "256Mi" - cpu: "100m" - limits: - memory: "2Gi" - probes: - liveness: - spec: - timeoutSeconds: 5 - readiness: - spec: - timeoutSeconds: 5 diff --git a/docs/changelog.d/immich-kustomize-v2.6.3.infra.md b/docs/changelog.d/immich-kustomize-v2.6.3.infra.md new file mode 100644 index 0000000..4d42094 --- /dev/null +++ b/docs/changelog.d/immich-kustomize-v2.6.3.infra.md @@ -0,0 +1 @@ +Migrate Immich from Helm chart to kustomize manifests and upgrade from v2.5.6 to v2.6.3 diff --git a/docs/explanation/no-helm-policy.md b/docs/explanation/no-helm-policy.md new file mode 100644 index 0000000..73f6835 --- /dev/null +++ b/docs/explanation/no-helm-policy.md @@ -0,0 +1,46 @@ +--- +title: No Helm Policy +modified: 2026-04-04 +tags: + - explanation + - kubernetes +--- + +# No Helm Policy + +BlumeOps avoids Helm charts as a deployment mechanism. Plain kustomize manifests are the standard for all services. + +## Rationale + +Helm templates add a layer of abstraction that works against the simplicity of Kubernetes YAML manifests. Go templates embedded in YAML are hard to read, hard to diff, and hard to reason about. A manifest should be a manifest — not a program that generates one. + +Kustomize overlays preserve the readability of plain YAML while providing the composition and patching features needed for environment-specific configuration. Version bumps are a one-line `newTag` edit in `kustomization.yaml`, and `kubectl diff` shows exactly what will change. + +## Current State + +All services in blumeops use kustomize manifests except: + +- **1Password Connect** — still deployed via Helm chart (`connect-helm-charts v2.3.0`). Migration is a future goal. + +## Migration History + +Services previously deployed via Helm that have been migrated to kustomize: + +| Service | Migrated | Notes | +|---------|----------|-------| +| Grafana | 2026-02 | Converted during v12.x upgrade | +| CloudNative-PG | 2026-02 | Switched to upstream release manifest via forge mirror | +| External Secrets | 2026-03 | Static manifests rendered from chart | +| Homepage | 2025-12 | Replaced chart with plain manifests | +| Immich | 2026-04 | Converted during v2.6.3 upgrade | + +## Guidelines + +- **Do not introduce new Helm chart dependencies.** When deploying a new service, write kustomize manifests directly — even if the upstream project provides a Helm chart. The chart's `helm template` output is a fine starting point for writing those manifests. +- **When upgrading a Helm-based service**, consider whether it's a good time to migrate off Helm as part of the upgrade. +- **Upstream manifests** can be referenced directly in `kustomization.yaml` resources (like ArgoCD and Tailscale operator do) or applied via ArgoCD's `directory.include` (like CloudNative-PG). Both avoid Helm. + +## Related + +- [[review-services]] — Service review process +- [[architecture]] — Overall infrastructure design diff --git a/docs/how-to/knowledgebase/review-services.md b/docs/how-to/knowledgebase/review-services.md index 8a2fc0d..de0a970 100644 --- a/docs/how-to/knowledgebase/review-services.md +++ b/docs/how-to/knowledgebase/review-services.md @@ -118,8 +118,13 @@ After reviewing, edit `service-versions.yaml` (repo root) and update the service Commit this change alongside any upgrades you make during the review. +## Deployment Policy + +BlumeOps uses kustomize manifests for all services. Helm charts should not be introduced for new services. See [[no-helm-policy]] for rationale and migration history. + ## Related +- [[no-helm-policy]] - Why blumeops avoids Helm charts - [[review-documentation]] - Periodically review documentation cards - [[deploy-k8s-service]] - Deploy changes to Kubernetes services - [[build-container-image]] - Build and release custom container images diff --git a/docs/reference/kubernetes/apps.md b/docs/reference/kubernetes/apps.md index 02215fc..e162c7a 100644 --- a/docs/reference/kubernetes/apps.md +++ b/docs/reference/kubernetes/apps.md @@ -24,9 +24,9 @@ Registry of all applications deployed via [[argocd]]. | `blumeops-pg` | databases | `argocd/manifests/databases/` | [[postgresql]] | | `prometheus` | monitoring | `argocd/manifests/prometheus/` | [[prometheus]] | | `loki` | monitoring | `argocd/manifests/loki/` | [[loki]] | -| `grafana` | monitoring | Helm chart (forge mirror) | [[grafana]] | +| `grafana` | monitoring | `argocd/manifests/grafana/` | [[grafana]] | | `grafana-config` | monitoring | `argocd/manifests/grafana-config/` | [[grafana]] | -| `immich` | immich | Helm chart | [[immich]] | +| `immich` | immich | `argocd/manifests/immich/` | [[immich]] | | `tempo` | monitoring | `argocd/manifests/tempo/` | [[tempo]] | | `alloy-k8s` | alloy | `argocd/manifests/alloy-k8s/` | [[alloy|Alloy]] | | `alloy-tracing-ringtail` | alloy | `argocd/manifests/alloy-tracing-ringtail/` | [[alloy|Alloy]] (eBPF tracing) | diff --git a/docs/reference/services/immich.md b/docs/reference/services/immich.md index 740dfa4..063deac 100644 --- a/docs/reference/services/immich.md +++ b/docs/reference/services/immich.md @@ -1,6 +1,6 @@ --- title: Immich -modified: 2026-02-07 +modified: 2026-04-04 last-reviewed: 2026-03-23 tags: - service @@ -17,7 +17,7 @@ Self-hosted photo and video management. |----------|-------| | **URL** | https://photos.ops.eblu.me | | **Namespace** | `immich` | -| **Deployment** | Helm chart (k8s) | +| **Deployment** | Kustomize (k8s) | | **Database** | [[postgresql]] (CNPG) | | **Storage** | [[sifaka|Sifaka]] photos volume | diff --git a/mise-tasks/service-review b/mise-tasks/service-review index 1bc2ae4..28b6dc4 100755 --- a/mise-tasks/service-review +++ b/mise-tasks/service-review @@ -169,7 +169,7 @@ def main( if svc_type == "argocd": checklist_parts += [ "\n[bold]ArgoCD Deployment:[/bold]\n", - "• Update image tag or Helm chart version in argocd/manifests/\n", + "• Update image tag in argocd/manifests//kustomization.yaml\n", f"• Verify sync status: argocd app get {top_svc['name']}\n", ] elif svc_type == "ansible": diff --git a/service-versions.yaml b/service-versions.yaml index f3a8394..3ad22ff 100644 --- a/service-versions.yaml +++ b/service-versions.yaml @@ -120,10 +120,10 @@ services: - name: immich type: argocd - last-reviewed: 2026-02-25 - current-version: "v2.5.6" + last-reviewed: 2026-04-04 + current-version: "v2.6.3" upstream-source: https://github.com/immich-app/immich/releases - notes: Deployed via Helm chart + notes: Kustomize manifests with upstream images - name: external-secrets type: argocd