Add Immich photo management service
Deploy Immich via Helm chart with: - PostgreSQL cluster with pgvecto.rs (immich-pg) for AI vector search - NFS storage on sifaka for photo library - Tailscale Ingress + Caddy proxy for photos.ops.eblu.me access - Machine learning service for face/object recognition Immich provides a self-hosted Google Photos/iCloud alternative with AI-powered search, face recognition, and support for RAW files. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
dbc47d0231
commit
5482f74500
13 changed files with 401 additions and 0 deletions
|
|
@ -58,6 +58,9 @@ caddy_services:
|
|||
- name: teslamate
|
||||
host: "tesla.{{ caddy_domain }}"
|
||||
backend: "https://tesla.tail8d86e.ts.net"
|
||||
- name: immich
|
||||
host: "photos.{{ caddy_domain }}"
|
||||
backend: "https://photos.tail8d86e.ts.net"
|
||||
|
||||
# Layer 4 (TCP) services
|
||||
# Format: { port: external_port, backend: "host:port" }
|
||||
|
|
|
|||
25
argocd/apps/immich-storage.yaml
Normal file
25
argocd/apps/immich-storage.yaml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# 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 kustomization resources (PV/PVC/Ingress), not values.yaml
|
||||
directory:
|
||||
include: "{kustomization.yaml,pv-nfs.yaml,pvc.yaml,ingress-tailscale.yaml}"
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: immich
|
||||
syncPolicy:
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
38
argocd/apps/immich.yaml
Normal file
38
argocd/apps/immich.yaml
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# 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
|
||||
#
|
||||
# Prerequisites:
|
||||
# 1. Mirror immich-charts to forge: https://github.com/immich-app/immich-charts
|
||||
# 2. 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
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: immich
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
sources:
|
||||
# Helm chart from forge mirror (SSH via egress)
|
||||
- repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/immich-charts.git
|
||||
targetRevision: immich-0.10.0
|
||||
path: charts/immich
|
||||
helm:
|
||||
releaseName: immich
|
||||
valueFiles:
|
||||
- $values/argocd/manifests/immich/values.yaml
|
||||
# Values from our git repo
|
||||
- repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: immich
|
||||
syncPolicy:
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
|
|
@ -2,6 +2,13 @@
|
|||
|
||||
PostgreSQL clusters managed by CloudNativePG operator.
|
||||
|
||||
## Clusters
|
||||
|
||||
| Cluster | Image | Purpose |
|
||||
|---------|-------|---------|
|
||||
| blumeops-pg | cloudnative-pg/postgresql:18 | General services (miniflux, teslamate) |
|
||||
| immich-pg | tensorchord/cloudnative-vectorchord:17 | Immich (requires pgvecto.rs extension) |
|
||||
|
||||
## blumeops-pg
|
||||
|
||||
Single-instance PostgreSQL cluster for blumeops services.
|
||||
|
|
@ -99,3 +106,35 @@ from brew PostgreSQL (indri) to this k8s cluster. At that point:
|
|||
1. Delete `service-tailscale.yaml` (the `k8s-pg` service)
|
||||
2. Update/create a service with `tailscale.com/hostname: "pg"`
|
||||
3. Verify the orphaned `k8s-pg` device is removed from tailnet
|
||||
|
||||
## immich-pg
|
||||
|
||||
PostgreSQL cluster for Immich with pgvecto.rs extension for AI-powered vector search.
|
||||
|
||||
### Configuration
|
||||
|
||||
- **Instances**: 1 (single-node for minikube)
|
||||
- **Storage**: 10Gi on `standard` storage class
|
||||
- **Image**: `tensorchord/cloudnative-vectorchord:17-v0.4.0` (includes pgvecto.rs)
|
||||
- **Extensions**: `vectors`, `earthdistance`
|
||||
|
||||
### Connection
|
||||
|
||||
Immich connects via `immich-pg-rw.databases.svc.cluster.local:5432`.
|
||||
|
||||
The `immich` user password is auto-generated by CloudNativePG and stored in `immich-pg-app` secret:
|
||||
|
||||
```bash
|
||||
# Get immich app credentials
|
||||
kubectl -n databases get secret immich-pg-app -o jsonpath='{.data.password}' | base64 -d
|
||||
```
|
||||
|
||||
### Status
|
||||
|
||||
```bash
|
||||
# Check cluster health
|
||||
kubectl -n databases get cluster immich-pg
|
||||
|
||||
# Check pods
|
||||
kubectl -n databases get pods -l cnpg.io/cluster=immich-pg
|
||||
```
|
||||
|
|
|
|||
47
argocd/manifests/databases/immich-pg.yaml
Normal file
47
argocd/manifests/databases/immich-pg.yaml
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# PostgreSQL Cluster for Immich
|
||||
# Uses tensorchord/cloudnative-vectorchord for pgvecto.rs extension (required by Immich for AI features)
|
||||
# Managed by CloudNativePG operator
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: immich-pg
|
||||
namespace: databases
|
||||
spec:
|
||||
instances: 1
|
||||
# vectorchord image includes pgvecto.rs extension for vector similarity search
|
||||
imageName: tensorchord/cloudnative-vectorchord:17-v0.4.0
|
||||
|
||||
storage:
|
||||
size: 10Gi
|
||||
storageClass: standard
|
||||
|
||||
# Bootstrap creates initial database and owner
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: immich
|
||||
owner: immich
|
||||
postInitSQL:
|
||||
- CREATE EXTENSION IF NOT EXISTS vectors;
|
||||
- CREATE EXTENSION IF NOT EXISTS earthdistance CASCADE;
|
||||
|
||||
# Resource limits for minikube environment
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
|
||||
# PostgreSQL configuration
|
||||
postgresql:
|
||||
parameters:
|
||||
max_connections: "50"
|
||||
shared_buffers: "128MB"
|
||||
password_encryption: "scram-sha-256"
|
||||
# pgvecto.rs settings
|
||||
shared_preload_libraries: "vectors.so"
|
||||
pg_hba:
|
||||
# Allow connections from k8s pods
|
||||
- host all all 0.0.0.0/0 scram-sha-256
|
||||
- host all all ::/0 scram-sha-256
|
||||
|
|
@ -5,5 +5,6 @@ namespace: databases
|
|||
|
||||
resources:
|
||||
- blumeops-pg.yaml
|
||||
- immich-pg.yaml
|
||||
- service-tailscale.yaml
|
||||
- service-metrics-tailscale.yaml
|
||||
|
|
|
|||
98
argocd/manifests/immich/README.md
Normal file
98
argocd/manifests/immich/README.md
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
# Immich
|
||||
|
||||
Self-hosted photo and video management solution with AI-powered search and face recognition.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **NFS Share**: Create `/volume1/photos` on sifaka with NFS permissions for indri
|
||||
2. **PostgreSQL**: The `immich-pg` cluster (with pgvecto.rs) must be healthy
|
||||
3. **Secrets**: Create the database password secret
|
||||
|
||||
## 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
|
||||
|
||||
## Secret Setup
|
||||
|
||||
```bash
|
||||
# Create namespace
|
||||
kubectl create namespace immich
|
||||
|
||||
# Get the auto-generated immich password from CloudNativePG
|
||||
kubectl -n databases get secret immich-pg-app -o jsonpath='{.data.password}' | base64 -d
|
||||
|
||||
# Store that password in 1Password under blumeops/immich-pg, then:
|
||||
op inject -i argocd/manifests/immich/secret-db.yaml.tpl | kubectl apply -f -
|
||||
```
|
||||
|
||||
## Access
|
||||
|
||||
- **URL**: https://photos.ops.eblu.me (after Caddy is updated)
|
||||
- **Tailscale**: https://photos.tail8d86e.ts.net (direct)
|
||||
|
||||
## First-Time Setup
|
||||
|
||||
1. Navigate to https://photos.ops.eblu.me
|
||||
2. Create an admin account
|
||||
3. Configure external library (optional - for importing existing photos)
|
||||
|
||||
## External Library (iCloud Photos)
|
||||
|
||||
To import existing photos from iCloud sync on indri:
|
||||
|
||||
1. In Immich Admin > External Libraries, create a new library
|
||||
2. Set the import path to the location where iCloud photos sync
|
||||
3. Configure scan schedule or trigger manual scan
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ immich-server │────▶│ immich-pg │
|
||||
│ (web/api) │ │ (PostgreSQL │
|
||||
└────────┬────────┘ │ + pgvecto.rs) │
|
||||
│ └─────────────────┘
|
||||
│
|
||||
┌────────▼────────┐ ┌─────────────────┐
|
||||
│ immich-ml │ │ valkey │
|
||||
│ (ML inference) │ │ (Redis cache) │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
│
|
||||
┌────────▼────────┐
|
||||
│ sifaka NFS │
|
||||
│ /volume1/photos│
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
## Helm Values
|
||||
|
||||
The Helm chart is configured via `values.yaml`. Key settings:
|
||||
|
||||
- `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
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
```bash
|
||||
# Check pods
|
||||
kubectl -n immich get pods
|
||||
|
||||
# Check immich-pg cluster
|
||||
kubectl -n databases get cluster immich-pg
|
||||
|
||||
# View server logs
|
||||
kubectl -n immich logs -l app.kubernetes.io/name=immich-server
|
||||
|
||||
# View ML logs
|
||||
kubectl -n immich logs -l app.kubernetes.io/name=immich-machine-learning
|
||||
|
||||
# Check PVC binding
|
||||
kubectl -n immich get pvc
|
||||
```
|
||||
26
argocd/manifests/immich/ingress-tailscale.yaml
Normal file
26
argocd/manifests/immich/ingress-tailscale.yaml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Tailscale Ingress for Immich
|
||||
# Exposes Immich at photos.tail8d86e.ts.net
|
||||
# Caddy will proxy photos.ops.eblu.me to this endpoint
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: immich-tailscale
|
||||
namespace: immich
|
||||
annotations:
|
||||
tailscale.com/funnel: "false"
|
||||
spec:
|
||||
ingressClassName: tailscale
|
||||
rules:
|
||||
- host: photos
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: immich-server
|
||||
port:
|
||||
number: 2283
|
||||
tls:
|
||||
- hosts:
|
||||
- photos
|
||||
11
argocd/manifests/immich/kustomization.yaml
Normal file
11
argocd/manifests/immich/kustomization.yaml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Immich non-Helm resources (storage)
|
||||
# These must be deployed before the Helm chart
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: immich
|
||||
|
||||
resources:
|
||||
- pv-nfs.yaml
|
||||
- pvc.yaml
|
||||
- ingress-tailscale.yaml
|
||||
22
argocd/manifests/immich/pv-nfs.yaml
Normal file
22
argocd/manifests/immich/pv-nfs.yaml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# NFS PersistentVolume for Immich photo library
|
||||
# Requires: NFS share on sifaka at /volume1/photos with NFS permissions for indri
|
||||
#
|
||||
# To create on Synology:
|
||||
# 1. Control Panel > Shared Folder > Create
|
||||
# 2. Name: photos, Location: Volume 1
|
||||
# 3. Control Panel > File Services > NFS > NFS Rules
|
||||
# 4. Add rule for "photos" share: Hostname=indri, Privilege=Read/Write, Squash=No mapping
|
||||
apiVersion: v1
|
||||
kind: PersistentVolume
|
||||
metadata:
|
||||
name: immich-library-nfs-pv
|
||||
spec:
|
||||
capacity:
|
||||
storage: 2Ti
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
persistentVolumeReclaimPolicy: Retain
|
||||
storageClassName: ""
|
||||
nfs:
|
||||
server: sifaka
|
||||
path: /volume1/photos
|
||||
15
argocd/manifests/immich/pvc.yaml
Normal file
15
argocd/manifests/immich/pvc.yaml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# PersistentVolumeClaim for Immich photo library
|
||||
# Binds to the NFS PV for sifaka:/volume1/photos
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: immich-library
|
||||
namespace: immich
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
storageClassName: ""
|
||||
volumeName: immich-library-nfs-pv
|
||||
resources:
|
||||
requests:
|
||||
storage: 2Ti
|
||||
12
argocd/manifests/immich/secret-db.yaml.tpl
Normal file
12
argocd/manifests/immich/secret-db.yaml.tpl
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Immich database password secret
|
||||
# Apply with: op inject -i argocd/manifests/immich/secret-db.yaml.tpl | kubectl apply -f -
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: immich-db
|
||||
namespace: immich
|
||||
type: Opaque
|
||||
stringData:
|
||||
# Password is auto-generated by CloudNativePG and stored in immich-pg-app secret
|
||||
# Retrieve with: kubectl -n databases get secret immich-pg-app -o jsonpath='{.data.password}' | base64 -d
|
||||
password: "{{ op://blumeops/immich-pg/password }}"
|
||||
64
argocd/manifests/immich/values.yaml
Normal file
64
argocd/manifests/immich/values.yaml
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# Immich Helm values for blumeops
|
||||
# Chart: https://github.com/immich-app/immich-charts
|
||||
#
|
||||
# Immich requires:
|
||||
# - PostgreSQL with pgvecto.rs extension (separate immich-pg cluster)
|
||||
# - Redis/Valkey (included in chart)
|
||||
# - Library storage PVC (photos directory from indri)
|
||||
|
||||
# Image version - explicitly set to avoid drift
|
||||
image:
|
||||
tag: v1.125.7
|
||||
|
||||
# Environment variables for all components
|
||||
env:
|
||||
TZ: "America/Los_Angeles"
|
||||
# Database connection - uses immich-pg cluster
|
||||
DB_HOSTNAME: "immich-pg-rw.databases.svc.cluster.local"
|
||||
DB_PORT: "5432"
|
||||
DB_DATABASE_NAME: "immich"
|
||||
DB_USERNAME: "immich"
|
||||
# Password injected from secret
|
||||
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
|
||||
persistence:
|
||||
cache:
|
||||
type: persistentVolumeClaim
|
||||
accessMode: ReadWriteOnce
|
||||
size: 10Gi
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
|
||||
# Valkey (Redis fork) - included in chart
|
||||
valkey:
|
||||
enabled: true
|
||||
persistence:
|
||||
size: 1Gi
|
||||
|
||||
# Server resources for minikube
|
||||
server:
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "2000m"
|
||||
Loading…
Add table
Add a link
Reference in a new issue