Skip to content
STACK IT FAST

Immich

Curated OSSHybridPhoto & Video Management Platform6-20 people

Audited from github.com/immich-app/immich

Immich is a self-hosted photo and video management platform with a NestJS/PostgreSQL/Redis server, a SvelteKit web app, Flutter mobile apps, and a separate Python FastAPI service that runs machine-learning features such as face recognition and smart search.

Language
TypeScript
Database
PostgreSQL
Hosting
Docker
License
AGPL-3.0
Running for
3 years
Team
6-20 people

Why this architecture

Machine-learning inference is split into its own FastAPI/Python service with pluggable CPU/GPU backends so heavy models stay off the request-serving NestJS process, while BullMQ and Redis move thumbnailing and job dispatch out of the request/response path.

Tech stack by layer

11 technologies · audited Sep 17, 2026
Frontend & UI
  • SvelteKitBuilds the web gallery UI as a static-adapter SvelteKit app served alongside the server.
  • ViteDev server and bundler for the web app.
  • MapLibre GLRenders the map view for geotagged photos in the web app.
  • FlutterCross-platform Dart client for iOS and Android, with its own local sync and camera-backup logic.
Backend & APIs
  • NestJSStructures the main API server (auth, assets, albums, jobs, websockets) into modules and controllers.
  • KyselyTyped SQL query builder over Postgres, paired with a custom migration CLI (@immich/sql-tools) instead of a full ORM.
  • BullMQRuns background jobs (thumbnail generation, ML triggers, library scans) as a NestJS module backed by Redis.
  • FastAPIServes the separate machine-learning service that runs face recognition, CLIP search, and OCR models.
  • ExtismWebAssembly plugin runtime the server loads compiled plugins through, via @extism/extism.
Data & Persistence
  • PostgreSQLPrimary datastore for assets, users, and metadata, reached through Kysely with the postgres.js and pg drivers.
  • RedisBacks the BullMQ job queue and the Socket.IO Redis adapter for realtime events, via ioredis.
  • Drift (SQLite)Local on-device database for the mobile app, used for offline-capable asset and sync state.
Infrastructure & Deploy
  • DockerMulti-stage server image built from immich-app base images; the machine-learning service has its own Dockerfile with CPU/CUDA/OpenVINO/ROCm variants.
  • OpenTelemetryInstruments HTTP, Postgres, and Redis calls in the server, exported to Prometheus.
  • misePins the toolchain used to compile server plugins to WebAssembly inside the Docker build.

Immich architecture diagram

Open SVG
Immich architecture diagramWeb app → API server (REST · WebSocket); Mobile app → API server (backup · sync); API server → PostgreSQL (Kysely); API server → Redis (enqueue); Redis → Job workers (jobs); Job workers → Machine learning (inference); Job workers → Library storage (thumbnails); API server → Plugins (Extism)CLIENTSSERVICESWORKERS & JOBSDATA & STORAGEWeb appSvelteKit · MapLibreMobile appFlutter · Drift (SQLite)API serverNestJS · KyselyMachine learningFastAPI · faces · CLIP · OCRPluginsWebAssembly · ExtismJob workersBullMQPostgreSQLassets · metadataRedisqueues · Socket.IOLibrary storagephotos · videosREST · WebSocketbackup · syncjobsinferencethumbnailsExtismKyselyenqueue
How the main components of Immich connect, drawn from the audited repository.
Diagram as text
  • Web app (SvelteKit · MapLibre) → API server (NestJS · Kysely): REST · WebSocket
  • Mobile app (Flutter · Drift (SQLite)) → API server (NestJS · Kysely): backup · sync
  • API server (NestJS · Kysely) → PostgreSQL (assets · metadata): Kysely
  • API server (NestJS · Kysely) → Redis (queues · Socket.IO): enqueue
  • Redis (queues · Socket.IO) → Job workers (BullMQ): jobs
  • Job workers (BullMQ) → Machine learning (FastAPI · faces · CLIP · OCR): inference
  • Job workers (BullMQ) → Library storage (photos · videos): thumbnails
  • API server (NestJS · Kysely) → Plugins (WebAssembly · Extism): Extism

Key architectural decisions

