1176132627
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016e2PKH5yN31h6JgHWCQb32
122 lines
5.4 KiB
Markdown
122 lines
5.4 KiB
Markdown
# 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 | **Resolved: docker runs 2.7.5 == stable nixpkgs 2.7.5.** Use the stable module as-is; no `package` override. Same-version restore, no forward schema migration |
|
||
| 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. **Resolved 2026-08-05: running version is 2.7.5, equal to stable nixpkgs.**
|
||
Use the stable module as-is (no `package` override). Kept for reference:
|
||
- running ≤ 2.7.5 → stable module as-is ← **this case**
|
||
- 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 — RESOLVED
|
||
|
||
The main risk was step 9 crossing the **pgvecto.rs → VectorChord** vector-extension
|
||
boundary. With source and target both at **2.7.5**, both use VectorChord — no
|
||
boundary crossing and no forward schema migration. The restore is a same-version
|
||
dump/load. Residual risk is limited to routine dump/restore mechanics
|
||
(roles, extension availability in the NixOS Postgres, ownership on restore).
|