C2(migrate-immich-to-ringtail): impl add immich Deployments + bump GPU time-slicing

- argocd/manifests/immich-ringtail/: full port of the immich stack
  (server, ML, valkey, services, ingress, pvc-ml-cache) from
  argocd/manifests/immich/, with ringtail-specific tweaks:
  - deployment-ml: runtimeClassName=nvidia, nvidia.com/gpu:1 limit,
    -cuda image tag
  - deployment-valkey + kustomization: drop the
    registry.ops.eblu.me/blumeops/valkey mirror (arm64-only), use
    upstream docker.io/valkey/valkey:8.1.6 (multi-arch)
  - ingress-tailscale: tls.hosts=[photos-ringtail] for staging
- argocd/apps/immich-ringtail.yaml: new ArgoCD app (manual sync,
  ringtail destination)
- argocd/manifests/nvidia-device-plugin/time-slicing-config.yaml:
  bump replicas 2 -> 4 so the ringtail GPU can be shared by
  frigate + ollama + immich-ml

The immich-db Secret in the immich namespace is created manually
(matching minikube pattern) — see argocd/apps/immich-ringtail.yaml
header for the procedure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erich Blume 2026-05-13 13:14:07 -07:00
commit 5a9596c7d9
11 changed files with 328 additions and 3 deletions

View file

@ -0,0 +1,31 @@
# Immich on ringtail k3s.
#
# Staging deployment; the minikube `immich` app remains in parallel
# until cutover. See [[immich-cutover-and-decommission]] for the
# routing flip + minikube cleanup.
#
# Prerequisites:
# - cnpg-on-ringtail + databases-ringtail (postgres)
# - 1password-connect-ringtail + external-secrets-ringtail (not used
# by this app today — immich-db Secret is created manually,
# matching the minikube pattern)
# - The immich-db Secret in the immich namespace, holding the
# password for the `immich` postgres role (copied from the source
# immich-pg-app Secret at migration time).
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: immich-ringtail
namespace: argocd
spec:
project: default
source:
repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git
targetRevision: main
path: argocd/manifests/immich-ringtail
destination:
server: https://ringtail.tail8d86e.ts.net:6443
namespace: immich
syncPolicy:
syncOptions:
- CreateNamespace=true

View file

@ -0,0 +1,69 @@
---
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:
runtimeClassName: nvidia
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: machine-learning
# ringtail uses the -cuda tag (set in kustomization.yaml)
# to take advantage of the RTX 4080 via the nvidia
# device plugin. Time-slicing is configured for 4 replicas
# so frigate + ollama + this pod can share.
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"
nvidia.com/gpu: "1"
volumes:
- name: cache
persistentVolumeClaim:
claimName: immich-ml-cache

View file

@ -0,0 +1,74 @@
---
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:
securityContext:
seccompProfile:
type: RuntimeDefault
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

View file

@ -0,0 +1,42 @@
---
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:
securityContext:
seccompProfile:
type: RuntimeDefault
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

View file

@ -0,0 +1,37 @@
# Tailscale ProxyGroup Ingress for Immich on ringtail.
#
# Staging hostname: photos-ringtail.tail8d86e.ts.net
# The minikube ingress claims the "photos" Tailscale device name.
# Tailscale enforces uniqueness across the tailnet, so this ingress
# uses photos-ringtail until the minikube ingress is torn down at
# cutover. See [[immich-cutover-and-decommission]] for the rename.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: immich-tailscale
namespace: immich
annotations:
tailscale.com/funnel: "false"
tailscale.com/proxy-group: "ingress"
gethomepage.dev/enabled: "true"
gethomepage.dev/name: "Immich"
gethomepage.dev/group: "Content"
gethomepage.dev/icon: "immich.png"
gethomepage.dev/description: "Photo management"
gethomepage.dev/href: "https://photos.ops.eblu.me"
gethomepage.dev/pod-selector: "app=immich,component=server"
spec:
ingressClassName: tailscale
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: immich-server
port:
number: 2283
tls:
- hosts:
- photos-ringtail

View file

@ -3,8 +3,26 @@ kind: Kustomization
namespace: immich
# Storage scaffolding for the ringtail-side Immich deployment.
# The Deployments/Services/Ingress land in immich-app-on-ringtail.
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
# CUDA variant of the same release — ringtail has an RTX 4080
newTag: v2.6.3-cuda
# Using upstream multi-arch valkey image directly; the
# registry.ops.eblu.me/blumeops/valkey mirror is arm64-only (built
# on indri) and would crashloop on ringtail.
- name: docker.io/valkey/valkey
newTag: "8.1.6"

View file

@ -0,0 +1,12 @@
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: immich-ml-cache
namespace: immich
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -11,4 +11,4 @@ data:
timeSlicing:
resources:
- name: nvidia.com/gpu
replicas: 2
replicas: 4