Immich is a self-hosted photo and video management platform — a privacy-first alternative to Google Photos. This guide covers every aspect of running, operating, and extending Immich: architecture internals, configuration reference, deployment patterns, security hardening, and developer integration. Version covered: 2.7.5 (AGPL-3.0).
Note
This is the Admin & Developer Guide. For end-user workflows — uploading, sharing, albums, mobile backup — see the Immich User Guide.
1. Architecture Overview
1.1 High-Level Topology
Immich is a monorepo product composed of six runtime components:
┌────────────────────────────────────────────────────────┐
│ Clients: Web (SvelteKit), Mobile (Flutter), CLI │
└────────────────┬───────────────────────────────────────┘
│ HTTP/WebSocket (port 2283)
┌────────────────▼───────────────────────────────────────┐
│ immich-server (NestJS / Node.js 24+) │
│ ├─ API Worker (HTTP + WebSocket) │
│ ├─ Microservices Worker (BullMQ consumers) │
│ └─ Maintenance Worker (backup + schema ops) │
└──────┬──────────────┬─────────────────────────────────┘
│ │
┌──────▼──────┐ ┌────▼────────────────────────────────┐
│ PostgreSQL │ │ Redis / Valkey (BullMQ broker) │
│ (port 5432) │ │ (port 6379) │
└─────────────┘ └─────────────────────────────────────┘
│
┌──────▼──────────────────────────────────────────────┐
│ immich-machine-learning (Python / FastAPI) │
│ Gunicorn + Uvicorn, port 3003 │
│ ONNX Runtime — CLIP · FaceNet · OCR │
└─────────────────────────────────────────────────────┘
All persistent state lives in PostgreSQL and the media filesystem. Redis is ephemeral — losing it causes no data loss, only in-flight job loss.
1.2 immich-server — NestJS Runtime
The server binary runs as three distinct worker types (controlled by environment variables):
| Worker | Enum Value | Responsibilities |
|---|---|---|
Api |
ImmichWorker.Api |
HTTP REST, WebSocket, SvelteKit SSR, file upload |
Microservices |
ImmichWorker.Microservices |
BullMQ job consumers: thumbnails, ML, metadata, transcoding |
Maintenance |
ImmichWorker.Maintenance |
Database backup, schema integrity, maintenance auth |
A single container runs all three workers by default. Separate them at scale using IMMICH_WORKERS_INCLUDE / IMMICH_WORKERS_EXCLUDE.
Key server characteristics:
- Framework: NestJS with full DI, Guards, Interceptors, and BullMQ integration
- Static asset serving:
sirvfor the compiled SvelteKit bundle - Request timeout: 24 hours (accommodates very large video uploads)
- Security headers: Helmet middleware (configurable via
IMMICH_HELMET_FILE) - Validation:
ZodValidationPipe+ZodSerializerInterceptor - OpenAPI spec: auto-generated from decorators
1.3 NestJS Module Architecture
ApiModule (HTTP, port 2283)
├── 64 controllers: activity, album, api-key, asset, auth,
│ download, face, job, library, map, memory, notification,
│ partner, person, plugin, queue, search, server,
│ session, shared-link, stack, sync, system-config,
│ system-metadata, tag, timeline, trash, user, version,
│ view, workflow …
├── 48 services (all extending BaseService)
├── 58 repository files (Kysely-backed)
├── Middleware: AuthGuard, ErrorInterceptor,
│ FileUploadInterceptor, LoggingInterceptor,
│ GlobalExceptionFilter
└── ZodValidationPipe, ZodSerializerInterceptor, BullMQ queues
MicroservicesModule (background workers)
├── Same services + repositories (no controllers)
└── BullMQ queue processors
MaintenanceModule (privileged ops)
├── Config, Logging, Storage, Process, Database, User,
│ SystemMetadata, App repos
├── MaintenanceWorkerController
├── DatabaseBackupService
└── MaintenanceAuthGuard
ImmichAdminModule (CLI, no HTTP)
├── All services + repositories
└── nest-commander commands
1.4 immich-machine-learning — Python Service
The ML service runs as a standalone Python/FastAPI application:
- Runtime: Gunicorn (process manager) + Uvicorn (ASGI workers)
- Inference engine: ONNX Runtime
- Default port: 3003 (overridden by
IMMICH_PORT) - Model cache: in-memory with configurable TTL (default 300 seconds)
- ANN: Approximate nearest-neighbor search for fast CLIP embedding lookup
Models used by default:
| Task | Default Model | Notes |
|---|---|---|
| Smart search (CLIP) | ViT-B-32__openai |
512-dimension embeddings |
| Facial recognition | buffalo_l |
Min score 0.7, max distance 0.5 |
| OCR | PP-OCRv5_mobile |
Text in images |
| Duplicate detection | (CLIP embeddings reused) | Max distance 0.01 |
Hardware acceleration backends (ONNX Runtime execution providers):
| Backend | Compose override | Use case |
|---|---|---|
| CUDA | hwaccel.ml.yml → cuda |
NVIDIA GPU |
| ROCm | hwaccel.ml.yml → rocm |
AMD GPU |
| OpenVINO | hwaccel.ml.yml → openvino / openvino-wsl |
Intel hardware |
| ARM NN | hwaccel.ml.yml → armnn |
Arm SoCs |
| RKNN | hwaccel.ml.yml → rknn |
Rockchip NPU |
| CPU (default) | hwaccel.ml.yml → cpu |
Software inference |
1.5 PostgreSQL — Database Layer
Immich requires a custom PostgreSQL image (ghcr.io/immich-app/postgres:14-vectorchord) that ships with vector extensions pre-installed. Using a stock PostgreSQL image will fail at startup.
| Requirement | Value |
|---|---|
| Minimum version | >=14.0.0 |
| VectorChord | >=0.3 <2 |
| pgvecto.rs | >=0.2 <0.4 (alternative) |
| pgvector | >=0.5 <1 (alternative) |
| Additional extensions | cube, earthdistance (geo queries) |
Recommended shm_size |
128mb |
| Data checksums | Enabled via POSTGRES_INITDB_ARGS=--data-checksums |
Important
Always use the Immich-provided PostgreSQL image. The vector extensions (VectorChord, pgvecto.rs, pgvector) are required for smart search and facial recognition. A vanilla PostgreSQL container will not work.
1.6 Redis / Valkey — Queue Broker
- Image:
valkey:9 - Role: BullMQ job queue broker only (no application data stored)
- Health check:
redis-cli ping - Losing Redis data does not lose photos or user data — it only loses queued jobs that have not yet been processed. Jobs will be re-queued on next trigger.
1.7 BullMQ Job Queues
All background processing is driven by BullMQ queues backed by Redis. Default concurrency values (tunable via SystemConfig):
| Queue | Default Concurrency | Purpose |
|---|---|---|
backgroundTask |
5 | Housekeeping tasks |
smartSearch |
2 | CLIP embedding generation |
metadataExtraction |
5 | EXIF, GPS, XMP extraction |
faceDetection |
2 | Facial recognition pipeline |
search |
5 | Search index updates |
sidecar |
5 | XMP sidecar file handling |
library |
5 | External library scanning |
migration |
5 | Asset file migrations |
thumbnailGeneration |
3 | Thumbnail + preview generation |
videoConversion |
1 | FFmpeg transcoding |
notification |
5 | Email / push notifications |
ocr |
1 | OCR text extraction |
workflow |
5 | User-defined workflows |
editor |
2 | Edit operations |
Tip
videoConversionruns at concurrency 1 by default — video transcoding is CPU/GPU-bound. Increase this only if you have hardware acceleration configured and sufficient resources.
1.8 Web Frontend — SvelteKit
The web frontend is a SvelteKit application served via SSR from the immich-server process using sirv for static assets. The SvelteKit app uses Svelte 5 runes and file-based routing. It communicates with the backend entirely through the REST API (auto-generated TypeScript SDK from the OpenAPI spec).
1.9 Mobile App — Flutter
The Flutter mobile app (iOS + Android) handles background photo backup, offline viewing, and full-featured library management. The mobile app communicates with the same REST API as the web frontend. OAuth callback URL: app.immich:///oauth-callback.
1.10 CLI — @immich/cli
A separate CLI package built with Commander.js that wraps the auto-generated TypeScript SDK. The CLI is distinct from the immich-admin server-side admin CLI described in Section 5.
2. System Requirements
2.1 Software Requirements
| Component | Minimum | Notes |
|---|---|---|
| Docker | Latest stable | Docker Compose v2 required |
| Docker Compose | v2.x | docker compose (not docker-compose) |
| Node.js (server) | 24+ | Bundled inside container |
| Python (ML) | 3.x | Bundled inside container |
| PostgreSQL | 14.0.0+ | Must use Immich custom image |
| Redis/Valkey | 7+ | Valkey 9 recommended |
2.2 Hardware Requirements
Minimum (small home deployment):
| Resource | Minimum | Recommended |
|---|---|---|
| CPU | 2 cores | 4+ cores |
| RAM | 4 GB | 8 GB |
| Storage | Sufficient for media library | SSD preferred for database |
| Network | 100 Mbps | 1 Gbps for fast bulk import |
Notes:
- ML inference (CLIP, face detection) is compute-intensive. Dedicated ML hardware acceleration dramatically reduces processing time for large libraries.
- The PostgreSQL vector index (
cube, VectorChord) requires RAM proportional to library size. - Video transcoding (
videoConversionqueue) is the most resource-intensive background task. Hardware acceleration (NVENC, QuickSync, VAAPI) is strongly recommended for libraries with significant video content.
2.3 Storage Layout
Default storage paths inside the container:
| Path | Purpose |
|---|---|
/usr/src/app/upload/ |
Default media location |
/usr/src/app/upload/library/ |
Original asset files |
/usr/src/app/upload/thumbs/ |
Thumbnails and previews |
/usr/src/app/upload/profile/ |
User profile images |
/usr/src/app/upload/backups/ |
Database backups |
~/.cache/immich_ml/ |
ML model cache (ML container) |
Override the media root with IMMICH_MEDIA_LOCATION.
Warning
Always bind-mount the media directory to a persistent volume. Container restarts with no volume will destroy all uploaded assets.
3. Configuration Reference
Immich has two layers of configuration:
- Environment variables — set at container start, control infrastructure connections and runtime behavior
- SystemConfig — runtime configuration stored in PostgreSQL, editable via the web UI Admin panel or REST API
When IMMICH_CONFIG_FILE is set, runtime configuration is loaded from a YAML/JSON file on disk instead of the database. You cannot update SystemConfig via the UI or API while a config file is in use.
3.1 Server Environment Variables
| Variable | Default | Description |
|---|---|---|
IMMICH_PORT |
2283 |
HTTP server listen port |
IMMICH_ENV |
— | Environment tag (e.g., testing) |
IMMICH_LOG_LEVEL |
log |
Log level for the server process |
IMMICH_MACHINE_LEARNING_ENABLED |
true |
Disable ML entirely by setting to false |
IMMICH_MACHINE_LEARNING_URL |
http://immich-machine-learning:3003 |
ML service endpoint |
IMMICH_MEDIA_LOCATION |
/usr/src/app/upload |
Override media storage root |
IMMICH_PROCESS_INVALID_IMAGES |
— | Process images that fail normal validation |
IMMICH_ALLOW_EXTERNAL_PLUGINS |
— | Enable external plugin loading |
IMMICH_ALLOW_SETUP |
— | Allow initial admin setup |
IMMICH_WORKERS_INCLUDE |
— | Comma-separated list of workers to enable |
IMMICH_WORKERS_EXCLUDE |
— | Comma-separated list of workers to disable |
IMMICH_TRUSTED_PROXIES |
— | CIDR list of trusted reverse proxy IPs |
IMMICH_API_METRICS_PORT |
— | Prometheus metrics port for API worker |
IMMICH_MICROSERVICES_METRICS_PORT |
— | Prometheus metrics port for microservices worker |
IMMICH_TELEMETRY_INCLUDE |
— | Set to all to enable all metrics |
IMMICH_TELEMETRY_EXCLUDE |
— | Telemetry exclusions |
IMMICH_HELMET_FILE |
— | Path to Helmet configuration file |
IMMICH_CONFIG_FILE |
— | Path to YAML/JSON config file (disables DB config) |
NO_COLOR |
— | Disable ANSI color in log output |
3.2 Database Environment Variables
| Variable | Default | Description |
|---|---|---|
DB_URL |
— | Full connection URL (overrides individual fields) |
DB_HOSTNAME |
database |
PostgreSQL hostname |
DB_USERNAME |
postgres |
Database user |
DB_PASSWORD |
postgres |
Database password |
DB_DATABASE_NAME |
immich |
Database name |
DB_SSL_MODE |
— | SSL mode (require, verify-full, etc.) |
DB_SKIP_MIGRATIONS |
— | Skip automatic schema migrations on startup |
Important
Change
DB_PASSWORDfrom the defaultpostgresbefore exposing Immich to any network. The default credentials are well-known.
3.3 Redis Environment Variables
| Variable | Default | Description |
|---|---|---|
REDIS_URL |
— | Full Redis/Valkey URL (overrides individual fields) |
REDIS_HOSTNAME |
redis |
Redis hostname |
REDIS_USERNAME |
— | Redis username (for ACL-enabled Redis) |
REDIS_PASSWORD |
— | Redis password |
3.4 Machine Learning Environment Variables
| Variable | Default | Description |
|---|---|---|
IMMICH_HOST |
[::] |
ML service bind host |
IMMICH_PORT |
3003 |
ML service listen port |
IMMICH_LOG_LEVEL |
info |
ML log level |
MACHINE_LEARNING_CACHE_FOLDER |
~/.cache/immich_ml |
Model download and cache directory |
MACHINE_LEARNING_MODEL_TTL |
300 |
Seconds before unloading idle models |
MACHINE_LEARNING_MODEL_TTL_POLL_S |
10 |
Seconds between TTL checks |
MACHINE_LEARNING_WORKERS |
1 |
Number of Gunicorn worker processes |
MACHINE_LEARNING_WORKER_TIMEOUT |
300 |
Gunicorn worker timeout (seconds) |
MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S |
2 |
Keep-alive timeout (seconds) |
MACHINE_LEARNING_REQUEST_THREADS |
CPU count | ONNX request threads per worker |
MACHINE_LEARNING_MODEL_INTER_OP_THREADS |
0 |
ONNX inter-op parallelism (0 = auto) |
MACHINE_LEARNING_MODEL_INTRA_OP_THREADS |
0 |
ONNX intra-op parallelism (0 = auto) |
MACHINE_LEARNING_ANN |
true |
Enable ANN index for fast search |
MACHINE_LEARNING_ANN_FP16_TURBO |
false |
FP16 turbo mode for ANN |
MACHINE_LEARNING_ANN_TUNING_LEVEL |
2 |
ANN accuracy vs. speed (higher = slower, more accurate) |
MACHINE_LEARNING_RKNN |
true |
Enable RKNN (Rockchip NPU) |
MACHINE_LEARNING_RKNN_THREADS |
1 |
RKNN thread count |
MACHINE_LEARNING_PRELOAD__CLIP |
— | Model name to preload at startup |
MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION |
— | Face model name to preload at startup |
MACHINE_LEARNING_DEVICE_ID |
0 |
Primary GPU device ID |
MACHINE_LEARNING_DEVICE_IDS |
— | Comma-separated list for multi-GPU |
MACHINE_LEARNING_OPENVINO_PRECISION |
— | FP32 or FP16 for OpenVINO |
MACHINE_LEARNING_ROCM_PRECISION |
— | FP32 or FP16 for ROCm |
3.5 SystemConfig Reference (Runtime Configuration)
SystemConfig is stored in PostgreSQL and can be edited via the Administration → Settings panel in the web UI. All values below are defaults.
3.5.1 Backup
| Key | Default | Description |
|---|---|---|
backup.database.enabled |
true |
Enable scheduled database backups |
backup.database.cronExpression |
EVERY_DAY_AT_2AM |
Backup schedule |
backup.database.keepLastAmount |
14 |
Number of backups to retain |
3.5.2 FFmpeg / Video Transcoding
| Key | Default | Description |
|---|---|---|
ffmpeg.crf |
23 |
Constant Rate Factor (quality; lower = better quality, larger file) |
ffmpeg.threads |
0 |
Encoding threads (0 = auto) |
ffmpeg.preset |
ultrafast |
FFmpeg encoding preset |
ffmpeg.targetVideoCodec |
H264 |
Output video codec |
ffmpeg.targetAudioCodec |
AAC |
Output audio codec |
ffmpeg.targetResolution |
720 |
Target resolution height (pixels) |
ffmpeg.maxBitrate |
0 |
Max bitrate (0 = unlimited) |
ffmpeg.transcode |
Required |
When to transcode: Required, Optimal, All, Disabled |
ffmpeg.accel |
Disabled |
Hardware acceleration backend |
ffmpeg.tonemap |
Hable |
HDR tonemapping algorithm |
3.5.3 Job Concurrency
Concurrency for each BullMQ queue is tunable at runtime. See Section 1.7 for defaults.
3.5.4 Logging
| Key | Default | Description |
|---|---|---|
logging.enabled |
true |
Enable application logging |
logging.level |
Log |
Minimum log level |
3.5.5 Machine Learning
| Key | Default | Description |
|---|---|---|
machineLearning.enabled |
true |
Enable ML features globally |
machineLearning.urls |
— | ML service URL(s) |
machineLearning.clip.modelName |
ViT-B-32__openai |
CLIP model for smart search |
machineLearning.duplicateDetection.maxDistance |
0.01 |
Embedding distance threshold for duplicates |
machineLearning.facialRecognition.modelName |
buffalo_l |
Face detection/recognition model |
machineLearning.facialRecognition.minScore |
0.7 |
Minimum confidence score |
machineLearning.facialRecognition.maxDistance |
0.5 |
Maximum embedding distance for clustering |
machineLearning.facialRecognition.minFaces |
3 |
Minimum faces to form a named person cluster |
machineLearning.ocr.modelName |
PP-OCRv5_mobile |
OCR model |
3.5.6 Maps & Geocoding
| Key | Default | Description |
|---|---|---|
map.enabled |
true |
Enable map view |
map.lightStyle |
tiles.immich.cloud |
Tile source for light mode |
map.darkStyle |
tiles.immich.cloud |
Tile source for dark mode |
reverseGeocoding.enabled |
true |
Enable GPS-to-place-name lookup |
3.5.7 OAuth / SSO
| Key | Default | Description |
|---|---|---|
oauth.autoLaunch |
false |
Redirect to OAuth provider on login page load |
oauth.autoRegister |
true |
Auto-create accounts for new OAuth users |
oauth.buttonText |
— | Label for the OAuth sign-in button |
oauth.clientId |
— | OAuth client ID |
oauth.clientSecret |
— | OAuth client secret |
oauth.scope |
openid email profile |
OAuth scopes to request |
oauth.signingAlgorithm |
RS256 |
JWT signing algorithm |
oauth.tokenEndpointAuthMethod |
client_secret_post |
Token endpoint auth method |
oauth.timeout |
30000 |
OAuth request timeout (ms) |
3.5.8 Password Login
| Key | Default | Description |
|---|---|---|
passwordLogin.enabled |
true |
Allow username/password authentication |
Caution
If you disable both
passwordLoginandoauth, users (including admins) will be unable to log in. Useimmich-admin enable-password-loginto recover.
3.5.9 Storage Template
| Key | Default | Description |
|---|---|---|
storageTemplate.enabled |
false |
Organize files by template on disk |
storageTemplate.template |
{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}} |
Path template using date/metadata tokens |
3.5.10 Image Quality
| Key | Default | Description |
|---|---|---|
image.thumbnail.format |
webp |
Thumbnail format |
image.thumbnail.size |
250 |
Thumbnail size (pixels, longest edge) |
image.thumbnail.quality |
80 |
Thumbnail quality (1–100) |
image.preview.format |
jpeg |
Preview format |
image.preview.size |
1440 |
Preview size (pixels, longest edge) |
image.preview.quality |
80 |
Preview quality (1–100) |
image.colorspace |
P3 |
Output colorspace |
image.fullsize.enabled |
false |
Serve original-resolution images |
3.5.11 Trash
| Key | Default | Description |
|---|---|---|
trash.enabled |
true |
Enable soft-delete trash bin |
trash.days |
30 |
Days before permanent deletion |
3.5.12 Library
| Key | Default | Description |
|---|---|---|
library.scan.enabled |
true |
Enable scheduled external library scans |
library.scan.cronExpression |
EVERY_DAY_AT_MIDNIGHT |
Scan schedule |
library.watch.enabled |
false |
Enable filesystem watching (inotify) |
3.5.13 Notifications (SMTP)
SMTP notifications are disabled by default. Configure via Administration → Notifications.
3.5.14 Server Behavior
| Key | Default | Description |
|---|---|---|
server.externalDomain |
— | Public-facing domain for generated links |
server.loginPageMessage |
— | Custom message shown on login page |
server.publicUsers |
true |
Whether user list is public within the instance |
3.5.15 Nightly Tasks
| Key | Default | Description |
|---|---|---|
nightlyTasks.startTime |
00:00 |
UTC time for nightly task window |
nightlyTasks.databaseCleanup |
true |
Purge stale DB records |
nightlyTasks.generateMemories |
true |
Generate "On This Day" memories |
nightlyTasks.syncQuotaUsage |
true |
Recalculate storage quotas |
nightlyTasks.missingThumbnails |
true |
Queue assets with missing thumbnails |
nightlyTasks.clusterNewFaces |
true |
Cluster newly detected faces |
3.5.16 User Management
| Key | Default | Description |
|---|---|---|
user.deleteDelay |
7 |
Days to retain user data after account deletion |
newVersionCheck.enabled |
true |
Check GitHub for new Immich releases |
metadata.faces.import |
false |
Import face metadata from EXIF |
4. Deployment & Scaling
4.1 Standard Docker Compose Deployment
The canonical production deployment uses docker/docker-compose.prod.yml:
# Key services and ports
services:
immich-server:
ports: ["2283:2283"] # HTTP API + Web UI
immich-machine-learning:
ports: ["3003:3003"] # ML API (internal only)
redis:
# Internal only — no exposed port
database:
ports: ["5432:5432"] # PostgreSQL (restrict in production)
immich-prometheus:
ports: ["9090:9090"] # Prometheus (restrict in production)
immich-grafana:
ports: ["3000:3000"] # Grafana (restrict in production)
Minimal production .env:
# Database
DB_PASSWORD=change_me_strong_password
DB_USERNAME=immich
DB_DATABASE_NAME=immich
# Paths
UPLOAD_LOCATION=/path/to/your/media
DB_DATA_LOCATION=/path/to/postgres/data
4.2 Rootless Deployment
docker/docker-compose.rootless.yml adds security hardening:
services:
immich-server:
user: '1000:1000'
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
Use rootless mode whenever the host allows it. Ensure the volume mount paths are owned by UID/GID 1000.
4.3 Splitting Workers for Scale
By default, a single immich-server container runs all three worker types. For high-load deployments, separate them:
# API-only container
IMMICH_WORKERS_INCLUDE=api docker compose up
# Microservices-only container (background jobs)
IMMICH_WORKERS_INCLUDE=microservices docker compose up
Note
When running split workers, all instances share the same PostgreSQL and Redis. The BullMQ queue ensures jobs are processed exactly once — multiple microservices workers safely consume from the same queues.
4.4 Hardware Acceleration — Video Transcoding
Override the base compose file with a hardware acceleration profile:
# NVIDIA GPU (NVENC)
docker compose -f docker-compose.yml -f hwaccel.transcoding.yml --profile nvenc up
# Intel Quick Sync
docker compose -f docker-compose.yml -f hwaccel.transcoding.yml --profile quicksync up
# AMD/Intel VAAPI (Linux)
docker compose -f docker-compose.yml -f hwaccel.transcoding.yml --profile vaapi up
# VAAPI on WSL2
docker compose -f docker-compose.yml -f hwaccel.transcoding.yml --profile vaapi-wsl up
# Rockchip RKMPP
docker compose -f docker-compose.yml -f hwaccel.transcoding.yml --profile rkmpp up
Then set ffmpeg.accel in SystemConfig to match the backend.
4.5 Hardware Acceleration — Machine Learning
# NVIDIA CUDA
docker compose -f docker-compose.yml -f hwaccel.ml.yml --profile cuda up
# AMD ROCm
docker compose -f docker-compose.yml -f hwaccel.ml.yml --profile rocm up
# Intel OpenVINO
docker compose -f docker-compose.yml -f hwaccel.ml.yml --profile openvino up
# Rockchip RKNN
docker compose -f docker-compose.yml -f hwaccel.ml.yml --profile rknn up
# ARM NN
docker compose -f docker-compose.yml -f hwaccel.ml.yml --profile armnn up
4.6 CLIP Model Selection
Immich ships with 40+ supported CLIP models. Models differ in embedding dimensions (512 to 1536), search quality, and inference speed.
Key model families:
| Model Family | Dimensions | Notes |
|---|---|---|
ViT-B-32__openai (default) |
512 | Fast, good quality |
ViT-L-14__openai |
768 | Higher quality, slower |
SigLIP variants |
768–1152 | Google's improved CLIP |
SigLIP2 variants |
768–1152 | Latest Google CLIP |
NLLB-CLIP variants |
512–768 | Multilingual support |
Warning
Changing the CLIP model requires re-embedding your entire library. This is a compute-intensive operation. After changing the model in SystemConfig, trigger Jobs → Smart Search → Run All from the admin panel.
4.7 Reverse Proxy Configuration
Set IMMICH_TRUSTED_PROXIES to your reverse proxy's IP/CIDR to enable proper client IP forwarding:
IMMICH_TRUSTED_PROXIES=172.16.0.0/12,192.168.0.0/16
Set server.externalDomain in SystemConfig to the public URL so generated share links are correct.
nginx example:
location / {
proxy_pass http://immich-server:2283;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 50000M;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
The proxy_read_timeout must match Immich's 24-hour request timeout to support large video uploads.
4.8 File-Based Configuration
To use a configuration file instead of database-stored SystemConfig:
IMMICH_CONFIG_FILE=/etc/immich/config.json
{
"ffmpeg": {
"crf": 23,
"preset": "ultrafast",
"targetVideoCodec": "H264"
},
"machineLearning": {
"enabled": true,
"clip": { "modelName": "ViT-B-32__openai" }
}
}
Caution
While
IMMICH_CONFIG_FILEis set, the SystemConfig API and admin UI settings panel are disabled. Any attempt to update settings via the API returns:"Cannot update configuration while IMMICH_CONFIG_FILE is in use".
5. Operations
5.1 immich-admin CLI
The immich-admin CLI runs inside the server container and provides privileged administrative operations that bypass the HTTP API. Always use this CLI to recover from authentication lockout scenarios.
docker exec -it immich_server immich-admin <command>
| Command | Description |
|---|---|
list-users |
List all user accounts with IDs and status |
grant-admin <email> |
Grant admin privileges to a user |
revoke-admin <email> |
Revoke admin privileges from a user |
reset-admin-password |
Interactively reset the admin password |
enable-password-login |
Re-enable password authentication |
disable-password-login |
Disable password authentication (OAuth only) |
enable-oauth-login |
Enable OAuth SSO |
disable-oauth-login |
Disable OAuth SSO |
enable-maintenance-mode |
Put server in maintenance mode |
disable-maintenance-mode |
Exit maintenance mode |
change-media-location |
Update DB file paths after storage migration |
schema-check |
Validate database schema integrity |
version |
Display installed Immich version |
Common recovery workflows:
# Locked out — reset admin password
docker exec -it immich_server immich-admin reset-admin-password
# Disabled password login accidentally
docker exec -it immich_server immich-admin enable-password-login
# Promote a user to admin
docker exec -it immich_server immich-admin grant-admin [email removed]
# Verify DB schema after upgrade
docker exec -it immich_server immich-admin schema-check
5.2 Database Backups
Immich performs automated PostgreSQL backups on the schedule defined in backup.database.cronExpression (default: daily at 2 AM). Backups are stored in the backups/ subdirectory of the media location.
Retention: backup.database.keepLastAmount (default: 14 backups).
Manual backup via Docker:
docker exec immich_postgres pg_dumpall -U postgres > immich_backup_$(date +%Y%m%d).sql
Restore from backup:
docker exec -i immich_postgres psql -U postgres < immich_backup_20260513.sql
Warning
After restoring a database from backup on a new machine, run
change-media-locationif the media paths differ. Inconsistent paths produce the error:"Detected an inconsistent media location".
5.3 Database Migrations
Migrations run automatically on server startup unless DB_SKIP_MIGRATIONS=true. If the schema check fails:
- Error
"Detected schema drift"— seedocs.immich.app/errors#schema-drift - Error
"Invalid upgrade path"— seedocs.immich.app/errors/#typeorm-upgrade
Never downgrade Immich without restoring a pre-upgrade database backup. Migrations are not reversible.
5.4 Job Management
The admin panel (Administration → Jobs) provides controls to:
- Run, pause, or clear individual queues
- View queue depths and processing status
- Trigger full re-processing (e.g., re-embed all assets for a new CLIP model)
5.5 External Library Management
External libraries allow Immich to index photos from arbitrary filesystem paths without moving files. Configuration:
- Mount the external path into the container
- Create a Library in Administration → Libraries
- Configure path(s) for the library
- Run a scan (manual or scheduled via
library.scan.cronExpression)
Filesystem watching (library.watch.enabled) uses inotify and reacts to changes in real time, but is disabled by default due to resource implications on large libraries.
5.6 Storage Template Migration
Enable storageTemplate.enabled to reorganize files on disk using a date/metadata template. Default template: {{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}.
Caution
Enabling storage templates moves files on disk. Ensure sufficient free space and a working backup before enabling. The migration runs as a background job in the
migrationqueue.
5.7 Telemetry and Metrics
Immich exposes Prometheus metrics when telemetry is configured:
IMMICH_TELEMETRY_INCLUDE=all
IMMICH_API_METRICS_PORT=8081
IMMICH_MICROSERVICES_METRICS_PORT=8082
The production Docker Compose ships with immich-prometheus and immich-grafana pre-configured. Access Grafana at port 3000 to view dashboards for queue depths, job throughput, API latency, and ML processing times.
5.8 Audit Log
Immich maintains an audit log of user actions. The audit log retains events for a maximum of 100 days (AUDIT_LOG_MAX_DURATION).
5.9 Maintenance Mode
Maintenance mode prevents user operations while allowing admin tasks:
docker exec -it immich_server immich-admin enable-maintenance-mode
# ... perform maintenance ...
docker exec -it immich_server immich-admin disable-maintenance-mode
6. Performance
6.1 Database Tuning
The vector extension (VectorChord / pgvecto.rs) drives CLIP search performance. Key tuning parameters for PostgreSQL:
-- Verify extensions are installed
SELECT * FROM pg_extension WHERE extname IN ('vector', 'vectorchord', 'vectors');
-- Check vector index status
SELECT schemaname, tablename, indexname FROM pg_indexes
WHERE indexname LIKE '%embedding%';
PostgreSQL container settings:
shm_size: 128mb— required for vector operationsPOSTGRES_INITDB_ARGS=--data-checksums— data integrity
For large libraries (100K+ assets), consider increasing shared_buffers and effective_cache_size in postgresql.conf.
6.2 Machine Learning Performance
Model preloading eliminates cold-start latency on first request after restart:
MACHINE_LEARNING_PRELOAD__CLIP=ViT-B-32__openai
MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION=buffalo_l
Multi-worker ML (if memory headroom exists):
MACHINE_LEARNING_WORKERS=2
Each Gunicorn worker loads models independently into memory. With 2 workers and both models loaded, expect substantially higher RAM usage.
Thread tuning for CPU-only inference:
# Let ONNX choose (usually best)
MACHINE_LEARNING_MODEL_INTER_OP_THREADS=0
MACHINE_LEARNING_MODEL_INTRA_OP_THREADS=0
# Or pin explicitly for a known CPU
MACHINE_LEARNING_MODEL_INTRA_OP_THREADS=4
ANN (Approximate Nearest Neighbor) is enabled by default and provides near-instant search after the index is built. Disable only to debug search accuracy:
MACHINE_LEARNING_ANN=false
6.3 Queue Concurrency Tuning
Tune job concurrency in SystemConfig based on your hardware:
| Scenario | Recommendation |
|---|---|
| Low-power NAS | Reduce all concurrency to 1–2 |
| High-core server, CPU transcoding | Increase videoConversion to 2–4 |
| GPU transcoding (NVENC) | videoConversion = 4–8 (GPU handles parallelism) |
| Dedicated ML GPU | smartSearch + faceDetection = 4–8 |
| Slow storage | Reduce thumbnailGeneration to 1–2 |
6.4 FFmpeg Optimization
For quality vs. size tradeoffs in systemConfig.ffmpeg:
# Better quality, larger files
crf: 18, preset: slow
# Balanced (default)
crf: 23, preset: ultrafast
# Smallest files
crf: 28, preset: ultrafast
HDR content: set tonemap to Hable (default), Reinhard, or Mobius depending on your display targets.
6.5 Network and Upload Performance
- Large video uploads are protected by a 24-hour request timeout
- For bulk initial imports, use the CLI upload tool rather than the web UI
- For external libraries on NAS, ensure the mount uses appropriate network filesystem tuning (NFS with
noatime, appropriatersize/wsize)
7. Security
7.1 Authentication Architecture
Immich supports two authentication mechanisms:
| Method | Config Key | Default |
|---|---|---|
| Password login | passwordLogin.enabled |
true |
| OAuth/OIDC SSO | oauth.clientId + related |
disabled |
Password hashing: bcrypt with SALT_ROUNDS = 10.
Session management: Sessions are tracked in the database. Admins can revoke sessions via Administration → Users → Sessions.
API Keys: Users generate API keys from their profile. Keys are stored as bcrypt hashes.
7.2 OAuth / OIDC Configuration
Immich implements OIDC (OpenID Connect). Configure in Administration → Settings → Authentication:
clientId: <your OIDC client ID>
clientSecret: <your OIDC client secret>
scope: openid email profile
signingAlgorithm: RS256
tokenEndpointAuthMethod: client_secret_post
Auto-registration (oauth.autoRegister: true): New users logging in via OAuth are automatically provisioned. Disable if you want to pre-create all accounts.
Auto-launch (oauth.autoLaunch: false): When true, the login page immediately redirects to the OAuth provider.
Mobile OAuth callback URL: app.immich:///oauth-callback
Important
Known issue (#26839): OAuth account linking fails if email addresses differ in case between the identity provider and Immich. Ensure your IdP sends lowercase email addresses, or normalize on the IdP side.
7.3 HTTP Security Headers
Immich applies Helmet middleware by default, which sets standard security headers including Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, and Strict-Transport-Security (when TLS is terminated upstream).
Customize Helmet configuration by providing a JSON file and setting IMMICH_HELMET_FILE.
7.4 Trusted Proxies
When running behind a reverse proxy, set IMMICH_TRUSTED_PROXIES to the proxy's IP range so client IPs are correctly recorded in audit logs:
IMMICH_TRUSTED_PROXIES=10.0.0.1/32,172.17.0.0/16
Without this, all traffic appears to originate from the proxy IP.
7.5 Network Isolation
Production deployments should:
- Expose only port 2283 (via reverse proxy with TLS)
- Keep PostgreSQL (5432), Redis (6379), ML (3003), Prometheus (9090), and Grafana (3000) on internal Docker networks only
- Use rootless containers (
docker-compose.rootless.yml) where possible
7.6 Storage Security
- Media files are served with signed download tokens
- Shared links use separate authentication (no user session required)
- Trash retention (
trash.days: 30) protects against accidental deletion
7.7 Admin Account Security
- Only one admin account is permitted at initial setup
- Additional admins are promoted via
immich-admin grant-admin - The admin password can be reset via
immich-admin reset-admin-password(requires container access) - Error:
"The server already has an admin"prevents multiple admin setups
7.8 Quota Management
Storage quotas are configurable per user. Quota usage is reconciled nightly (nightlyTasks.syncQuotaUsage: true). When a user exceeds their quota, uploads fail with: "Quota has been exceeded!".
8. Developer Internals
8.1 Repository Structure
immich-app/immich/
├── server/ NestJS backend (TypeScript)
│ └── src/
│ ├── controllers/ 64 HTTP controllers
│ ├── services/ 48 services (extending BaseService)
│ ├── repositories/ 58 Kysely-backed repository files
│ ├── commands/ immich-admin CLI commands
│ ├── middleware/ Guards, interceptors, filters
│ ├── schema/ Database schema (Kysely)
│ ├── config.ts Queue defaults, SystemConfig structure
│ └── constants.ts App-wide constants
├── web/ SvelteKit frontend (TypeScript, Svelte 5)
├── mobile/ Flutter mobile app (Dart)
├── machine-learning/ Python ML service
│ └── app/
│ ├── models/ ONNX model wrappers
│ ├── sessions/ ONNX session management
│ └── ann/ ANN index
├── cli/ @immich/cli (Commander.js)
├── open-api/ OpenAPI spec + TypeScript SDK
├── docker/ Compose files (dev, prod, rootless, hwaccel)
├── e2e/ End-to-end tests (Vitest + Playwright)
├── i18n/ Translations (79 locale files)
├── docs/ Docusaurus documentation site
├── plugins/ Plugin system
└── deployment/ Deployment modules (Cloudflare)
8.2 Service Layer Architecture
All 48 services extend BaseService, which provides access to injected repository instances, Logger, Config, and the event emitter.
Services organized by domain:
| Domain | Services |
|---|---|
| Assets | AssetService, AssetMediaService, MediaService, StorageService, StorageTemplateService |
| Search & ML | SearchService, SmartInfoService, OcrService |
| People & Faces | PersonService |
| Users & Auth | UserService, UserAdminService, AuthService, AuthAdminService, SessionService, ApiKeyService |
| Albums | AlbumService |
| Libraries | LibraryService |
| Jobs | JobService, QueueService |
| System | SystemConfigService, SystemMetadataService, ServerService, DatabaseService, DatabaseBackupService |
| Notifications | NotificationService, NotificationAdminService |
| Social | PartnerService, SharedLinkService, ActivityService, MemoryService |
| Organization | TagService, StackService, DuplicateService, TimelineService |
| Infrastructure | TelemetryService, MapService, VersionService, PluginService |
| Workflows | WorkflowService |
| Maintenance | MaintenanceService |
8.3 Repository Layer
The 58 repository files implement the data access layer using Kysely — a type-safe SQL query builder:
CamelCasePlugin— automatically convertssnake_caseDB columns tocamelCaseTypeScript properties- Transactions for multi-step operations
- Repository interfaces enable dependency injection and mocking in tests
- No ORM — raw SQL via Kysely type-safe builder
- Migrations managed via Kysely migrations in
server/src/schema/
8.4 API Design
The REST API is defined via NestJS decorators, validated with Zod schemas (ZodValidationPipe), serialized with Zod interceptors (ZodSerializerInterceptor), and documented via auto-generated OpenAPI/Swagger spec.
API categories (from ApiTag enum):
Activities, Albums, ApiKeys, Assets, Authentication, AuthenticationAdmin, DatabaseBackups, Deprecated, Download, Duplicates, Faces, Jobs, Libraries, Maintenance, Map, Memories, Notifications, NotificationsAdmin, Partners, People, Plugins, Queues, Search, Server, Sessions, SharedLinks, Stacks, Sync, SystemConfig, SystemMetadata, Tags, Timeline, Trash, UsersAdmin, Users, Views, Workflows
Authentication on API calls:
x-api-key: YOUR_API_KEY
Or via session cookie for browser-based clients.
8.5 WebSocket Events
Real-time events (upload progress, job status, sync) are pushed via WebSocket. The server uses NestJS's WebSocket adapter on the same port 2283.
8.6 Plugin System
Immich has a plugin architecture (plugins/ directory, PluginService, PluginHostFunctions). External plugins are disabled by default:
IMMICH_ALLOW_EXTERNAL_PLUGINS=true
8.7 OpenAPI SDK Generation
The TypeScript SDK (open-api/) is auto-generated from the server's OpenAPI spec. Both the web frontend and CLI consume this SDK. When developing new API endpoints:
- Add controller + decorators in
server/src/controllers/ - Run the SDK generation script
- Update web/CLI clients
8.8 Machine Learning Service Internals
The Python ML service is a FastAPI application:
Session lifecycle:
- Models are loaded on first request
- Sessions are cached in memory
MACHINE_LEARNING_MODEL_TTLcontrols eviction of idle modelsMACHINE_LEARNING_MODEL_TTL_POLL_Scontrols how often the TTL check runs
ANN index:
- Built from CLIP embeddings stored in PostgreSQL
- Provides sub-millisecond search on large libraries
- Exported and reloaded between restarts
8.9 End-to-End Testing
The e2e/ directory contains:
- Vitest — API-level integration tests against a running Immich instance
- Playwright — browser automation tests for web UI flows
Run E2E tests against a local dev stack:
# Start dev environment first
docker compose -f docker/docker-compose.dev.yml up
# Run API tests
cd e2e && npm run test:api
# Run browser tests
cd e2e && npm run test:browser
8.10 Internationalization
79 locale files live in i18n/. The web frontend uses these via SvelteKit's i18n support.
8.11 Key Constants
| Constant | Value | Purpose |
|---|---|---|
POSTGRES_VERSION_RANGE |
>=14.0.0 |
Enforced at startup |
VECTORCHORD_VERSION_RANGE |
>=0.3 <2 |
Vector extension version |
SALT_ROUNDS |
10 |
bcrypt cost factor |
FACE_THUMBNAIL_SIZE |
250 |
Face crop dimensions (px) |
AUDIT_LOG_MAX_DURATION |
100 days |
Audit log retention |
MOBILE_REDIRECT |
app.immich:///oauth-callback |
OAuth mobile callback |
LOGIN_URL |
/auth/login?autoLaunch=0 |
Login page with autoLaunch bypassed |
9. Integration Points
9.1 REST API Access
Base URL: https://your-immich-domain/api
Authentication:
curl -H "x-api-key: YOUR_API_KEY" https://your-immich/api/server/ping
Core endpoint categories:
| Category | Base Path | Description |
|---|---|---|
| Assets | /api/assets |
Upload, list, update, delete assets |
| Albums | /api/albums |
Album management |
| People | /api/people |
Named people, face management |
| Search | /api/search |
Smart search, metadata search |
| Libraries | /api/libraries |
External library management |
| Jobs | /api/jobs |
Queue status and control |
| System Config | /api/system-config |
Runtime config (admin only) |
| Server | /api/server |
Version, stats, health |
| Users (Admin) | /api/admin/users |
User management |
| Auth | /api/auth |
Login, logout, sessions |
| Shared Links | /api/shared-links |
Public share management |
| Memories | /api/memories |
"On This Day" memories |
| Tags | /api/tags |
Tag management |
| Stacks | /api/stacks |
Asset stack management |
| Duplicates | /api/duplicates |
Duplicate detection results |
| Workflows | /api/workflows |
Workflow automation |
| Sync | /api/sync |
Incremental sync for mobile |
| Timeline | /api/timeline |
Timeline view data |
| Notifications | /api/notifications |
User notifications |
| Partners | /api/partners |
Partner sharing |
9.2 @immich/cli — Bulk Operations
The @immich/cli package provides command-line access for bulk operations:
npm install -g @immich/cli
# Configure
immich login https://your-immich/api YOUR_API_KEY
# Bulk upload
immich upload --recursive /path/to/photos/
# Upload to album
immich upload --album "Vacation 2026" /path/to/vacation/
The CLI uses the auto-generated TypeScript SDK and communicates via the same REST API as the web UI.
9.3 Prometheus Metrics Integration
Enable metrics:
IMMICH_TELEMETRY_INCLUDE=all
IMMICH_API_METRICS_PORT=8081
IMMICH_MICROSERVICES_METRICS_PORT=8082
Add to Prometheus scrape_configs:
scrape_configs:
- job_name: 'immich-api'
static_configs:
- targets: ['immich-server:8081']
- job_name: 'immich-microservices'
static_configs:
- targets: ['immich-server:8082']
The production Docker Compose includes pre-configured Prometheus and Grafana services.
9.4 SMTP / Email Notifications
Configure SMTP in Administration → Notifications. Immich can send new device login alerts, shared album invitations, and storage quota warnings via the NotificationService / NotificationAdminService pipeline.
9.5 Webhook Integration
Immich supports outbound webhooks for event notifications, configured in Administration → Webhooks. Webhooks are signed with HMAC for authenticity verification.
9.6 OAuth Provider Integration
Immich works as an OIDC relying party. Configure your identity provider with:
- Redirect URI:
https://your-immich/auth/login - Mobile redirect URI:
app.immich:///oauth-callback - Required claims:
email,sub(and optionallyname,picture)
10. Troubleshooting
Error: "Password login has been disabled"
Cause: passwordLogin.enabled was set to false and OAuth is not functioning.
Solution:
docker exec -it immich_server immich-admin enable-password-login
Prevention: Test OAuth login before disabling password login. Keep a break-glass admin procedure documented.
Error: "Incorrect email or password"
Cause: Wrong credentials, or account was created via OAuth and has no password.
Solution: Use the OAuth flow if the account was OAuth-created. For admin password reset:
docker exec -it immich_server immich-admin reset-admin-password
Prevention: Ensure users know which auth method their account uses.
Error: "Quota has been exceeded!"
Cause: User's storage quota has been reached.
Solution: Increase or remove the user's quota in Administration → Users → edit user. Or wait for nightly quota sync to reconcile actual usage.
Prevention: Set quotas with sufficient headroom and monitor usage in the admin dashboard.
Error: "OAuth is not enabled"
Cause: Client is attempting OAuth flow but oauth.clientId is not configured.
Solution: Configure OAuth settings in Administration → Settings → Authentication.
Prevention: Validate OAuth configuration before communicating OAuth availability to users.
Error: "The server already has an admin"
Cause: Attempting to run initial setup when an admin account already exists.
Solution: Log in with the existing admin account. If credentials are lost:
docker exec -it immich_server immich-admin reset-admin-password
Prevention: Document admin credentials securely at setup time.
Error: "Cannot update configuration while IMMICH_CONFIG_FILE is in use"
Cause: IMMICH_CONFIG_FILE env var is set, disabling database-backed SystemConfig.
Solution: Edit the config file directly, then restart the server. Or remove IMMICH_CONFIG_FILE to switch back to database config.
Prevention: Choose one config method and document it. Do not mix file-based and database-based config.
Error: "Smart search is not enabled"
Cause: machineLearning.enabled is false, or the ML container is unreachable.
Solution:
# Verify ML container is running
docker ps | grep machine-learning
# Check ML health
curl http://immich-machine-learning:3003/ping
# Check ML container logs
docker logs immich_machine_learning
Verify IMMICH_MACHINE_LEARNING_URL matches the ML container's address.
Prevention: Monitor ML container health. The prod compose includes health checks.
Error: "Detected an inconsistent media location"
Cause: Database records reference file paths that no longer exist on disk (e.g., after moving the media volume).
Solution:
docker exec -it immich_server immich-admin change-media-location
See docs.immich.app/errors#inconsistent-media-location for detailed steps.
Prevention: Always update IMMICH_MEDIA_LOCATION and run change-media-location when migrating storage.
Error: "Detected schema drift"
Cause: The database schema does not match what the current server version expects. Often occurs after a failed migration or manual DB modification.
Solution: See docs.immich.app/errors#schema-drift. Run:
docker exec -it immich_server immich-admin schema-check
Prevention: Never manually modify the Immich database schema. Always upgrade via official Docker images.
Error: "Invalid upgrade path"
Cause: Attempting to upgrade Immich by skipping major versions.
Solution: See docs.immich.app/errors/#typeorm-upgrade. Upgrade through intermediate versions sequentially.
Prevention: Follow the official upgrade guide. Always back up the database before upgrading.
Issue: ML container stuck "unhealthy" after update (GitHub #27228)
Cause: Model cache on disk is incompatible with the new ML version (format change).
Solution:
docker stop immich_machine_learning
# Clear the model cache volume
docker volume rm immich_model-cache
docker start immich_machine_learning
# Wait for models to re-download
docker logs -f immich_machine_learning
Prevention: When updating Immich, check the changelog for ML model cache format changes.
Issue: Faces disappeared (GitHub #27637)
Cause: Re-running face detection with changed thresholds or after a model update can cause re-clustering that loses named assignments.
Solution: Re-assign faces in People view. Consider exporting face assignments before major ML updates.
Prevention: Back up face assignment data before changing facialRecognition settings or ML model versions.
Issue: New photos not appearing on timeline (GitHub #27787)
Cause: Metadata extraction or thumbnail generation jobs are stuck or backed up.
Solution:
- Check job queue status in Administration → Jobs
- Look for stuck
metadataExtractionorthumbnailGenerationjobs - Clear and re-run the affected queues
Prevention: Monitor queue depths via Prometheus/Grafana.
Issue: Android app won't sync, stuck on hashing (GitHub #27846)
Cause: Network timeout or reverse proxy configuration rejecting large uploads.
Solution:
- Check reverse proxy
client_max_body_size— must be large enough for video files - Verify
proxy_read_timeoutis set to at least 86400s to match Immich's 24-hour request timeout - Restart the mobile app sync process
Prevention: Configure reverse proxy timeouts appropriately (see Section 4.7).
Issue: OAuth login fails with email case mismatch (GitHub #26839)
Cause: Identity provider sends [email removed] but Immich stored [email removed] at registration.
Solution: Manually update the email in the database to match the IdP exactly, or configure the IdP to always send lowercase email.
Prevention: Normalize email addresses in your IdP to lowercase before they reach Immich.
Issue: Restore on another machine not working (GitHub #27684)
Cause: Media files exist on the old machine but not the new one, or file paths differ.
Solution:
- Copy media files to the new machine at the same absolute path
- If paths differ, run
change-media-locationafter restoring the database - Verify with
schema-check
Prevention: Document exact media paths. Keep media and database backups synchronized.
Issue: Assets cannot be added to album (GitHub #27545)
Cause: Attempting to add partner-owned photos to an album in certain web UI flows.
Solution: Use the asset owner's account to add assets. Album membership for partner assets has known limitations.
Prevention: Design album workflows around asset ownership boundaries.
Issue: Video thumbnails not generating
Cause: FFmpeg unavailable in the container, or video format is unsupported with current transcode settings.
Solution:
# Verify FFmpeg is available
docker exec immich_server ffmpeg -version
# Check microservices logs for errors
docker logs immich_server 2>&1 | grep -i "ffmpeg\|transcode\|thumbnail"
# Re-queue thumbnail generation:
# Administration → Jobs → Thumbnail Generation → Run All
Prevention: Ensure the immich-server image is not modified (FFmpeg is bundled).
Issue: High memory usage in ML container
Cause: Multiple models loaded simultaneously, or MACHINE_LEARNING_WORKERS > 1 with large models.
Solution:
- Reduce
MACHINE_LEARNING_MODEL_TTLto evict models sooner - Set
MACHINE_LEARNING_WORKERS=1 - Disable unused ML features in SystemConfig
Prevention: Size ML container memory to hold: (worker count) × (sum of enabled model sizes).
Issue: Database backup failing
Cause: Insufficient disk space in the backup destination, or pg_dumpall timeout.
Solution:
# Check backup directory space
docker exec immich_server df -h /usr/src/app/upload/backups
# Manual backup with compression
docker exec immich_postgres pg_dumpall -U postgres | gzip > backup.sql.gz
Prevention: Monitor disk space. The default 14-backup retention helps but large databases need explicit space planning.
Issue: Port 2283 unreachable after deployment
Cause: Firewall rules, or container failed to start due to missing env vars or Redis/DB connection failure.
Solution:
# Check container status
docker compose ps
# Check server startup logs
docker logs immich_server --tail 100
Prevention: Always verify all required env vars are set before starting. The server logs a clear error for missing database or Redis connections.
Issue: Storage template migration hanging
Cause: Very large library or I/O-bound storage causing slow file moves.
Solution: The migration runs as a background job. Monitor progress in Administration → Jobs → Migration. Do not restart the server mid-migration.
Prevention: Run storage template migration during low-traffic periods. Ensure the migration queue has sufficient concurrency.
Issue: Shared link returns 404 after domain change
Cause: server.externalDomain was not updated to match the new domain.
Solution: Update server.externalDomain in Administration → Settings → Server. Existing share links will use the new domain for future requests.
Prevention: Configure externalDomain from day one. Document domain dependency for any server moves.
Issue: Duplicate detection not finding obvious duplicates
Cause: machineLearning.duplicateDetection.maxDistance threshold may be too low, or smart search embeddings are not yet indexed for all assets.
Solution:
- Increase
maxDistanceslightly (e.g., from0.01to0.02) - Ensure smart search jobs have completed for all assets
- Check for stuck
smartSearchqueue jobs in Administration → Jobs
Prevention: Run a full smart search job after bulk imports before relying on duplicate detection.