Files
nix-config-ryan4yin/hosts/12kingdoms-youko/homelab-services/immich/README.md
T
Ryan Yin 6fa6e6626e refactor(youko): run homepage from the native NixOS module
services.homepage-dashboard runs the nixpkgs package with DynamicUser,
replacing the :latest container, its system user and the activation
script that rsynced the YAML into place. The YAML stays as is and is
linked into /etc/homepage-dashboard.

Assisted-by: Claude Code (claude-opus-5-5)
2026-11-01 11:42:57 -06:00

6.0 KiB

Immich (photo platform)

Design for the homelab photo platform on youko. Implementation lives in ./default.nix; this document is the agreed design and the rationale.

Goal

  • Browse and search the existing 36G phone gallery (/data/fileshare/mydata/DCIM) in place, with ML (faces, objects) and duplicate detection.
  • Keep the footprint reasonable next to the k3s microVMs on the same host.

Non-goals (for now)

  • Scheduled phone background backup (the mobile app is used for manual uploads).
  • Multi-user / family accounts and public share links.
  • Off-host/off-site copy (see Backup).

Decisions

  • App: Immich via the nixpkgs services.immich module — a native systemd service, not containers.
  • Database: reuse the host PostgreSQL 16 (services.postgresql) instead of a bundled DB container.
  • Cache: a single shared Valkey instance (services.redis.package = pkgs.valkey), reused by Immich and future services over loopback TCP — not a private per-service Redis, and not Dragonfly (its Lua handling breaks Immich's BullMQ job queue; see Risks).
  • Existing photos: exposed as a read-only Immich External Library, not imported/copied (no 36G duplication).
  • ML: enable it, running on CPU initially.

Architecture

  • services.immich (server) on youko; host = "127.0.0.1", port = 2283.
  • services.immich.database.enable = true and createDB = true: the module adds pgvector + vectorchord to services.postgresql, sets shared_preload_libraries = [ "vchord.so" ], and creates the immich database/user. Confirmed present in this flake's nixpkgs: PG 16.15, pgvector 0.8.6, vectorchord 1.1.1, immich 3.2.4.
  • services.immich.redis.enable = false with host = "127.0.0.1" and port = 6379: Immich uses the shared Valkey instance (../valkey.nix) over loopback TCP; the module then passes REDIS_HOSTNAME/REDIS_PORT.
  • services.immich.machine-learning.enable = true (CPU; accelerationDevices left unset — AMD iGPU ML is unreliable, revisit /dev/dri/renderD128 later).
  • Caddy vhost immich.writefor.fun -> http://127.0.0.1:2283. Unlike the other vhosts (private ecc-ca), this one is reached by the Immich mobile app, so it gets a publicly-trusted Let's Encrypt cert via security.acme (lego + Cloudflare DNS-01); see ../caddy.nix.
  • A homepage entry on the dashboard.

Storage

  • mediaLocation = "/data/apps/immich" (the encrypted HDD; ~3.5T free), created by tmpfiles as d /data/apps/immich 0750 immich immich. This holds the managed library and generated thumbnails/transcodes.
  • The Immich database lives in the Postgres data directory (/var/lib/postgresql/16), i.e. under @persistent.

Photo sources and permissions

  • The DCIM tree is 0700, owned by sftpgo (sftpgo:postgres-exporter). Grant the immich user read access with a recursive and default ACL (u:immich:rX + d:u:immich:rX) so existing and future files stay readable, without loosening the tree for everyone.
  • In the Immich admin UI, add an External Library pointing at the DCIM path and run a scan. Files stay read-only to Immich; albums/faces metadata is stored in the database.

Backup and disaster recovery

  • Database: already covered by the existing youko restic backup, which snapshots /btr_pool/@persistent (Postgres data lives there).
  • Media and existing photos: both live on the HDD already. A local HDD->HDD backup adds no protection, so none is added here. This is deferred to the planned cloud copy, which will cover the photo library.
  • Note for the cloud-copy work: a raw btrfs snapshot of a running Postgres is only crash-consistent; prefer a pg_dump into the cloud set for a clean restore. That set should include mediaLocation and the external-library source.
  • One-time disruption: adding the extensions and shared_preload_libraries restarts PostgreSQL (affects the playground DB only).

Monitoring

  • Enabled: IMMICH_API_METRICS_PORT=8081 + IMMICH_MICROSERVICES_METRICS_PORT=8082 (OTEL metrics, IMMICH_TELEMETRY_INCLUDE=all). VictoriaMetrics scrapes them on this host (monitoring/victoriametrics.nix); caddy does not proxy them.
  • Note: Immich has no bind-host option for these listeners, so they bind all interfaces and are LAN-reachable like node_exporter:9100. Add a firewall rule if that must be loopback-only.

Risks

  • Immich's job queue (BullMQ) needs a Redis that tolerates Lua scripts accessing dynamically-built keys; Dragonfly rejected these (script tried accessing undeclared key) and every upload returned 500. We therefore run Valkey (services.redis), Redis-compatible and the supported backend.

Resource budget

  • Immich server + ML worker + Valkey: roughly 2-4G RAM, alongside the k3s microVMs.

Rollout

Applied — immich runs on youko. The steps below are the procedure as deployed:

  1. ../valkey.nix: the shared Valkey instance.
  2. ./default.nix: the services.immich configuration (server, database, machine-learning, tmpfiles).
  3. ../caddy.nix: the immich.writefor.fun vhost.
  4. ../homepage/services.yaml: dashboard entry.
  5. Preview and deploy: just youko dry-activate, then just youko.
  6. Register the first user (becomes admin), disable open signup, add the external library, trigger a scan.

Validation

  • just fmt; just test passes; nix build the youko toplevel.
  • Postgres: \dx lists vectorchord/pgvector; SHOW shared_preload_libraries includes vchord.so; the immich DB exists.
  • Units: immich-server, immich-machine-learning, redis-shared, postgresql are active.
  • curl 127.0.0.1:2283 and https://immich.writefor.fun respond; login works; the external library scan indexes the DCIM photos and ML search returns results.

Future

  • iGPU ML (/dev/dri/renderD128) if it proves reliable.
  • immich-public-proxy / immich-kiosk / immichframe for sharing and dashboards.
  • Mobile apps and phone auto-backup.
  • pg_dump of the Immich DB into the cloud backup set.