# Immich: Docker → NixOS module migration **Date:** 2026-08-05 **Machine:** jupiter (home server, Intel iGPU) **Status:** Design approved, pending implementation plan ## Goal Replace the existing docker-compose Immich deployment on jupiter with the native `services.immich` NixOS module, wrapped in the repo's standard `my.profiles.*` pattern. Preserve all existing data (albums, faces, shared links, metadata) and photo/video library. ## Decisions | Topic | Decision | |-------|----------| | Approach | Native `services.immich` (nixpkgs), not `oci-containers` | | Version target | Stable nixpkgs ships **2.7.5**, unstable **3.0.3**. Pin target ≥ running docker version (forward-migration only) | | Media location | Default local path `/var/lib/immich`. NAS deferred to a future read-only external library | | Database | Migrate via dump/restore — keep everything | | HW acceleration | Video transcoding only (VAAPI/QSV via existing Intel graphics stack). ML on CPU | | Access | LAN + VPN only: open port 2283, register on homepage dashboard. No reverse proxy/TLS | ### Deliberately deferred (YAGNI) - OpenVINO ML acceleration - NAS-backed external library - Reverse proxy / TLS / public hostname ## Part 1 — The module New file `modules/environments/immich/default.nix` following the profile pattern; add `./environments/immich` to `modules/environments/default.nix`; enable `my.profiles.immich.enable = true` in `machines/jupiter/environments.nix`. ```nix { config, lib, pkgs, ... }: let cfg = config.my.profiles.immich; hostName = config.networking.hostName; port = 2283; in { options.my.profiles.immich.enable = lib.mkEnableOption "Immich photo server"; config = lib.mkIf cfg.enable { services.immich = { enable = true; # package = pkgs.unstable.immich; # only if docker :release is > 2.7.5 host = "0.0.0.0"; inherit port; openFirewall = true; mediaLocation = "/var/lib/immich"; machine-learning.enable = true; accelerationDevices = [ "/dev/dri/renderD128" ]; settings.server.externalDomain = "http://${hostName}:${toString port}"; }; # native module does not add GPU groups; needed for VAAPI/QSV transcoding users.users.immich.extraGroups = [ "video" "render" ]; my.homepage.services = [{ group = "Media"; name = "Immich"; description = "Photo & video server"; href = "http://${hostName}:${toString port}"; icon = "immich.png"; }]; }; } ``` **Provided for free by the native module:** local PostgreSQL with the required vector extension over a **unix socket + peer auth** (so no DB password / sops secret needed), Redis, `immich-server` and `immich-machine-learning` systemd units, the `immich` system user, and `mediaLocation` created via tmpfiles. **Transcoding is two parts:** (a) NixOS exposes the GPU device + `video`/`render` groups (above); (b) the hwaccel backend (QSV/VAAPI) is chosen in Immich's **admin → video transcoding** settings after cutover — a UI toggle, not Nix. ## Part 2 — Migration runbook (on jupiter) ### Pre-flight (hard blocker) 1. Get running docker Immich version (`docker exec immich --version` or web UI footer). 2. Compare to target (stable 2.7.5 / unstable 3.0.3). Target **must be ≥ running**. - running ≤ 2.7.5 → stable module as-is - 2.7.6–3.0.3 → set `package = pkgs.unstable.immich` - `> 3.0.3` → bump nixpkgs first; **stop and re-plan** 3. Record docker `UPLOAD_LOCATION` and DB container name/credentials. ### Backup (before touching anything) 4. `docker compose down` (DB may stay up for the dump). 5. Dump DB: `docker exec -t pg_dumpall --clean --if-exists --username=postgres > immich-db.sql` 6. Verify upload folder intact; note size (no copy yet). ### Cutover 7. Add the module to jupiter's `environments.nix` (leave `database.createDB` default). 8. `sudo nixos-rebuild switch --flake '.#jupiter'` → creates user, empty DB + role, `mediaLocation`. Then `systemctl stop immich-server immich-machine-learning`. 9. Restore the DB into the NixOS Postgres (drop the freshly-created empty `immich` DB, load `immich-db.sql`) per Immich's restore docs. 10. Move media into `/var/lib/immich` (subfolders `library/`, `upload/`, `thumbs/`, `encoded-video/`, `profile/`); `chown -R immich:immich /var/lib/immich`. 11. `systemctl start immich-server`; it runs schema migrations forward. Watch `journalctl -u immich-server -f`. ### Verify 12. UI at `http://jupiter:2283` loads; log in; spot-check albums, faces, a shared link, and that thumbnails/originals actually load. 13. Homepage tile works. 14. Enable QSV/VAAPI in admin settings; transcode one video; confirm `journalctl` shows the hw path, not a CPU fallback error. ### Rollback Before deleting any docker data: `systemctl stop immich-*`, disable the profile, `nixos-rebuild switch`, `docker compose up -d`. Original docker DB + upload folder remain untouched until explicitly removed after a few days of confidence. ## Known risk Step 9 crosses the **pgvecto.rs → VectorChord** vector-extension boundary if the docker version predates VectorChord. Follow Immich's official "migrate vector database" guidance during restore. The exact case is known only after the pre-flight version check.