docs: Immich docker→NixOS module migration design
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016e2PKH5yN31h6JgHWCQb32
This commit is contained in:
@@ -0,0 +1,119 @@
|
|||||||
|
# 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 <server> 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 <db> 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.
|
||||||
Reference in New Issue
Block a user