Immich (photos)

Immich reads library/pictures, makes thumbnails and previews (from HEIC and raw too), recognises faces and places, and searches by what is in a picture. SoundStorm provisions it with nobody logging in, gives every member an Immich account that sees only their own folder, and never lets Immich touch a photo file.

Why Immich

Photos fail the rule for what SoundStorm owns: iPhones save HEIC, which Go's standard library cannot decode, clips need transcoding, and a photo library is a scanning job. Immich was chosen over lighter options such as Photoview (closer to "the folder is the interface") for search by what is in a picture - the feature people compare against Google Photos - at the price of four containers (server, machine learning, Postgres with vector search, Valkey), about 5 GB of images and 6-8 GB of RAM.

Pinned to a major version

Researched, not assumed: Immich 2.0 (October 2025) promised semver, and 3.0 (July 2026) then broke API endpoints "that affect only third-party tools" - which is what SoundStorm is. So every Immich image is pinned to its major version, v3, never latest, and moving to v4 is a deliberate change with an adapter update behind it.

Provisioning

Every step was checked against a live 3.2.2:

  1. /api/auth/admin-sign-up with soundstorm@soundstorm.invalid - RFC 2606's never-existing domain. Immich validates the address's shape and sends nothing to it.
  2. Log in, then create an API key with permissions: ["all"]. The session token expires and the key does not, so the key is kept and the password is not.
  3. Create an external library on /pictures, mounted read-only. Immich's docs describe creating one only through its admin screens; the endpoint behind them is in the API.
  4. Switch folder watching on by reading /api/system-config, changing library.watch.enabled, and sending the whole document back - a test holds that this changes nothing else.
An external library rather than Immich's own upload storage, deliberately: a photo is a file in pictures/ however it arrived, and Immich can never move, rename or delete one. Its thumbnails and previews live in a named volume, regenerable from the photos. Its database must be a named volume too: Immich's Postgres must not sit on a network share or an NTFS drive, and a named volume lives on Docker's own Linux disk on every host.

An account per member

Each member gets an Immich account (/api/admin/users, made the first time they look) whose one external library reads only pictures/Personal/<name>/. The owner keeps the administrator's account, whose library is the whole folder. The adapter acts as the person asking (immich.Config.ActAs): every request carries their own key - never a fallback to the administrator's. Filtering one shared account was rejected because Immich groups faces across every photo it can see; separate accounts keep People, Places and search to each person's own photos. Verified first: overlapping libraries are allowed, and a member asking for another's photo by id gets a 400. Removing a member deletes their account with force and keeps their folder. See Photos.

The calls SoundStorm makes

CallUsed for
/api/search/metadataBrowsing newest first, filename search, a person's or town's photos, On this day (one search per earlier year)
/api/search/smartSearch by what is in a picture
/api/assets/{id}/thumbnailThumbnails (WebP) and previews (JPEG, art id <id>@preview)
/api/assets/{id}/originalThe original, as a download
/api/assets/{id}/video/playbackClips; honors Range, so seeking works through the proxy
/api/people, PUT /api/people/{id}People, faces, naming someone found but unnamed
/api/search/citiesPlaces
POST /api/libraries/{id}/scan"Look now" - every library, members' included

Quirks

Uploads appear about four seconds after landing, through folder watching plus SoundStorm's rescan. Immich's Postgres uses a fixed default password reachable only on the compose network; generating one would require telling a fresh install from an existing database, whose password cannot change under it. That is recorded as a decision left open.