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-gofrom github.com/simulot/immich-go. - prepare API keys:
- user (rights:
asset.*,album.*,albumAsset.*,user.readTODO - verify)- save it to
opt/immich-go/api-key-<user>.txt
- save it to
- optional – admin (rights:
job.*TODO - verify)- save it to
opt/immich-go/api-key-admin.txt
- save it to
chmod go= opt/immich-go/api-key-*.txt
- user (rights:
- 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-uioption (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)
- User visits
https://photos.mixi.cz - Traefik ingress checks Authelia ForwardAuth
- Redirected to Authelia login if not authenticated
- Authelia validates against LLDAP
- Session cookie set, access granted to web UI
API Access (Mobile App)
- Mobile app initiates OAuth2 flow
- Authelia provides OIDC authentication
- User authenticates via Authelia/LLDAP
- OAuth2 token returned to app
- App uses token for direct
/apiaccess (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 timewriteTimeout=0s- Unlimited response timeidleTimeout=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)