Immich Deep Dive: AI-Powered Photo Management for Your Homelab
Your phone has 30,000 photos. Google Photos holds them hostage behind an ever-shrinking free tier. Apple iCloud costs $2.99/month and makes sharing difficult. You want your memories on hardware you own, with search that actually works, face recognition that groups your family together automatically, and mobile backup that just runs.
Immich is that solution. It’s the self-hosted photo management app that has done for photos what Jellyfin did for video — built a genuinely good, open-source alternative to the cloud giants. With AI-powered semantic search, DBSCAN-based face clustering, hardware-accelerated ML, and a polished mobile app, Immich is no longer a compromise. For many people it’s better than Google Photos.
This guide covers everything: architecture, Docker Compose deployment, hardware acceleration, face recognition internals, mobile backup, storage design for large libraries, and migration from Google Photos.
Architecture Overview
Immich follows a microservices pattern with four core backend components communicating over Docker networks:
┌─────────────────────────────────────────────────────────────┐
│ Clients │
│ Mobile (Flutter/Dart) │ Web (SvelteKit) │ CLI (Node) │
└───────────────────────────────┬─────────────────────────────┘
│ REST/OpenAPI
┌───────────────────────────────▼─────────────────────────────┐
│ immich-server │
│ NestJS · REST API · Job scheduling · Admin panel │
└──────┬─────────────────────────────────┬────────────────────┘
│ BullMQ jobs │ ML inference
┌──────▼───────────┐ ┌────────▼──────────────────┐
│ immich-microsvcs │ │ immich-machine-learning │
│ Background jobs │ │ FastAPI · ONNX models │
│ Thumbnail gen │ │ CLIP · InsightFace │
│ EXIF extraction │ │ CUDA/OpenVINO/ROCm/RKNN │
│ Transcoding │ └───────────────────────────┘
└──────┬───────────┘
│
┌──────▼──────────────────────────┐ ┌──────────────┐
│ PostgreSQL + pgvector │ │ Redis │
│ Assets · People · Albums · │ │ (Valkey 9) │
│ CLIP vectors · face embeddings │ │ BullMQ queue │
└─────────────────────────────────┘ └──────────────┘
immich-server handles all HTTP traffic. It also schedules background jobs into Redis queues — but does not execute them.
immich-microservices is a separate container running the same image with a different entrypoint. It consumes jobs from Redis: thumbnail generation, EXIF extraction, video transcoding, and triggering ML jobs. Because jobs fan out across the microservices workers, you can scale this container independently.
immich-machine-learning is a Python FastAPI service. It runs ONNX-format models for two tasks: CLIP-based semantic search and InsightFace-based facial recognition. Models are cached in a Docker volume and downloaded on first use.
PostgreSQL stores everything — assets, albums, people, shared links, tags — plus CLIP vector embeddings and facial recognition embeddings stored as pgvector columns. Semantic search queries translate directly into vector similarity searches.
Redis (Valkey) is the job queue. Every background task flows through BullMQ: when a new photo uploads, the server enqueues a thumbnail job; when thumbnails complete, a facial recognition job is enqueued, and so on. Redis also provides session caching.
The client layers — iOS/Android apps written in Flutter/Dart, web app in SvelteKit with Tailwind CSS, and the Node.js CLI — all speak to the server via auto-generated OpenAPI clients.
Docker Compose Deployment
Directory and environment setup
|
|
Create .env:
|
|
Full docker-compose.yml
|
|
Start everything:
|
|
Navigate to http://your-server:2283 and create the admin account. First user becomes the administrator.
Important: Always use
docker compose(notdocker-compose). Environment variable changes requiredocker compose up -d --force-recreate— a plain restart does not apply new variables.
Putting Immich behind Traefik
Add labels to immich-server:
|
|
Hardware Acceleration for Machine Learning
The ML container runs two models continuously: CLIP for semantic search embeddings, and InsightFace for facial recognition. On CPU, these are slow. With a GPU or dedicated accelerator they become near-instant.
CUDA (NVIDIA)
Requires NVIDIA Container Toolkit and driver >= 545 (CUDA 12.3+).
|
|
Update docker-compose.yml:
|
|
OpenVINO (Intel integrated/arc GPU)
|
|
OpenVINO uses more RAM than CPU mode but is significantly faster. Requires a recent kernel for device support.
ROCm (AMD)
|
|
Note: The ROCm image is large (~35 GiB). Ensure sufficient disk space before pulling.
Remote ML server
If your photo server is low-powered (Raspberry Pi, old NAS), you can offload ML to a separate machine:
|
|
Run the ML container on your GPU machine with port 3003 exposed. The Immich server sends inference requests over the network.
How Face Recognition Works
Understanding the internals helps you tune it for your library.
The pipeline
- Upload triggers thumbnail generation in immich-microservices
- Thumbnail completion enqueues a facial recognition job
- Face detection (InsightFace detection model): finds bounding boxes and confidence scores for faces in each photo
- Embedding generation (InsightFace recognition model): crops each face and generates a 512-dimensional embedding vector
- Embeddings stored in PostgreSQL alongside the asset
- DBSCAN clustering groups faces with similar embeddings into “People”
DBSCAN clustering
Immich uses a modified DBSCAN (Density-Based Spatial Clustering of Applications with Noise) algorithm. Unlike k-means, DBSCAN doesn’t require knowing the number of clusters upfront — critical since you don’t know how many distinct people are in your library.
DBSCAN classifies points as:
- Core points: Have at least
minSamplesneighbors within distanceepsilon - Border points: Within epsilon of a core point but not dense enough to be core
- Noise: Not reachable from any core point (unrecognized faces)
Immich’s implementation is incremental — new faces are assigned to existing clusters rather than requiring full reclustering. A new face joins an existing cluster if it falls within epsilon of a core point in that cluster. This means face recognition improves over time as more photos are processed.
Tuning parameters (Admin > Administration > Machine Learning):
| Setting | Default | Effect |
|---|---|---|
| Facial Recognition Model | buffalo_l | Accuracy/speed tradeoff |
| Min Detection Score | 0.7 | Higher = fewer false detections |
| Min Recognized Faces | 3 | Minimum faces to create a named person |
| Max Recognition Distance | 0.6 | Lower = stricter matching |
Accuracy in practice
Expect 80–95% accuracy for typical household libraries:
- Works best with clear, frontal, well-lit faces
- Degrades with occlusion, extreme angles, infant photos (faces change rapidly)
- Improves significantly as you assign names — tagged faces become reference anchors
Strategy for large imports:
1. Import everything
2. Set "Min Recognized Faces" = 20
3. Run "Recognize Faces" on ALL assets
4. Name the clearly-clustered people
5. Lower threshold to 10, re-run in MISSING mode
6. Name any new clusters
7. Lower to 5, re-run in MISSING mode
8. Merge similar unnamed clusters
This incremental approach prevents the “flood of micro-clusters” problem that occurs when you start with a low threshold on a fresh import.
Semantic Search with CLIP
Immich’s “Search” isn’t just filename matching — it’s semantic vector search powered by CLIP (Contrastive Language-Image Pretraining).
When you upload a photo, the ML service generates a CLIP embedding — a vector representation of the image’s semantic content. These vectors are stored in PostgreSQL via pgvector. When you search “dog at beach”, your query is also converted to a CLIP vector and PostgreSQL finds the nearest image vectors using cosine similarity.
This means you can search:
"sunset over mountains"— finds photos by visual content"birthday cake"— finds birthday celebrations"sleeping baby"— finds sleeping infants"documents"— finds photos of papers, whiteboards
No tags, no captions required. The model understands the relationship between language and visual content.
CLIP vectors are also used for “More like this” — finding visually similar photos to a selected image.
Mobile Backup Configuration
Android
- Install Immich app from F-Droid, Google Play, or direct APK
- Open app → tap the user icon → Settings
- Enable Background backup
- Grant photo permissions (All Photos, Videos)
- Configure which albums to back up (default: Camera, Screenshots)
- Set backup timing: While charging only (recommended for large initial syncs)
Battery optimization warning: Samsung, Xiaomi, OnePlus, and Huawei devices aggressively kill background apps. Add Immich to your battery optimization exclusion list:
- Samsung: Settings → Device care → Battery → Background usage limits → Never sleeping apps → Add Immich
- Xiaomi: Settings → Apps → Immich → Battery saver → No restrictions
- OnePlus: Settings → Battery → Battery optimization → Immich → Don’t optimize
Check dontkillmyapp.com for device-specific instructions.
iOS
- Install Immich from the App Store
- Settings → General → Background App Refresh → Enable for Immich
- Disable Low Power Mode for reliable background sync
- Open Immich → Profile → Settings → Background backup → Enable
iOS background sync frequency is entirely controlled by iOS. Apple’s algorithm considers: how often you open the app, device battery level, network conditions, time of day. Apps you use frequently get more background time. There’s no way to force immediate background sync — opening the app triggers foreground backup.
Connection settings
Server URL: https://photos.yourdomain.com
# Or local: http://192.168.1.100:2283
For secure external access without exposing ports directly, use Cloudflare Tunnel:
|
|
Storage Design for Large Libraries
This is the single highest-impact decision you’ll make. Getting storage layout right from the start prevents painful migrations later.
The naive setup (avoid)
|
|
This works for small libraries. For 50k+ photos, it’s painful. Immich generates enormous numbers of small files — multiple thumbnail sizes for every photo, encoded video variants, database files. Small files on spinning disks are slow due to seek time.
Recommended: split generated content onto SSD
|
|
Your originals sit on cheap HDD capacity. Thumbnails (which the web app loads constantly while scrolling) live on SSD. The database, which services every API call, lives on the fastest storage you have.
Storage template
The storage template controls how Immich organizes uploaded files on disk. Configure at Admin → Administration → Settings → Storage Template.
Default: upload/YYYY/MM/DD/filename.jpg
Custom examples:
{albumName}/{YYYY}/{MM}/{originalFilename} # Album-organized
{person}/{YYYY}/{originalFilename} # Person-organized (after tagging)
{YYYY}/{MM}/{DD}/{deviceName}/{originalFilename} # Date + device
Template variables: {YYYY}, {MM}, {DD}, {HH}, {mm}, {ss}, {originalFilename}, {fileExtension}, {albumName}, {person}, {deviceName}, {assetId}.
After changing the template, run “Storage Migration” job under Admin → Jobs to reorganize existing files.
External libraries
External libraries let Immich index photos that live outside its upload directory — useful if you have an existing NAS library you don’t want to move.
- Admin → External Libraries → Create Library
- Set import paths:
/mnt/nas/photos - Schedule automatic scanning or trigger manually
- Immich indexes files without moving them; originals stay in place
External library files cannot be uploaded to from the mobile app — external libraries are read-only from Immich’s perspective.
Backup Strategy
Immich stores two distinct things you need to back up:
- PostgreSQL database — asset metadata, albums, people, shares, face embeddings, CLIP vectors
- Asset files — original photos, videos, thumbnails, encoded video
The built-in database backup runs daily at 2:00 AM and stores dumps in UPLOAD_LOCATION/backups/. By default the last 14 dumps are retained.
Consistent backup procedure
|
|
For offsite backup, pipe to Restic:
|
|
Restore procedure
|
|
Migrating from Google Photos
Export with Google Takeout
- Go to takeout.google.com
- Deselect all, then select Google Photos only
- Choose “All photo albums included”
- Export format: ZIP, maximum file size (50 GB recommended)
- Download all ZIP archives
Takeout produces JSON sidecar files alongside each photo containing Google’s metadata (date taken, GPS coordinates, starred status, album membership).
Import with immich-go
The official @immich/cli can upload files but doesn’t handle Takeout’s JSON sidecars correctly. immich-go is the purpose-built migration tool:
|
|
immich-go handles:
- JSON sidecar metadata (date taken, GPS, description)
- Duplicate detection via file hashing (typically ~33% of exports are duplicates)
- RAW + JPEG pairs (kept together, not double-counted)
- Album reconstruction
- Starred photos (mapped to Immich favorites)
Expect a large initial migration to take several hours to days depending on library size and server hardware.
OAuth / OIDC Integration
With Authentik
Create an OAuth2/OpenID Provider in Authentik:
- Redirect URIs:
https://photos.yourdomain.com/auth/callback - Also add mobile:
app.immich:///oauth-callback - Signing Key: your Authentik signing key
- Note the Client ID and Client Secret
In Immich (Admin → Administration → Settings → OAuth):
Issuer URL: https://auth.yourdomain.com/application/o/immich/
Client ID: <from Authentik>
Client Secret: <from Authentik>
Scope: openid profile email
Button Text: Sign in with Authentik
Auto Launch: enabled (skips local login form)
If you lock yourself out after enabling Auto Launch, navigate to /auth/login?autoLaunch=0 to access the local login form.
With Authelia, Keycloak, or any OIDC provider
The configuration is provider-agnostic — set the Issuer URL to your OIDC provider’s discovery endpoint and provide Client ID/Secret. Immich auto-discovers the token endpoint, userinfo endpoint, and signing keys from the OIDC discovery document.
CLI Usage
The official CLI is useful for scripted uploads and automation:
|
|
For Docker environments without Node.js installed:
|
|
Performance Tuning for Large Libraries
Hardware requirements by library size
| Library Size | CPU | RAM | SSD (DB + thumbs) | HDD (originals) |
|---|---|---|---|---|
| < 10k photos | 2 cores | 4 GB | 50 GB | 500 GB |
| 10k–50k | 4 cores | 8 GB | 200 GB | 2 TB |
| 50k–200k | 8 cores | 16 GB | 500 GB | 8 TB |
| 200k+ | 16+ cores | 32+ GB | 1+ TB NVMe | 20+ TB |
PostgreSQL tuning
For large libraries, increase shared_buffers in the database container:
|
|
Adjust based on available RAM: shared_buffers = 25% of total RAM, effective_cache_size = 75% of total RAM.
Concurrency settings
|
|
Initial import optimization
For large initial imports (100k+ photos):
- Increase job concurrency temporarily:
IMMICH_JOBS_CONCURRENCY=10 - Disable thumbnails for offline assets until import completes
- Under Admin → Jobs → Thumbnail Generation → Run (after all assets are indexed)
- Then run: Smart Search, Face Detection, in that order
- Restore default concurrency after initial processing
Immich v1.130+ includes rewritten scan code that processes millions of assets in minutes rather than hours.
Monitoring
Enable Prometheus metrics:
|
|
Metrics available at :8081/metrics (server) and :8082/metrics (microservices). Key metrics to watch:
|
|
Keeping Immich Updated
Immich releases frequently (weekly minor releases). The easiest update workflow:
|
|
Or pin to a specific version in .env:
|
|
Check the release notes before each update — Immich occasionally has breaking changes that require database migrations (these run automatically on startup, but it’s worth knowing what changed).
What Immich Can’t Do Yet
Honest caveats for managing expectations:
- Smart albums are manual — Immich groups faces automatically, but rule-based “show me all photos in Paris this year” smart albums aren’t fully implemented yet. Filtering and search accomplish the same goal, just without automatic album creation.
- OCR text search is in development but not yet stable
- Map view works, but offline tile servers require additional configuration
- Sharing is functional but not as polished as Google Photos’ sharing UX
The project moves fast. Features that were missing six months ago often land in the next release.
Summary
Immich is the closest self-hosted equivalent to Google Photos. The architecture — NestJS server, background microservices, FastAPI ML service, pgvector for semantic search — is solid enough for production home use. With hardware acceleration, face recognition and CLIP search run fast enough that you barely notice they’re running on your own hardware.
The key decisions for a reliable deployment:
- Storage layout — Put the database and thumbnails on SSD. Originals can live on HDD.
- Hardware acceleration — Even an integrated Intel GPU dramatically speeds up ML processing.
- Mobile backup — Disable battery optimization for Immich on Android; on iOS, open the app regularly.
- Backups — Back up both the database dump and the asset files together; consistency matters.
- Face recognition strategy — Start with a high “minimum recognized faces” threshold, name the main clusters, then lower and iterate.
Your 30,000 photos can be home again.
Comments