From 46e82016afc5c196ecd8ba59437db2af793676e5 Mon Sep 17 00:00:00 2001 From: Oleksandr Berezovskyi Date: Sun, 22 Feb 2026 18:20:21 +0200 Subject: [PATCH] feat(k8s/immich): add immich stack (HelmRelease suspended for migration) --- kubernetes/app/immich/MIGRATION.md | 291 ++++++++++++++++++ kubernetes/app/immich/cronjob-backup.yaml | 132 ++++++++ kubernetes/app/immich/database.yaml | 69 +++++ kubernetes/app/immich/namespace.yaml | 4 + kubernetes/app/immich/networkpolicy.yaml | 94 ++++++ kubernetes/app/immich/pv-library.yaml | 17 + kubernetes/app/immich/pvc.yaml | 26 ++ kubernetes/app/immich/release.yaml | 82 +++++ kubernetes/app/immich/repository.yaml | 10 + kubernetes/app/immich/secret-backup.sops.yaml | 25 ++ kubernetes/app/immich/secret-rclone.sops.yaml | 22 ++ kubernetes/app/immich/secret.sops.yaml | 24 ++ kubernetes/app/immich/service.yaml | 16 + kubernetes/config/cluster-vars.sops.yaml | 6 +- 14 files changed, 816 insertions(+), 2 deletions(-) create mode 100644 kubernetes/app/immich/MIGRATION.md create mode 100644 kubernetes/app/immich/cronjob-backup.yaml create mode 100644 kubernetes/app/immich/database.yaml create mode 100644 kubernetes/app/immich/namespace.yaml create mode 100644 kubernetes/app/immich/networkpolicy.yaml create mode 100644 kubernetes/app/immich/pv-library.yaml create mode 100644 kubernetes/app/immich/pvc.yaml create mode 100644 kubernetes/app/immich/release.yaml create mode 100644 kubernetes/app/immich/repository.yaml create mode 100644 kubernetes/app/immich/secret-backup.sops.yaml create mode 100644 kubernetes/app/immich/secret-rclone.sops.yaml create mode 100644 kubernetes/app/immich/secret.sops.yaml create mode 100644 kubernetes/app/immich/service.yaml diff --git a/kubernetes/app/immich/MIGRATION.md b/kubernetes/app/immich/MIGRATION.md new file mode 100644 index 0000000..d5a9b76 --- /dev/null +++ b/kubernetes/app/immich/MIGRATION.md @@ -0,0 +1,291 @@ +# Immich Migration Guide + +This document describes how to migrate Immich from the existing Docker Compose stack (`docker/stacks/immich/`) to this Kubernetes deployment. + +## Overview + +The migration involves three categories of data: + +1. **PostgreSQL database** — all Immich metadata, users, albums, face recognition data, search vectors +2. **Photo library** — already on Synology NAS via NFS, no data movement needed +3. **Ephemeral state** (ML model cache, Redis/Valkey) — will be recreated automatically + +Only the PostgreSQL database requires active migration. Everything else either stays in place (photos) or regenerates on first start (model cache, Valkey). + +### What Changes + +| Aspect | Docker Compose | Kubernetes | +|---|---|---| +| Orchestration | Docker Compose via Portainer | Helm chart via Flux CD HelmRelease | +| PostgreSQL | Container with bind mount | StatefulSet with NFS-backed PVC | +| Redis | Standalone Redis 6.2 | Valkey (Redis fork) via Helm subchart | +| Backups (photos) | resticprofile with crond inside container | K8s CronJob running resticprofile | +| Backups (database) | postgres-backup-local (hourly local dumps) | K8s CronJob: pg_dump + rclone to R2 | +| Ingress | Traefik Docker labels | K8s Ingress with cert-manager TLS | +| Secrets | `stack.env.real` (plain text on disk) | SOPS-encrypted Secrets in Git | +| Network isolation | Docker network `immich` | K8s NetworkPolicies (default-deny + explicit allow) | +| Authentication | Immich built-in | Immich built-in (unchanged, no Authelia) | + +### What Stays the Same + +- Photo library location on NAS (same NFS path) +- Immich PostgreSQL image with vectorchord/pgvectors extensions +- Restic backup repository on Backblaze B2 (same repo, same key — history carries over) +- Database credentials (can be reused from `stack.env.real`) + +## Prerequisites + +- `kubectl` configured for the target cluster +- `sops` and `age` installed for encrypting secrets +- Docker Compose stack still running (for database dump) +- Note the following values from your Docker `stack.env.real`: + - `UPLOAD_LOCATION` — photo library path on NAS (this becomes `IMMICH_UPLOAD_NFS_PATH`) + - `DB_PASSWORD`, `DB_USERNAME`, `DB_DATABASE_NAME` — current database credentials +- Verify which Immich version Docker is running: + + ```bash + docker inspect immich-server --format '{{.Config.Image}}' + ``` + + The HelmRelease pins `v2.0.0`. If Docker runs a different version, update `image.tag` in `release.yaml` to match before migrating, then upgrade after migration is verified. + +## Phase 1: Deploy Infrastructure (Immich Suspended) + +The HelmRelease is deployed in a suspended state so that only the supporting infrastructure (database, PVCs, secrets, network policies) is created. Immich app pods will not start until Phase 3. + +1. Suspend the HelmRelease by adding `spec.suspend: true` to `release.yaml`: + + ```yaml + spec: + suspend: true + chart: + ... + ``` + +2. Fill in and encrypt all secrets: + + ```bash + # Edit with actual values, then encrypt + sops --encrypt --in-place kubernetes/app/immich/secret.sops.yaml + sops --encrypt --in-place kubernetes/app/immich/secret-rclone.sops.yaml + sops --encrypt --in-place kubernetes/app/immich/secret-backup.sops.yaml + ``` + +3. Set `IMMICH_HOST` and `IMMICH_UPLOAD_NFS_PATH` in cluster-vars: + + ```bash + sops kubernetes/config/cluster-vars.sops.yaml + ``` + + `IMMICH_UPLOAD_NFS_PATH` must be the same NAS path as Docker's `UPLOAD_LOCATION`. + +4. Commit and push: + + ```bash + git add kubernetes/app/immich/ kubernetes/config/cluster-vars.sops.yaml + git commit -m "feat(k8s/immich): add immich stack (HelmRelease suspended for migration)" + git push + ``` + +5. Wait for Flux to reconcile: + + ```bash + flux reconcile kustomization apps --with-source + kubectl get pvc -n immich + ``` + + All PVCs should be `Bound`. The HelmRelease will show as `Suspended`. + +6. Verify the database pod is ready: + + ```bash + kubectl wait --for=condition=ready pod -n immich -l app=immich-db --timeout=120s + ``` + +## Phase 2: Migrate PostgreSQL Database + +The StatefulSet is running from Phase 1 with an empty database. Dump from Docker and restore into K8s. + +1. Dump the database from the running Docker container: + + ```bash + docker exec immich-database pg_dump -U immich -d immich --clean --if-exists > immich.sql + ``` + + > The container name may differ — check with `docker ps | grep postgres`. Use the container running the `ghcr.io/immich-app/postgres` image. + +2. Verify the dump is non-empty: + + ```bash + wc -l immich.sql + grep -c "CREATE TABLE" immich.sql + ``` + +3. Copy the dump into the K8s pod and restore: + + ```bash + kubectl cp immich.sql immich/immich-db-0:/tmp/immich.sql + kubectl exec -n immich immich-db-0 -- psql -U immich -d immich -f /tmp/immich.sql + ``` + + Some `DROP ... does not exist` notices are expected (from `--clean --if-exists`). Errors about extensions already existing are also normal. + +4. Verify restoration: + + ```bash + # Check table count + kubectl exec -n immich immich-db-0 -- psql -U immich -d immich -c \ + "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public';" + + # Check that vector extensions are present + kubectl exec -n immich immich-db-0 -- psql -U immich -d immich -c \ + "SELECT extname, extversion FROM pg_extension WHERE extname LIKE '%vector%';" + + # Check asset count (your photo count) + kubectl exec -n immich immich-db-0 -- psql -U immich -d immich -c \ + "SELECT count(*) FROM asset;" + ``` + +5. Verify the NFS library path is correct. The photo library PV should point to the same NFS directory as Docker's `UPLOAD_LOCATION`: + + ```bash + kubectl get pv immich-library -o jsonpath='{.spec.nfs.path}' + ``` + +## Phase 3: Start Immich + +With the database restored and NFS path verified, unsuspend the HelmRelease. + +1. Remove `suspend: true` from `release.yaml`. + +2. Commit and push: + + ```bash + git add kubernetes/app/immich/release.yaml + git commit -m "feat(k8s/immich): unsuspend HelmRelease after data migration" + git push + ``` + +3. Wait for Flux to deploy the Helm chart: + + ```bash + flux reconcile kustomization apps --with-source + kubectl get helmrelease -n flux-system immich + kubectl get pods -n immich -w + ``` + + You should see pods for: `immich-server`, `immich-machine-learning`, `immich-valkey-master`, and `immich-db-0` (already running). + +4. The ML service will download models on first start — this is expected and may take several minutes. + +## Post-Migration Verification + +### Web Access + +Open `https://` in a browser. You should see the Immich login page with your existing users. + +### Photo Library + +- Log in and verify your photos and albums are visible +- Check that thumbnails load (they're stored in the upload directory) +- Verify face recognition data is intact (People tab) + +### Ingress and TLS + +```bash +kubectl get ingress -n immich +kubectl get certificate -n immich +``` + +The certificate should show `Ready: True` after cert-manager provisions it. + +### Backup CronJobs + +Trigger manual test runs: + +```bash +# Test database backup +kubectl create job -n immich --from=cronjob/immich-db-backup immich-db-backup-test +kubectl logs -n immich -l job-name=immich-db-backup-test -f + +# Test library backup (resticprofile) +kubectl create job -n immich --from=cronjob/immich-library-backup immich-library-backup-test +kubectl logs -n immich -l job-name=immich-library-backup-test -f +``` + +The library backup should connect to the existing Backblaze B2 restic repository and complete an incremental backup. + +### Network Policies + +```bash +# Verify immich-server can reach the database +kubectl exec -n immich deploy/immich-server -- nc -z immich-db 5432 +``` + +## Stop Docker Compose Stack + +Only after K8s is verified working: + +In Portainer: Stop the immich stack. + +Or via CLI: + +```bash +docker compose -f docker-compose.yaml --env-file stack.env.real down +``` + +The photo library on the NAS remains accessible via the K8s NFS PV. + +## Rollback + +The Docker data is not modified during migration (pg_dump reads only, NFS path is shared). To roll back: + +1. Revert the immich manifests from Git and push. Flux will delete the namespace and all resources via pruning. + + Alternatively, suspend Flux and delete manually: + + ```bash + flux suspend kustomization apps + kubectl delete namespace immich + ``` + +2. Re-deploy the Docker Compose stack from Portainer with the original `stack.env.real`. + +## Pitfalls and Troubleshooting + +### PostgreSQL PGDATA path difference + +Docker uses `PGDATA=/var/lib/postgresql/data` (the mount root). The K8s StatefulSet sets `PGDATA=/var/lib/postgresql/data/pgdata` (a subdirectory). Never copy the Docker PostgreSQL data directory directly to the K8s PVC. Always use `pg_dump`/`psql`. + +### Immich version mismatch + +If Docker was running a different Immich version than the one pinned in the HelmRelease (`v2.0.0`), Immich may run database migrations on startup. This is normally fine (Immich handles forward migrations), but there is no rollback path for schema changes. Match versions first if unsure. + +### NFS path mismatch + +`IMMICH_UPLOAD_NFS_PATH` must resolve to exactly the same NAS directory as Docker's `UPLOAD_LOCATION`. If it differs, Immich will start but show no photos, and thumbnails will be broken. Verify by checking inside a running pod: + +```bash +kubectl exec -n immich deploy/immich-server -- ls /usr/src/app/upload/ +``` + +You should see directories like `library/`, `thumbs/`, `encoded-video/`, `upload/`, `profile/`. + +### vectorchord/pgvectors extensions + +The K8s StatefulSet uses the same custom PostgreSQL image (`ghcr.io/immich-app/postgres:14-vectorchord0.3.0-pgvectors0.2.0`) as Docker. Extensions are installed during `initdb` and preserved through `pg_dump`/`psql` restore. If you see errors about missing `vector` type, verify the extensions: + +```bash +kubectl exec -n immich immich-db-0 -- psql -U immich -d immich -c "SELECT extname FROM pg_extension;" +``` + +### Restic lock errors + +If the Docker resticprofile container was not cleanly stopped, a stale lock may exist in the Backblaze B2 restic repository. The K8s CronJob will fail with a lock error. Fix by running: + +```bash +kubectl create job -n immich --from=cronjob/immich-library-backup immich-unlock-test --dry-run=client -o yaml | \ + sed 's/backup && resticprofile.*forget/unlock/' | kubectl apply -f - +``` + +Or exec into a temporary pod with the restic key and AWS credentials and run `restic unlock`. diff --git a/kubernetes/app/immich/cronjob-backup.yaml b/kubernetes/app/immich/cronjob-backup.yaml new file mode 100644 index 0000000..9de9fa2 --- /dev/null +++ b/kubernetes/app/immich/cronjob-backup.yaml @@ -0,0 +1,132 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: immich-db-backup + namespace: immich + labels: + app: immich-db-backup +spec: + schedule: "0 3 * * *" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + jobTemplate: + spec: + template: + metadata: + labels: + app: immich-db-backup + spec: + restartPolicy: OnFailure + initContainers: + - name: pg-dump + image: ghcr.io/immich-app/postgres:14-vectorchord0.3.0-pgvectors0.2.0 + env: + - name: PGHOST + value: immich-db + - name: PGUSER + valueFrom: + secretKeyRef: + name: immich-credentials + key: DB_USERNAME + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: immich-credentials + key: DB_PASSWORD + - name: PGDATABASE + valueFrom: + secretKeyRef: + name: immich-credentials + key: DB_DATABASE_NAME + command: + - sh + - -c + - pg_dump --clean --if-exists > /backup/dump.sql + volumeMounts: + - name: backup + mountPath: /backup + containers: + - name: rclone-upload + image: rclone/rclone:1.69 + command: + - sh + - -c + - rclone copy /backup/dump.sql b2crypt:immich-db/ --config /config/rclone/rclone.conf + volumeMounts: + - name: backup + mountPath: /backup + - name: rclone-config + mountPath: /config/rclone + readOnly: true + volumes: + - name: backup + emptyDir: {} + - name: rclone-config + secret: + secretName: immich-rclone-config +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: immich-library-backup + namespace: immich + labels: + app: immich-library-backup +spec: + schedule: "0 4 * * *" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + jobTemplate: + spec: + template: + metadata: + labels: + app: immich-library-backup + spec: + restartPolicy: OnFailure + containers: + - name: resticprofile-backup + image: creativeprojects/resticprofile:latest + command: + - sh + - -c + - resticprofile -c /etc/resticprofile/profiles.yaml backup && resticprofile -c /etc/resticprofile/profiles.yaml forget + env: + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: immich-backup-credentials + key: AWS_ACCESS_KEY_ID + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: immich-backup-credentials + key: AWS_SECRET_ACCESS_KEY + volumeMounts: + - name: library + mountPath: /photos + readOnly: true + - name: resticprofile-config + mountPath: /etc/resticprofile + readOnly: true + - name: restic-key + mountPath: /etc/restic + readOnly: true + volumes: + - name: library + persistentVolumeClaim: + claimName: immich-library + - name: resticprofile-config + secret: + secretName: immich-backup-credentials + items: + - key: profiles.yaml + path: profiles.yaml + - name: restic-key + secret: + secretName: immich-backup-credentials + items: + - key: RESTIC_KEY + path: key diff --git a/kubernetes/app/immich/database.yaml b/kubernetes/app/immich/database.yaml new file mode 100644 index 0000000..c012fa2 --- /dev/null +++ b/kubernetes/app/immich/database.yaml @@ -0,0 +1,69 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: immich-db + namespace: immich + labels: + app: immich-db +spec: + replicas: 1 + serviceName: immich-db + selector: + matchLabels: + app: immich-db + template: + metadata: + labels: + app: immich-db + spec: + securityContext: + runAsUser: 999 + runAsGroup: 999 + fsGroup: 999 + containers: + - name: postgres + image: ghcr.io/immich-app/postgres:14-vectorchord0.3.0-pgvectors0.2.0 + env: + - name: POSTGRES_DB + valueFrom: + secretKeyRef: + name: immich-credentials + key: DB_DATABASE_NAME + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: immich-credentials + key: DB_USERNAME + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: immich-credentials + key: DB_PASSWORD + - name: POSTGRES_INITDB_ARGS + value: --data-checksums + - name: DB_STORAGE_TYPE + value: HDD + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + ports: + - containerPort: 5432 + name: postgres + protocol: TCP + livenessProbe: + tcpSocket: + port: 5432 + initialDelaySeconds: 30 + periodSeconds: 30 + failureThreshold: 5 + readinessProbe: + tcpSocket: + port: 5432 + initialDelaySeconds: 5 + periodSeconds: 10 + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + volumes: + - name: data + persistentVolumeClaim: + claimName: immich-db diff --git a/kubernetes/app/immich/namespace.yaml b/kubernetes/app/immich/namespace.yaml new file mode 100644 index 0000000..c796392 --- /dev/null +++ b/kubernetes/app/immich/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: immich diff --git a/kubernetes/app/immich/networkpolicy.yaml b/kubernetes/app/immich/networkpolicy.yaml new file mode 100644 index 0000000..65bd2c5 --- /dev/null +++ b/kubernetes/app/immich/networkpolicy.yaml @@ -0,0 +1,94 @@ +# Default deny all ingress in the immich namespace +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-ingress + namespace: immich +spec: + podSelector: {} + policyTypes: + - Ingress +--- +# Allow ingress controller to reach immich-server +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-ingress-controller + namespace: immich +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: server + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: traefik +--- +# immich-db: only reachable from immich app pods and backup jobs +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: immich-db + namespace: immich +spec: + podSelector: + matchLabels: + app: immich-db + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchLabels: + app.kubernetes.io/name: server + - podSelector: + matchLabels: + app.kubernetes.io/name: microservices + - podSelector: + matchLabels: + app: immich-db-backup +--- +# Allow immich pods to reach valkey +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-valkey + namespace: immich +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: valkey + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchLabels: + app.kubernetes.io/name: server + - podSelector: + matchLabels: + app.kubernetes.io/name: microservices +--- +# Allow immich pods to reach machine-learning +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-machine-learning + namespace: immich +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: machine-learning + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchLabels: + app.kubernetes.io/name: server + - podSelector: + matchLabels: + app.kubernetes.io/name: microservices diff --git a/kubernetes/app/immich/pv-library.yaml b/kubernetes/app/immich/pv-library.yaml new file mode 100644 index 0000000..19b13dc --- /dev/null +++ b/kubernetes/app/immich/pv-library.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: immich-library +spec: + capacity: + storage: 1Ti + accessModes: + - ReadWriteMany + storageClassName: "" + persistentVolumeReclaimPolicy: Retain + mountOptions: + - hard + - nointr + nfs: + server: synology.storage.lviv + path: ${IMMICH_UPLOAD_NFS_PATH} diff --git a/kubernetes/app/immich/pvc.yaml b/kubernetes/app/immich/pvc.yaml new file mode 100644 index 0000000..5b02197 --- /dev/null +++ b/kubernetes/app/immich/pvc.yaml @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: immich-library + namespace: immich +spec: + accessModes: + - ReadWriteMany + storageClassName: "" + volumeName: immich-library + resources: + requests: + storage: 1Ti +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: immich-db + namespace: immich +spec: + accessModes: + - ReadWriteOnce + storageClassName: nfs-synology-ssd + resources: + requests: + storage: 10Gi diff --git a/kubernetes/app/immich/release.yaml b/kubernetes/app/immich/release.yaml new file mode 100644 index 0000000..af450fa --- /dev/null +++ b/kubernetes/app/immich/release.yaml @@ -0,0 +1,82 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: immich + namespace: flux-system +spec: + suspend: true + chart: + spec: + chart: immich + reconcileStrategy: ChartVersion + sourceRef: + kind: HelmRepository + name: immich + namespace: flux-system + version: 0.10.3 + interval: 1m0s + targetNamespace: immich + values: + controllers: + main: + containers: + main: + image: + tag: v2.5.6 + env: + DB_HOSTNAME: immich-db + DB_USERNAME: + valueFrom: + secretKeyRef: + name: immich-credentials + key: DB_USERNAME + DB_PASSWORD: + valueFrom: + secretKeyRef: + name: immich-credentials + key: DB_PASSWORD + DB_DATABASE_NAME: + valueFrom: + secretKeyRef: + name: immich-credentials + key: DB_DATABASE_NAME + DB_STORAGE_TYPE: HDD + immich: + persistence: + library: + existingClaim: immich-library + server: + enabled: true + ingress: + main: + enabled: true + annotations: + cert-manager.io/cluster-issuer: letsencrypt + hosts: + - host: ${IMMICH_HOST} + paths: + - path: / + pathType: Prefix + tls: + - hosts: + - ${IMMICH_HOST} + secretName: immich-tls + machine-learning: + enabled: true + persistence: + cache: + enabled: true + storageClass: nfs-synology-ssd + accessMode: ReadWriteOnce + size: 10Gi + type: persistentVolumeClaim + valkey: + enabled: true + persistence: + data: + enabled: true + size: 1Gi + type: persistentVolumeClaim + accessMode: ReadWriteOnce + storageClass: nfs-synology-ssd diff --git a/kubernetes/app/immich/repository.yaml b/kubernetes/app/immich/repository.yaml new file mode 100644 index 0000000..399cecb --- /dev/null +++ b/kubernetes/app/immich/repository.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: immich + namespace: flux-system +spec: + interval: 1m0s + type: oci + url: oci://ghcr.io/immich-app/immich-charts diff --git a/kubernetes/app/immich/secret-backup.sops.yaml b/kubernetes/app/immich/secret-backup.sops.yaml new file mode 100644 index 0000000..beee420 --- /dev/null +++ b/kubernetes/app/immich/secret-backup.sops.yaml @@ -0,0 +1,25 @@ +apiVersion: v1 +kind: Secret +metadata: + name: immich-backup-credentials + namespace: immich +stringData: + AWS_ACCESS_KEY_ID: ENC[AES256_GCM,data:lGrcLznG4NVn/xM+pyaRwdbnt1DMioucgA==,iv:4gb4Rdd2RCFS0SjK/nUjSbNcgcs8QrUlkz04BOimmv4=,tag:Q+3vV6Fknl/BpHcgeef2AA==,type:str] + AWS_SECRET_ACCESS_KEY: ENC[AES256_GCM,data:Kw7Pt1HiDi/ZsWwZcSeyWgVAtBkmqyNi8XVW3wjU0Q==,iv:AjpZVeXZthyKtOqWzz4K/0CxEL2QB75PXD/EnFTI1wM=,tag:NdW8QJUKz2W4u5LCgk64XQ==,type:str] + RESTIC_KEY: ENC[AES256_GCM,data:dmb7QIwP35GF+L6+aKsYiyKgRrYOSw7LOdSakZVK6CRlKB7izmTboGAZHOitDHXw9lHASBfE3gYKkDZtLSK9tw==,iv:34i8s9xax+XVe2k5nyazbsz0wD+pWX0BWde+sf77TZY=,tag:VijQe0wBxCZNVv8hPopvgQ==,type:str] + profiles.yaml: ENC[AES256_GCM,data:WO05cJpUjcdvAqmd7DSfIL+o859jLOsMgx26U858rnXSZGYGFmAexkcePmb+XnRxxNoxGLdC1xyC1KcpLfWz9llorxN27FojkBIWm7ZlqBPpAoVpLoroYnPrMVlSQgZsuKW+zcN/wHWH1KmgU9ImPEfS5qtcLmcr3RZLmm6HPVwdtUBU6HDe6boYHuYV1FSFDvZWE2swWp9Ml6rCEwcr7dvOWRN/djEr8AMbZPhj8Qhe94VeX1Ocx/jzTSFBLZrhLN1xcwBrwPNSjb00+JtNrMhpA0z8/bJnLMhLWWDb/WbWNqQnJkJImJPFj2t3gI0klkdaosq29Rc5bHJcKzMDed3RSvdFu7dkQGr86cw+qBtPLYq7oTYEu4Zo1hXjDfeckcpk2tI+z++lNOws+O+L6SBbxx4ZPhlOymonf1xBvBeXH65LU9YOtjAp/fBmSIAXsgoYEOBhkXd/vAG8oblFp2VXvGSs8agZGbTZTeON4Kam8eyGnVeA92Vjk5OgpE+qW+dVdT2qSUyOdpJmJoQbxnpKjNs1Qft+arDNP87hA+cFav7/3i5+WawesFPTAuSTgZBSzCd3I2nJtmO/zonGVaB5,iv:qDSbTaPMqqTDBWw+pU5pKwOT8Vo8BQCGb81PmPh3qbM=,tag:uym01wpo+OMWiVxyWNr3cQ==,type:str] +sops: + age: + - recipient: age1zffnskvuezntkk703a0pyxsd5m8vx2hm33dr47wdfy8mn4fdw4sqgw0jgc + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5WFFRWVAwVkx6SjBPZUw5 + Ykx2QnJjYS96VEJHaWFOTC9Yb0Q1ckppSEg4CjYvcGVYQ284c1JzRXJseTFPVDJQ + bDNHeXVKNXNydzcyVFlkWHJuQ0R0T1kKLS0tIHNuMThacHk1dko0N3hRd3NjZUxv + U25jVnVQQjhaQlI2TU5QSGhtcjRvZWcKNCiNhsb+lZehYXAx87a3h5G5mifOdqxQ + 5xa/TTuqQwv4v5xrsMKcYvt2VvKipWYaByP/4F6D5mkH28GK2etlgg== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-02-22T16:17:56Z" + mac: ENC[AES256_GCM,data:525DsywY9tCeDrNwU39oFdJoF3jtB4AoA+i4LLEomnYsvQrdpMQRRn5BKVS40nmGZ3wA37JrJkGRk8Be21Wri6M3Vh0hzExWmqYLY7Xhvc+6IK6UJzgL/RQ9ynl7vtw7buozWWZhOWoBjzC84t/hWxepNSA30XHR9NypWUF+TpE=,iv:3lA2d8MtVGLtmhDlWVUHcyqrM6u4aoNoFL/l8YYS4Kw=,tag:9L/gobcRaGxk9n6RRChtXw==,type:str] + encrypted_regex: ^(data|stringData|email)$ + version: 3.11.0 diff --git a/kubernetes/app/immich/secret-rclone.sops.yaml b/kubernetes/app/immich/secret-rclone.sops.yaml new file mode 100644 index 0000000..cfafd2f --- /dev/null +++ b/kubernetes/app/immich/secret-rclone.sops.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: Secret +metadata: + name: immich-rclone-config + namespace: immich +stringData: + rclone.conf: ENC[AES256_GCM,data:CryFjZBVyHdDPT1yGlmhCGDthmyhT3tTAmj2RmUl+pmrO4qkiT1UdADn/aSdSNjIE89dXzHcn22bY/4K9EuKhKPHm+MzHx10isPtrwERNy1FAhc2a1/pwBRIi5ej4MWZrCNnJCyG5coWDtsJhW/nRev0aAc24Jsp2S4Iyw4ZZcVDPLmYGCb0GYunOQOFKQkB3R+q3aFeaPOJnrn7EJVRzsiwFAd4hiVyCpSFme7n6xddKkMMrDICsyFg6ICkKYgFgRp0JnXVleA+y9phI17F/P63VXJsaI25JE5SMENTNpRHQ0OIC/HYQO92fadGXh7lMwp3zNwaoLk5YY1j4FXYuEHoxmtd82eEuvLYwM7CL4qj/aYZ1CiYWWiRg3oaUrKt7btFLDmdgkr4dhiv2S6t9AzwTp0Fo7Gg,iv:bSN2tyGWIQJVZ26dgFr/GEhmEeDM6cmfx6b6yEBWXY0=,tag:wKWO4uYYmb51sWq7JQY4UQ==,type:str] +sops: + age: + - recipient: age1zffnskvuezntkk703a0pyxsd5m8vx2hm33dr47wdfy8mn4fdw4sqgw0jgc + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4NDg1WVBjbnB6YlFWTFpk + S2M3Tm1zVUZhUFRSZi9mcHVNWkhCcm5OQVNnCmNBTmllY2U0Mkw1dUpvSkpoR2Rj + SUN5T09kelNNZFBuaVFPMW1GUkhkNW8KLS0tIG1jNHdzczc0Z1c4bzVOVitBVHUy + U1V5QVBjdUptb3YrNkJjNkZzZ0xZZnMKyH1YkErMgv7n7t9Wr1aAE5LJvLKPO18r + z5gjcgUy7sCq77eRU4XjEgqivyy6fUcdbyTazhTGYIuUB5i3LbYSdQ== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-02-22T16:16:54Z" + mac: ENC[AES256_GCM,data:gq7I3d82pjWZyHtX97jk8l+vYHqes1te43BXyI+yqYazh/ZyBshJT+60B5EjnfK8RA/C/jGcVTpBHa2Gc8BkcAxX1eDEvd9g72X70owB4MW3UYGSQ4XmeBzIdKC3p/LIW6SSzGcQB4MZZpuQ8HyP0+qGDE7NZJrim7wp6zI6Ovo=,iv:2l3NX+UuDdAQ9wAcaTFpWbWpq1G98p6CJBZnmzLLCvg=,tag:fzpx0i+w6lwfiQBHBMU9+A==,type:str] + encrypted_regex: ^(data|stringData|email)$ + version: 3.11.0 diff --git a/kubernetes/app/immich/secret.sops.yaml b/kubernetes/app/immich/secret.sops.yaml new file mode 100644 index 0000000..97bd299 --- /dev/null +++ b/kubernetes/app/immich/secret.sops.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +kind: Secret +metadata: + name: immich-credentials + namespace: immich +stringData: + DB_PASSWORD: ENC[AES256_GCM,data:VnoJR8ALDlw/BwU/3/b7KbCrMbwdTFMagRvit//NsWTw5flqsQirZ8JxXPek8VJxDkQkbmVUld+slgh4NkBBbQ==,iv:2Jttn6CmDEI8/i6LFJ8aAw7YvEZsT0iK/SEhnBYLPhw=,tag:4dyAA9HuA6Rm+vciXHqMag==,type:str] + DB_USERNAME: ENC[AES256_GCM,data:x5K6Si9p,iv:bt/DiC2SGDXz8vWvVATaHOSgz0SoL/jGJR7dNuYmaQc=,tag:iP3HIICt4SrMDJ0/XxZ0Qw==,type:str] + DB_DATABASE_NAME: ENC[AES256_GCM,data:yPids+wE,iv:eDtreKHSfi+SXSwX4g7Y5MNPTRsOW88TC/nA+YZ0Ww8=,tag:RdJDG7r8ncMYU5wOt9wr6A==,type:str] +sops: + age: + - recipient: age1zffnskvuezntkk703a0pyxsd5m8vx2hm33dr47wdfy8mn4fdw4sqgw0jgc + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNblZZWkZ0cnhPbGpUa2FK + R1RDZzBrUERMQ0tHUGw3QmZYZEptTGd0RHhnCkhOdmR5eGRoVitlbjRIaW5PdXpa + NmhRcnl1dDZ4ME5nMTRENUV2ZGkxVVUKLS0tIGcxUHFrVENQQTlpeDM0dmVWNGxh + MlRtZHFheTdIb0RZQTRDKzVKSEVtTHMKucqUfpKcxxyBU9rnoV/wVoAwM8gTVJGX + d615yxwcmsKtivzarkMbrSh14jFFjhJKiyYLZ1ExfwpfEykl9DpZ6w== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-02-23T17:43:35Z" + mac: ENC[AES256_GCM,data:AE6aKYrkZ+fOy/Cadjegrzlau9X1vFiegkRNMZs15JV6sN5hOQ1PqX5Fyi4gyHqzXDrD0BNED4ZT4+8z5zxeRxKxP7nmyh1rwz8UnT/vDsp9zdWNl2qYu62JH5rxnU1c/Vmi8NfqPGdaT2fVpCz+HhV//zusFOLP1mi3L06TK5M=,iv:4QdJr1Q2jP6ics/kOmo63+7YOM+5UIh5s2LjnSqfNx8=,tag:MHwd3RJ/3zo9ohgGdYM7Jg==,type:str] + encrypted_regex: ^(data|stringData|email)$ + version: 3.11.0 diff --git a/kubernetes/app/immich/service.yaml b/kubernetes/app/immich/service.yaml new file mode 100644 index 0000000..555ece1 --- /dev/null +++ b/kubernetes/app/immich/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: immich-db + namespace: immich + labels: + app: immich-db +spec: + type: ClusterIP + ports: + - name: 5432-5432 + port: 5432 + targetPort: 5432 + protocol: TCP + selector: + app: immich-db diff --git a/kubernetes/config/cluster-vars.sops.yaml b/kubernetes/config/cluster-vars.sops.yaml index 9a63894..b37712d 100644 --- a/kubernetes/config/cluster-vars.sops.yaml +++ b/kubernetes/config/cluster-vars.sops.yaml @@ -10,6 +10,8 @@ stringData: SONARR_HOST: ENC[AES256_GCM,data:dMEgQYHxa0tIilY8HPkppFgOWzH52Q==,iv:yCUF8ZVOSY2IfULa9B44ALGjgFyZyQrQWPZsyQzqwbM=,tag:kfhsZhPE1rZNYUJ3X2rRSQ==,type:str] RADARR_HOST: ENC[AES256_GCM,data:lsWSI+D/qzru3qHGvJjKRepYEGHe5Q==,iv:cq9yNVCMuon+ZUpFwfkwBvVg9oIT0MXagjDi6l/29YA=,tag:koGF34SrtHe0brRiNKP8rg==,type:str] MEDIA_NFS_PATH: ENC[AES256_GCM,data:BSDMu0n2Vx4koYBIMF8=,iv:c9kGdcTxObNaaaTzEhSRkyHvo+dxSN+o+96n9UqJieU=,tag:W9+MbyAuK85xajjwntRi0Q==,type:str] + IMMICH_HOST: ENC[AES256_GCM,data:KnzX89wzQvb5Pa/MqX4YiHZ0JS5geA==,iv:05jEIwQEjJnvZ1Ot33Lkfs1TB3L/mwX5dqaTfsugcx4=,tag:LFzOLZSqHQ58bL3oVvGM9g==,type:str] + IMMICH_UPLOAD_NFS_PATH: ENC[AES256_GCM,data:l8F1AkmhGkNxo29X5UER,iv:Z/u0yLNv5ClQu44lPPzGIB2bEsADFCD/mCd+Kw8kuhc=,tag:a8QGaUEYF3iJbZKcAiRKUg==,type:str] sops: age: - recipient: age1zffnskvuezntkk703a0pyxsd5m8vx2hm33dr47wdfy8mn4fdw4sqgw0jgc @@ -21,7 +23,7 @@ sops: LzhUN3Z4cExIL1IyS3ZCNWh5aWpLbDgKQ7c3MmLykA00NaLoctKVDfJvPqTqh3Ia cDZJUc6jYJXOJYM6YYyZOYcCL2z8V2RpIfA9sPg8PB2eiipZxjk+Cg== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-02-22T10:48:28Z" - mac: ENC[AES256_GCM,data:qw4wqmlHntZiybI2VPKcQEUJg0AG8kgWn7hEw6WlCltf7STbRWLLedR5TTRmKpcCQofE4T02ZZPLbzVVP7tSer1S4nAYSMwkfQhgkZ8DVvn/E2O/Yxzja/DXLV9tNMYpYexSkfVX8zYRS1zdd48VBgA4P4N0WR65kvoUnIScrgA=,iv:+brh3Gehjes1G6wNxPVDgHltI80Ih5d21Va/ADJxxCI=,tag:pay3NKWyLYF4BPZnCszW0Q==,type:str] + lastmodified: "2026-02-22T16:16:12Z" + mac: ENC[AES256_GCM,data:i8QGecMBJwUwb9dki6ZLFM+4cNPjSLtI31eeiGBqu5dqyeX/e60FprfIHBHfdiastFDDrCZ4SRLd49KKBocGsX3QbX/uk+8rmtHC83ACp6iYiv3kGUS+3u6OrRB/HzXqKiDI5w8cN1lWWUSFHelcXsPoWAv9jUNi1qEBaKAjmnk=,iv:gATtJAJacVoRAsYXhknZkWzASq6aMfjX10oHg3GEVuI=,tag:ekZMb7g0hD1KdjW5u85kkA==,type:str] encrypted_regex: ^(data|stringData|email)$ version: 3.11.0