Skip to content

Immich Photo Management

Self-hosted photo and video management platform running on k3s cluster with NPU acceleration and SSO authentication.

Usage

Batch photo upload

  • download immich-go from github.com/simulot/immich-go.
  • prepare API keys:
    • user (rights: asset.*, album.*, albumAsset.*, user.read TODO - verify)
      • save it to opt/immich-go/api-key-<user>.txt
    • optional – admin (rights: job.* TODO - verify)
      • save it to opt/immich-go/api-key-admin.txt
    • chmod go= opt/immich-go/api-key-*.txt
  • use relevant upload option below

Upload from directory

cd photos-directory/
immich-go upload from-folder --server=https://photos.mixi.cz --api-key=$(cat ~/opt/immich-go/api-key-mixi.txt) --recursive --concurrent-uploads=6 --admin-api-key=$(cat ~/opt/immich-go/api-key-admin.txt) ./

Upload from Google takeout

TODO

immich-go upload from-google-photos --server=https://photos.mixi.cz --api-key=$(cat ~/opt/immich-go/api-key-mixi.txt) --concurrent-uploads=2 --admin-api-key=$(cat ~/opt/immich-go/api-key-admin.txt) --on-server-errors=  
continue takeout-*.zip

Tips

  • test upload with --dry-run
  • use of --admin-api-key=$(cat ~/opt/immich-go/api-key-admin.txt) allows to pause background jobs, speeding up upload.
  • for measuring time, it is useful to use --no-ui option (UI does not quit automatically after finished upload).

Architecture

graph TB
    subgraph "External Access"
        User[User/Mobile App]
        Apache[Apache Reverse Proxy\ncindy.intranet]
    end

    subgraph "k3s Ingress Layer"
        Traefik[Traefik Ingress\nSSL Termination]
    end

    subgraph "Authentication"
        Authelia[Authelia SSO\nForwardAuth + OAuth2]
        LLDAP[LLDAP\nUser Directory]
    end

    subgraph "Immich Services"
        WebUI[Immich Web UI\nReact SPA]
        API[Immich Server\nNode.js API]
        ML[Machine Learning\nNPU Accelerated]
    end

    subgraph "Data Layer"
        Postgres[(PostgreSQL\nUser DB + Metadata)]
        Redis[(Redis\nJob Queue)]
        NFS[NAS Storage\ncindy:/srv/nas/immich]
    end

    User -->|HTTPS| Apache
    Apache -->|Web /| Traefik
    Apache -->|API /api| Traefik

    Traefik -->|Web Auth| Authelia
    Authelia -->|LDAP Query| LLDAP

    Traefik -->|Web Frontend| WebUI
    Traefik -->|API Direct| API

    WebUI --> API
    API --> ML
    API --> Postgres
    API --> Redis
    API --> NFS

    Authelia -.->|OAuth2 OIDC| API

Deployment Overview

Namespace: immich URL: https://photos.mixi.cz Storage: NFS (cindy) + Longhorn PVCs Authentication: Authelia SSO with LLDAP backend

Components

Component Purpose Storage Resources
immich-server API backend Longhorn + NFS 2 replicas, 1 CPU, 1GB RAM
immich-web React frontend - 2 replicas, 0.5 CPU, 512MB RAM
immich-machine-learning AI/ML with NPU Longhorn cache 1-4 replicas, 2 CPU, 2GB RAM
postgres User DB + metadata Longhorn 20GB 1 CPU, 1GB RAM
redis Job queue (BullMQ) Longhorn 5GB 0.5 CPU, 512MB RAM

Storage Layout

NFS (cindy.intranet):

/srv/nas/immich/
├── upload/         # Original photos/videos
├── library/        # Processed library
├── profile/        # User avatars
└── external/       # External library mounts

Longhorn PVCs: - PostgreSQL: 20GB (user accounts, metadata, search indexes) - Redis: 5GB (job queue persistence) - Thumbnails: 50GB (cache) - ML Cache: 10GB (models)

Authentication Flow

Web Access (Browser)

  1. User visits https://photos.mixi.cz
  2. Traefik ingress checks Authelia ForwardAuth
  3. Redirected to Authelia login if not authenticated
  4. Authelia validates against LLDAP
  5. Session cookie set, access granted to web UI

API Access (Mobile App)

  1. Mobile app initiates OAuth2 flow
  2. Authelia provides OIDC authentication
  3. User authenticates via Authelia/LLDAP
  4. OAuth2 token returned to app
  5. App uses token for direct /api access (bypasses ForwardAuth)

Dual-Path Ingress

Path 1 - Web Frontend (/): - Protected by Authelia ForwardAuth middleware - Session-based authentication - Full web UI access

