Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016e2PKH5yN31h6JgHWCQb32
5.4 KiB
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.
{ 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)
- Get running docker Immich version (
docker exec <server> immich --versionor web UI footer). - Resolved 2026-08-05: running version is 2.7.5, equal to stable nixpkgs.
Use the stable module as-is (no
packageoverride). 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
- Record docker
UPLOAD_LOCATIONand DB container name/credentials.
Backup (before touching anything)
docker compose down(DB may stay up for the dump).- Dump DB:
docker exec -t <db> pg_dumpall --clean --if-exists --username=postgres > immich-db.sql - Verify upload folder intact; note size (no copy yet).
Cutover
- Add the module to jupiter's
environments.nix(leavedatabase.createDBdefault). sudo nixos-rebuild switch --flake '.#jupiter'→ creates user, empty DB + role,mediaLocation. Thensystemctl stop immich-server immich-machine-learning.- Restore the DB into the NixOS Postgres (drop the freshly-created empty
immichDB, loadimmich-db.sql) per Immich's restore docs. - Move media into
/var/lib/immich(subfolderslibrary/,upload/,thumbs/,encoded-video/,profile/);chown -R immich:immich /var/lib/immich. systemctl start immich-server; it runs schema migrations forward. Watchjournalctl -u immich-server -f.
Verify
- UI at
http://jupiter:2283loads; log in; spot-check albums, faces, a shared link, and that thumbnails/originals actually load. - Homepage tile works.
- Enable QSV/VAAPI in admin settings; transcode one video; confirm
journalctlshows 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).