6 decisions
  1. 01

    Machine learning runs as a separate FastAPI/Python service, not inside the NestJS API

    machine-learning/pyproject.toml defines a FastAPI app with onnxruntime and CPU/CUDA/OpenVINO/ROCm/RKNN optional dependency groups; machine-learning/Dockerfile builds it as its own image, so GPU-heavy inference is isolated from the request-serving server process.

  2. 02

    The API layer uses Kysely with a custom migration CLI instead of a traditional ORM

    server/package.json depends on kysely, kysely-postgres-js, and the workspace tool @immich/sql-tools, with migrations:generate/migrations:run scripts driving typed SQL migrations against Postgres rather than an ORM like TypeORM or Prisma.

  3. 03

    Background work is offloaded to Redis-backed BullMQ queues

    server/package.json pulls in @nestjs/bullmq, bullmq, and ioredis so jobs such as thumbnail generation, library scanning, and ML job dispatch run asynchronously outside the request/response cycle.

  4. 04

    Server plugins are compiled to WebAssembly and loaded through Extism

    packages/plugin-sdk and packages/plugin-core build plugin code with extism-js into a plugin.wasm artifact; server/package.json depends on @extism/extism to load and run those plugins inside the Node server, sandboxing third-party plugin code from the host process.

  5. 05

    The mobile app keeps its own local SQLite database for offline-capable browsing

    mobile/pubspec.yaml depends on drift, drift_sqlite_async, and sqlite_async, giving the Flutter app a local relational cache of assets and sync state instead of relying on the server being reachable for every screen.

  6. 06

    One Dockerfile builds the SDK, server, web, CLI, and plugins as separate stages

    server/Dockerfile chains FROM builder AS sdk -> plugin-sdk -> server / web / cli / plugins stages from a shared immich-app base image, then copies only the built output of each into the final production image.

How Immich is built

How Immich is structured

The repository is a pnpm workspace (pnpm-workspace.yaml) with server/ (NestJS API), web/ (SvelteKit app), mobile/ (Flutter app), and machine-learning/ (Python FastAPI service) as the four main deployables, plus shared packages under packages/: packages/sdk (generated TypeScript API client), packages/cli (@immich/cli), packages/plugin-sdk and packages/plugin-core (the server plugin system), and packages/e2e-auth-server for end-to-end tests. server/Dockerfile builds all of the TypeScript deployables (SDK, plugin SDK, server, web, CLI, plugins) as separate stages from a shared ghcr.io/immich-app/base-server-* image, while machine-learning/Dockerfile is a fully separate Python image. docker/ holds several compose variants (docker-compose.yml, docker-compose.dev.yml, docker-compose.prod.yml, docker-compose.rootless.yml) for self-hosting.

Frontend