Path 2 - API Direct (/api): - No Authelia middleware (direct access) - OAuth2 token or API key authentication - Mobile app and programmatic access

NPU Acceleration

[!warning] NPU acceleration not yet working.

Worker Nodes: piks01, piks02, piks03, piks04 NPU: RK3568 (0.8 TOPS each) = 3.2 TOPS total Node Selector: workload_npu: odroid-m1

ML Workloads: - Facial recognition: 30-40x faster than CPU - Object detection: ~60ms vs ~1800ms - Smart search indexing - Auto-scaling 1-4 replicas based on upload volume

Key Features

User Management

  • Admin Account: Local Immich authentication (admin@mixi.cz)
  • Regular Users: OAuth via Authelia + LLDAP
  • Auto-registration: First login creates account automatically
  • User Linking: Email-based account linking

Large File Upload Support

  • Problem: Traefik v2.11.2+ defaults to 60s timeout
  • Solution: Disabled read/write timeouts via 99-traefik-timeout-patch.yaml
  • Configuration:
  • readTimeout=0s - Unlimited upload time
  • writeTimeout=0s - Unlimited response time
  • idleTimeout=180s - 3 min idle timeout
  • Result: Supports 700MB+ video uploads

Backup Strategy

  • PostgreSQL: Daily backup at 2 AM via CronJob
  • Compression: zstd/gzip using ODROID CPU
  • Retention: 14 days on NFS (cindy:/srv/backup/immich)
  • Critical Data: User accounts, metadata, facial recognition data

Configuration Files

Located in /home/mixi/pg/home/automation/kube/immich/:

File Purpose
01-namespace.yaml Namespace creation
02-storage.yaml PVCs for Longhorn and NFS
03-secrets.yaml Database passwords, JWT, OAuth secrets
04-postgres.yaml PostgreSQL database
05-redis.yaml Redis with BullMQ queue
06-machine-learning.yaml NPU-accelerated ML service
07-server.yaml Immich API backend
08-web.yaml Web frontend
09-ingress.yaml Dual-path Traefik ingress
10-backup.yaml Database backup CronJob
99-traefik-timeout-patch.yaml Large file upload fix

Maintenance

Check Service Status

# Check all pods
kubectl get pods -n immich

# Check ML pod NPU access
kubectl exec -n immich deploy/immich-machine-learning -- cat /sys/kernel/debug/rknpu/version

# Check database size
kubectl exec -n immich deploy/immich-postgres -- psql -U immich -c "SELECT pg_size_pretty(pg_database_size('immich'));"

Monitor Uploads

# Watch Immich logs
kubectl logs -n immich -l app.kubernetes.io/component=server -f

# Watch ML processing
kubectl logs -n immich -l app.kubernetes.io/component=machine-learning -f

# Check job queue
kubectl exec -n immich deploy/immich-redis -- redis-cli INFO stats

Backup Verification

# Check latest backup
ssh cindy 'ls -lh /srv/backup/immich/ | tail -5'

# Verify backup size
ssh cindy 'du -sh /srv/backup/immich/'

Troubleshooting

Upload Failures (ECONNRESET)

Symptom: Large files fail with connection reset Cause: Traefik timeout (60s default) Fix: Applied in 99-traefik-timeout-patch.yaml

# Verify timeout configuration
kubectl get deployment traefik -n kube-system -o json | jq '.spec.template.spec.containers[0].args' | grep timeout

Authentication Issues

Symptom: OAuth login fails Check:

# Verify Authelia OIDC client
kubectl get configmap -n authelia authelia-config -o yaml | grep -A 10 immich

# Check LLDAP connectivity
kubectl exec -n immich deploy/immich-server -- curl -I http://lldap-ldap.authelia.svc.cluster.local:389

ML Processing Slow

Symptom: Face detection takes long time Check NPU:

# Verify NPU device access
kubectl exec -n immich deploy/immich-machine-learning -- ls -l /dev/dri

# Check RKNPU module
kubectl exec -n immich deploy/immich-machine-learning -- lsmod | grep rknpu

Performance Expectations

  • Photo Upload: Limited by network (1Gbps)
  • Thumbnail Generation: <1s per photo (Longhorn SSD)
  • Facial Recognition: ~30-40 faces/second (NPU accelerated)
  • Smart Search: Near real-time indexing
  • Background Processing: 1000+ photos/hour

Security

  • Web UI: Protected by Authelia 2FA + LDAP
  • API: OAuth2 tokens or API keys
  • Network: Internal cluster communication only
  • Storage: Non-root containers, read-only root filesystem where possible
  • Secrets: Kubernetes secrets for sensitive data
  • TLS: End-to-end encryption (Apache → Traefik → Services)