The stack

Five containers:

  • immich-server — TypeScript / Nest.js API + web UI
  • immich-machine-learning — Python worker running CLIP + face detection + face recognition models
  • redis — job queue and short-lived caches
  • postgres + pgvector — metadata, faces, embeddings (the embeddings rely on pgvector — see that tutorial)
  • (optional) external storage backend if not using a local volume

docker compose

Grab the official template and env file. The current canonical version is published per release on the Immich docs; the structure below is correct as of v1.x in early 2026:

# docker-compose.yml (trimmed)
services:
  immich-server:
    image: ghcr.io/immich-app/immich-server:release
    extends:
      file: hwaccel.transcoding.yml
      service: cpu          # or "nvenc", "qsv", "vaapi" if you have a GPU
    volumes:
      - ${UPLOAD_LOCATION}:/usr/src/app/upload
      - /etc/localtime:/etc/localtime:ro
    env_file: [ .env ]
    ports:
      - "127.0.0.1:2283:2283"
    depends_on: [ redis, database ]
    restart: always

  immich-machine-learning:
    image: ghcr.io/immich-app/immich-machine-learning:release
    volumes:
      - model-cache:/cache
    env_file: [ .env ]
    restart: always

  redis:
    image: docker.io/redis:6.2-alpine
    healthcheck: { test: [ "CMD-SHELL", "redis-cli ping || exit 1" ] }
    restart: always

  database:
    image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_USER:     ${DB_USERNAME}
      POSTGRES_DB:       ${DB_DATABASE_NAME}
      POSTGRES_INITDB_ARGS: '--data-checksums'
    volumes:
      - ${DB_DATA_LOCATION}:/var/lib/postgresql/data
    healthcheck:
      test: [ "CMD-SHELL", "pg_isready -U ${DB_USERNAME} || exit 1" ]
    restart: always

volumes:
  model-cache:

Critical environment variables in .env:

UPLOAD_LOCATION=/srv/immich/library
DB_DATA_LOCATION=/srv/immich/postgres
TZ=America/Toronto

DB_PASSWORD=<long-random>
DB_USERNAME=postgres
DB_DATABASE_NAME=immich

# Optional: enable CLIP downloads through a proxy / pin model version
# IMMICH_MACHINE_LEARNING_URL=http://immich-machine-learning:3003

docker compose up -d and watch docker compose logs -f immich-server — first start runs DB migrations.

Don't bind-mount over an existing Postgres volume

Immich's Postgres image is a forked build with the vectorchord/pgvector extension. Pointing it at a vanilla Postgres data directory or vice-versa corrupts the database. The DB_DATA_LOCATION directory must be Immich-managed only.

Reverse proxy

Immich uploads can be very large (4K video). The proxy needs generous body-size limits:

# Caddy
photos.example.com {
    reverse_proxy 127.0.0.1:2283

    request_body {
        max_size 50000MB
    }
}
# nginx
client_max_body_size 50000M;
proxy_read_timeout 600s;
proxy_request_buffering off;     # stream uploads instead of buffering

proxy_request_buffering off matters — otherwise nginx tries to spool every multi-GB upload to disk first.

Mobile setup

Install the Immich app on iOS (App Store) or Android (Play Store / F-Droid / direct APK). On first launch:

  1. Server URL: https://photos.example.com. The app must hit that exact URL with TLS — no plain HTTP from real iOS/Android.
  2. Log in with the account created at the web UI signup flow.
  3. Settings → Backup → pick album(s) to back up. "Camera Roll" or specific named albums.
  4. Settings → Backup → turn on "Foreground" and "Background" backup. Background backup on iOS is best-effort by the OS — expect periodic catch-up rather than real-time uploads.

ML model selection

By default Immich uses the smallest CLIP model (ViT-B-32) for semantic search. Web UI → Administration → Settings → Machine Learning lets you swap to larger models:

  • ViT-B-32 — fastest, ~150 MB, OK quality
  • ViT-L-14 — better quality, ~1.7 GB, 4–6× slower per image
  • SigLIP — current quality leader for many tasks, intermediate size

Switching models triggers a re-embedding job for the whole library. Plan for hours-to-days of background CPU on a large library; queue progress is visible at Administration → Jobs.

External libraries (no copy)

Immich can index files in place from a read-only mount, so an existing photo archive doesn't have to be re-uploaded. Mount the archive into the server container at any path (e.g. /mnt/old-photos), then web UI → Administration → External Libraries → add the path. Immich reads metadata, generates thumbnails into its own storage, and indexes the originals without touching them.

Backups

Three things to back up:

  • The library (UPLOAD_LOCATION) — originals and uploaded media.
  • The Postgres database — metadata, embeddings, face clusters. A nightly pg_dumpall through a sidecar or systemd job is enough.
  • The .env — without DB_PASSWORD, restored data is opaque.

For Postgres specifically, Immich's docs recommend a logical dump rather than file-level copies of the data directory, because vectorchord index files don't always survive a raw-file copy across container restarts:

docker exec -t immich-database \
    pg_dumpall -c -U postgres | gzip > immich-pg-$(date +%F).sql.gz

Both the library directory and the pg-dump fit comfortably inside a restic job (see restic + S3).

Performance notes

  • CPU: ML jobs are the dominant load. On a 4-core x86_64 box, ViT-B-32 indexes ~50–150 photos/minute; ViT-L-14 is roughly 4× slower.
  • RAM: 4 GB minimum, 8 GB comfortable for the ML worker. Larger CLIP models want more.
  • GPU: NVENC/QSV/VAAPI extends the compose file to use hardware acceleration for transcoding (the hwaccel.transcoding.yml reference above). CUDA on the ML worker is a separate opt-in: set image: ghcr.io/immich-app/immich-machine-learning:release-cuda and add deploy.resources.reservations.devices to expose the GPU.
  • Disk: thumbnails and ML embeddings add roughly 5–10% on top of original media size.

What it isn't (yet)

Immich is a backup-and-browse product, not full photo editing. Cropping, exposure, color, lens-correction — not in scope. Edit in Lightroom / Darktable / a native app, then upload. For an editor-first workflow that also self-hosts, look at PhotoPrism; for a "literally Google Photos clone, full stop" workflow, Immich is the closest thing in the open-source world.