# Immich — Administrator & Developer Guide

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: `sirv` for 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]
> `videoConversion` runs 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 (`videoConversion` queue) 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:

1. **Environment variables** — set at container start, control infrastructure connections and runtime behavior
2. **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_PASSWORD` from the default `postgres` before 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 `passwordLogin` and `oauth`, users (including admins) will be unable to log in. Use `immich-admin enable-password-login` to 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`:

```yaml
# 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`:**

```bash
# 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:

```yaml
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:

```bash
# 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:

```bash
# 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

```bash
# 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:

```bash
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:**

```nginx
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:

```bash
IMMICH_CONFIG_FILE=/etc/immich/config.json
```

```json
{
  "ffmpeg": {
    "crf": 23,
    "preset": "ultrafast",
    "targetVideoCodec": "H264"
  },
  "machineLearning": {
    "enabled": true,
    "clip": { "modelName": "ViT-B-32__openai" }
  }
}
```

> [!CAUTION]
> While `IMMICH_CONFIG_FILE` is 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.

```bash
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:**

```bash
# 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:**

```bash
docker exec immich_postgres pg_dumpall -U postgres > immich_backup_$(date +%Y%m%d).sql
```

**Restore from backup:**

```bash
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-location` if 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"` — see `docs.immich.app/errors#schema-drift`
- Error `"Invalid upgrade path"` — see `docs.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:

1. Mount the external path into the container
2. Create a Library in Administration → Libraries
3. Configure path(s) for the library
4. 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 `migration` queue.

### 5.7 Telemetry and Metrics

Immich exposes Prometheus metrics when telemetry is configured:

```bash
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:

```bash
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:

```sql
-- 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 operations
- `POSTGRES_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:

```bash
MACHINE_LEARNING_PRELOAD__CLIP=ViT-B-32__openai
MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION=buffalo_l
```

**Multi-worker ML** (if memory headroom exists):

```bash
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:

```bash
# 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:

```bash
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`, appropriate `rsize`/`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:

```bash
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 converts `snake_case` DB columns to `camelCase` TypeScript 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:**

```http
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:

```bash
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:

1. Add controller + decorators in `server/src/controllers/`
2. Run the SDK generation script
3. 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_TTL` controls eviction of idle models
- `MACHINE_LEARNING_MODEL_TTL_POLL_S` controls 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:

```bash
# 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:**

```bash
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:

```bash
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:

```bash
IMMICH_TELEMETRY_INCLUDE=all
IMMICH_API_METRICS_PORT=8081
IMMICH_MICROSERVICES_METRICS_PORT=8082
```

Add to Prometheus `scrape_configs`:

```yaml
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 optionally `name`, `picture`)

---

## 10. Troubleshooting

### Error: "Password login has been disabled"

**Cause:** `passwordLogin.enabled` was set to `false` and OAuth is not functioning.

**Solution:**
```bash
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:

```bash
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:

```bash
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:**

```bash
# 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:**

```bash
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:

```bash
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:**

```bash
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:**

1. Check job queue status in Administration → Jobs
2. Look for stuck `metadataExtraction` or `thumbnailGeneration` jobs
3. 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:**

1. Check reverse proxy `client_max_body_size` — must be large enough for video files
2. Verify `proxy_read_timeout` is set to at least 86400s to match Immich's 24-hour request timeout
3. 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:**

1. Copy media files to the new machine at the same absolute path
2. If paths differ, run `change-media-location` after restoring the database
3. 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:**

```bash
# 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:**

1. Reduce `MACHINE_LEARNING_MODEL_TTL` to evict models sooner
2. Set `MACHINE_LEARNING_WORKERS=1`
3. 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:**

```bash
# 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:**

```bash
# 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:**

1. Increase `maxDistance` slightly (e.g., from `0.01` to `0.02`)
2. Ensure smart search jobs have completed for all assets
3. Check for stuck `smartSearch` queue jobs in Administration → Jobs

**Prevention:** Run a full smart search job after bulk imports before relying on duplicate detection.

---

_Generated May 2026 from immich-app/immich at 6aadb7b. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