web/package.json is a SvelteKit app (@sveltejs/kit, @sveltejs/adapter-static, @sveltejs/vite-plugin-svelte) built with Vite. It renders the photo grid, timeline, and album views, integrates maplibre-gl and svelte-maplibre for the map view, @photo-sphere-viewer/* packages for 360° photo/video playback, and hls.js/hls-video-element for streamed video playback. Styling uses tailwindcss via tailwind-merge/tailwind-variants, and the shared @immich/ui component library. mobile/pubspec.yaml defines the Flutter client separately: hooks_riverpod for state management, auto_route for navigation, drift/sqlite_async for a local database, background_downloader for backup uploads, and maplibre_gl (Flutter binding) for the mobile map view. Both clients consume the generated @immich/sdk package rather than hand-writing API calls.

Backend & APIs

server/package.json is a NestJS application using @nestjs/platform-express, @nestjs/websockets with @nestjs/platform-socket.io, and @nestjs/swagger for the OpenAPI spec that packages/sdk is generated from. Data access goes through kysely and kysely-postgres-js/pg rather than a full ORM, with a companion CLI package @immich/sql-tools driving typed migration generation (migrations:generate, migrations:run scripts). Background work — thumbnail generation, library scans, ML job dispatch — runs through @nestjs/bullmq and bullmq, queued in Redis via ioredis; the same Redis instance backs @socket.io/redis-adapter for realtime events across multiple server instances. A separate plugin system lets third-party code extend the server: packages/plugin-sdk and packages/plugin-core compile plugin code to WebAssembly with extism-js, and the server loads .wasm plugins through the @extism/extism dependency rather than running arbitrary Node code in-process.

Data & persistence

PostgreSQL is the system of record, reached from the server through Kysely with the postgres and pg drivers. Redis, via ioredis, is the second core datastore: it is the BullMQ broker for background jobs and the pub/sub backend for @socket.io/redis-adapter. On mobile, drift and drift_sqlite_async give the Flutter app its own local SQLite database for offline browsing and sync-state tracking, independent of the server's PostgreSQL instance.

Build, test & deploy

CI lives in .github/workflows, with test.yml, static_analysis.yml, docker.yml, build-mobile.yml, cli.yml, sdk.yml, and check-openapi.yml among the workflows the facts gather resolved. The server is checked with ESLint, Prettier, tsc --noEmit, and tested with Vitest (test:cov for coverage, test:medium for a slower suite using testcontainers); the web app adds svelte-check for Svelte-specific type checking. The machine-learning service is linted with ruff and type-checked with mypy, and tested with pytest. Docker builds are multi-stage: server/Dockerfile produces the combined server+web+cli+plugins image, while machine-learning/Dockerfile produces separate CPU and accelerator-specific images from the onnxruntime optional dependency groups (cpu, cuda, openvino, rocm, rknn, armnn) declared in machine-learning/pyproject.toml.

Self-hosting notes

docker/docker-compose.yml (with docker-compose.dev.yml, docker-compose.prod.yml, and docker-compose.rootless.yml variants) and docker/example.env are the documented self-hosting path, bringing up the server, machine-learning, PostgreSQL, and Redis containers together. The production server image (built in server/Dockerfile's final stage from ghcr.io/immich-app/base-server-prod) exposes port 2283, mounts a /data volume for the asset library, and starts under tini with an immich-healthcheck HEALTHCHECK. GPU-accelerated machine learning requires selecting the matching machine-learning/Dockerfile build target and dependency group (cuda, openvino, rocm, rknn, armnn) rather than a single universal image.

What to copy (and what not to)

Splitting GPU/CPU-heavy ML inference into its own FastAPI service with build-time-selectable accelerator backends (cuda, openvino, rocm, rknn, armnn as separate pyproject.toml optional-dependency groups) is a pattern worth copying for any product bundling inference alongside a web API — it lets operators pick a matching image for their hardware without the main API depending on any particular accelerator toolchain. Compiling third-party server plugins to WebAssembly via Extism, instead of loading them as native Node modules, is a good default when a project wants a real security boundary around plugin code; a project without an actual third-party plugin ecosystem would not need that complexity. Using Kysely with a custom migration CLI instead of a full ORM is a defensible choice for a team that wants typed SQL without ORM-generated queries, but it does mean maintaining @immich/sql-tools as an extra in-house tool rather than relying on a community migration system.

Sources & repo audit

Audited
Sep 17, 2026
Commit
e8c6218
License
AGPL-3.0

Independent analysis of repository at github.com/immich-app/immich. Spotted an inaccuracy? Use the claim form to request a correction.

Show this stack on your README or website
Tech stack of Immich
[![Tech stack of Immich on STACK IT FAST](https://stackitfast.com/badge/immich.svg)](https://stackitfast.com/project/immich)

Paste it into your README. It shows your current stack and links to this page.

Use this stack

Scaffold it with your agent

Paste this prompt into Claude Code, Cursor, Windsurf or AGY to start a project with Immich's architecture.

  1. 1Copy the promptThe full markdown spec, with every layer and decision.
  2. 2Open your AI toolClaude Code, Cursor, Windsurf or Copilot, in a new repo.
  3. 3Paste and scaffoldUse it as the first instruction; review before you ship.
use-this-stack.md · 60 lines · 7.7 KB
# MISSION: Scaffold "Immich" Production Architecture
You are an expert Senior Staff Software Architect and Full-Stack Engineer. Your mission is to scaffold and implement a production-grade, highly reliable, and modular codebase following the proven architecture of **Immich**.
---
## 1. PROJECT SPECIFICATIONS & BENCHMARK
- **Reference Architecture**: Immich
- **What It Does**: Immich is a self-hosted photo and video management platform with a NestJS/PostgreSQL/Redis server, a SvelteKit web app, Flutter mobile apps, and a separate Python FastAPI service that runs machine-learning features such as face recognition and smart search.
- **Domain & Category**: Photo & Video Management Platform
- **Production Scale**: 6-20 people
- **Development Mode**: HYBRID
- **Architectural Rationale**: Machine-learning inference is split into its own FastAPI/Python service with pluggable CPU/GPU backends so heavy models stay off the request-serving NestJS process, while BullMQ and Redis move thumbnailing and job dispatch out of the request/response path.
- **Live Website Reference**: https://immich.app
- **Source Repository**: https://github.com/immich-app/immich
---
## 2. PRODUCTION TECH STACK
- **Full Stack Array**: SvelteKit, TypeScript, NestJS, Flutter, Dart, Python, FastAPI, PostgreSQL, Redis, Kysely, Docker
- **Primary Language(s)**: TypeScript, Dart, Svelte, Python
- **License of the reference repo**: AGPL-3.0
- **Frontend**: SvelteKit — Builds the web gallery UI as a static-adapter SvelteKit app served alongside the server.; Vite — Dev server and bundler for the web app.; MapLibre GL — Renders the map view for geotagged photos in the web app.; Flutter — Cross-platform Dart client for iOS and Android, with its own local sync and camera-backup logic.
- **Backend & APIs**: NestJS — Structures the main API server (auth, assets, albums, jobs, websockets) into modules and controllers.; Kysely — Typed SQL query builder over Postgres, paired with a custom migration CLI (@immich/sql-tools) instead of a full ORM.; BullMQ — Runs background jobs (thumbnail generation, ML triggers, library scans) as a NestJS module backed by Redis.; FastAPI — Serves the separate machine-learning service that runs face recognition, CLIP search, and OCR models.; Extism — WebAssembly plugin runtime the server loads compiled plugins through, via @extism/extism.
- **Data & persistence**: PostgreSQL — Primary datastore for assets, users, and metadata, reached through Kysely with the postgres.js and pg drivers.; Redis — Backs the BullMQ job queue and the Socket.IO Redis adapter for realtime events, via ioredis.; Drift (SQLite) — Local on-device database for the mobile app, used for offline-capable asset and sync state.
- **Infrastructure & deploy**: Docker — Multi-stage server image built from immich-app base images; the machine-learning service has its own Dockerfile with CPU/CUDA/OpenVINO/ROCm variants.; OpenTelemetry — Instruments HTTP, Postgres, and Redis calls in the server, exported to Prometheus.; mise — Pins the toolchain used to compile server plugins to WebAssembly inside the Docker build.
---
## 3. KEY ARCHITECTURAL DECISIONS (audited from https://github.com/immich-app/immich @ e8c6218)
1. **Machine learning runs as a separate FastAPI/Python service, not inside the NestJS API**: machine-learning/pyproject.toml defines a FastAPI app with onnxruntime and CPU/CUDA/OpenVINO/ROCm/RKNN optional dependency groups; machine-learning/Dockerfile builds it as its own image, so GPU-heavy inference is isolated from the request-serving server process.
2. **The API layer uses Kysely with a custom migration CLI instead of a traditional ORM**: server/package.json depends on kysely, kysely-postgres-js, and the workspace tool @immich/sql-tools, with migrations:generate/migrations:run scripts driving typed SQL migrations against Postgres rather than an ORM like TypeORM or Prisma.
3. **Background work is offloaded to Redis-backed BullMQ queues**: server/package.json pulls in @nestjs/bullmq, bullmq, and ioredis so jobs such as thumbnail generation, library scanning, and ML job dispatch run asynchronously outside the request/response cycle.
4. **Server plugins are compiled to WebAssembly and loaded through Extism**: packages/plugin-sdk and packages/plugin-core build plugin code with extism-js into a plugin.wasm artifact; server/package.json depends on @extism/extism to load and run those plugins inside the Node server, sandboxing third-party plugin code from the host process.
5. **The mobile app keeps its own local SQLite database for offline-capable browsing**: mobile/pubspec.yaml depends on drift, drift_sqlite_async, and sqlite_async, giving the Flutter app a local relational cache of assets and sync state instead of relying on the server being reachable for every screen.
6. **One Dockerfile builds the SDK, server, web, CLI, and plugins as separate stages**: server/Dockerfile chains FROM builder AS sdk -> plugin-sdk -> server / web / cli / plugins stages from a shared immich-app base image, then copies only the built output of each into the final production image.
---
## 4. NON-NEGOTIABLE ARCHITECTURAL GUARDRAILS
1. **Monorepo & Modular Separation**:
   - Structure as a Turborepo monorepo with strict package boundaries:
     - `apps/web`: Application UI, routing, layouts, and server endpoints.
     - `packages/ui`: Shared design tokens, CSS variables, and Radix UI primitive components.
     - `packages/db`: Database schemas, client singleton, declarative migrations, and seed scripts.
     - `packages/config`: Shared TypeScript, ESLint, and build configurations.
2. **Strict Type Safety & Zero `any` Policy**:
   - Enable `strict: true`, `noImplicitAny: true`, and `strictNullChecks: true`.
   - Validate ALL external inputs, API request bodies, and query parameters with **Zod** schemas before execution.
3. **Frontend & Rendering Guidelines**:
   - Isolate interactive UI state to leaf components. Keep core pages lightweight and performant.
4. **Design System & Aesthetics**:
   - Keep every color, radius, shadow and font in a single token file (CSS variables) and consume tokens everywhere; never hardcode hex values in components.
   - Prefer crisp 1px borders and one subtle shadow scale over blurry default shadows. Pair one sans-serif for body/headings with one monospace for tags, badges, metrics, and code.
5. **Data Layer & Reliability**:
   - Write declarative schema definitions with foreign keys, composite indexes on queried filters, and automated timestamp triggers.
   - Use connection pooling and prepared statements for serverless database execution.
---
## 5. STEP-BY-STEP SCAFFOLDING ROADMAP
- **Phase 1: Workspace & Root Config**: Initialize package manager, monorepo configuration (`turbo.json`, `tsconfig.base.json`, `package.json`).
- **Phase 2: Database Schema & Client**: Set up the data layer (PostgreSQL, Redis, Drift (SQLite)): client, connection pool, models, and migration scripts.
- **Phase 3: Design Tokens & UI Primitives**: Build accessible `Button`, `Input`, `Card`, `Badge`, and layout wrappers inside `packages/ui`.
- **Phase 4: Core Application Routes & Handlers**: Implement primary authentication, user session handling, and application routes.
- **Phase 5: Quality Assurance & Build Verification**: Run `tsc --noEmit`, ESLint, Prettier, and smoke test suites to ensure zero compilation or runtime errors.
---
## 6. EXECUTION INSTRUCTIONS
1. Review all specifications, architectural guardrails, and stack choices above.
2. Present the full monorepo directory tree structure.
3. Systematically generate the complete, production-ready codebase according to the 5-phase roadmap above — starting with the root workspace setup, followed by the database schema, UI design system package, and full-stack application routes until the repository is fully scaffolded and ready to run.
Scaffolded something with this prompt?
Would you pick this stack for a photo & video management platform project?

Frequently asked about Immich

What is Immich built with?

The server is NestJS and TypeScript on PostgreSQL (via Kysely) and Redis (via BullMQ); the web app is SvelteKit; the mobile app is Flutter/Dart; and machine learning (face recognition, smart search) runs in a separate Python FastAPI service.

Does Immich need a backend?

Yes. server/package.json is the whole application backend: it serves the API, stores data in PostgreSQL, queues jobs in Redis via BullMQ, and dispatches machine-learning requests to the separate FastAPI service.

Can I self-host Immich?

Yes, Immich is designed to be self-hosted. The repository ships docker/docker-compose.yml plus dev, prod, and rootless compose variants, and server/Dockerfile and machine-learning/Dockerfile build the images those compose files run.

How does Immich run machine learning features like face recognition and smart search?

A dedicated FastAPI service (machine-learning/pyproject.toml) runs onnxruntime models, with optional CPU, CUDA, OpenVINO, ROCm, and RKNN dependency groups selecting the inference backend; the NestJS server dispatches jobs to it rather than running models in-process.

Does Immich have a mobile app?

Yes, mobile/pubspec.yaml defines a Flutter app using Riverpod for state and Drift/sqlite_async for a local on-device database, so the app can browse and queue backups even when the server connection is intermittent.

One email a month: new deep dives and stack trends

New source-audited architectures, head-to-head comparisons and the monthly stack report. No spam, unsubscribe anytime.

use-this-stack.md