Immich ist eine selbst gehostete Verwaltung für Fotos und Videos, mit Apps für Android und iOS, Gesichtserkennung, Volltextsuche in Bildern und Freigabe-Links.
Seit kurzem liegen vier Ports im Baum, und damit läuft der Dienst nativ, mit rc-Skripten, newsyslog und pkg-Updates wie jeder andere Serverdienst:
www/immich, Server und Weboberflächewww/immich-ml, maschinelles Lernen für Suche, Gesichter und Texterkennungwww/immich-cli, Kommandozeilenwerkzeug für Massenimportewww/immich-public-proxy, schlanker Proxy nur für öffentliche Freigaben
Diese Anleitung geht den kompletten Weg durch, von der leeren Maschine bis zur ersten hochgeladenen Aufnahme. Alle Befehle sind so getestet.
Voraussetzungen
Eine 64-Bit-Maschine, also amd64 oder aarch64. Die Ports sind bewusst auf diese Architekturen beschränkt, auf i386 gibt es weder eine lauffähige ONNX-Laufzeit noch die nötigen nativen Node-Module.
Rechne mit reichlich Arbeitsspeicher. Server und Lerndienst halten ihre Modelle und Caches im Speicher, und die Bildverarbeitung arbeitet parallel. Eine knapp bemessene VM ist der häufigste Grund für abgebrochene Jobs.
Für die Mediendateien brauchst du viel Platz an einer Stelle, die sich später noch erweitern lässt. Ein eigenes ZFS-Dataset ist dafür die bequemste Wahl.
Schritt 1: PostgreSQL mit pgvector und VectorChord
Immich speichert die Suchvektoren in der Datenbank und braucht dafür zwei Erweiterungen. Beide liegen als Ports vor:
pkg install postgresql18-server postgresql18-contrib \
postgresql18-pgvector postgresql18-vchord
sysrc postgresql_enable=YES
service postgresql initdb
VectorChord muss vom Server vorab geladen werden. Dazu in /var/db/postgres/data18/postgresql.conf eintragen:
shared_preload_libraries = 'vchord.so'
Dann starten:
service postgresql start
Schritt 2: Datenbank und Rolle anlegen
Rolle und Datenbank gehören Immich allein. Das Passwort trägst du später in die Konfigurationsdatei ein, vergib also etwas Langes:
su -l postgres -c "createuser -P immich"
su -l postgres -c "createdb -O immich immich"
Die Erweiterungen legt der Superuser in dieser Datenbank an. Wenn du das hier tust, braucht die Rolle immich später keine Superuser-Rechte:
su -l postgres -c "psql -d immich -c 'CREATE EXTENSION vector'"
su -l postgres -c "psql -d immich -c 'CREATE EXTENSION vchord CASCADE'"
su -l postgres -c "psql -d immich -c 'CREATE EXTENSION cube'"
su -l postgres -c "psql -d immich -c 'CREATE EXTENSION earthdistance'"
su -l postgres -c "psql -d immich -c 'CREATE EXTENSION pg_trgm'"
Das ist der Punkt, an dem die meisten Installationen hängen bleiben. Immich führt beim Start Schema-Migrationen aus, und die wollen die Erweiterungen vorfinden. Alternativ gibst du der Rolle vorübergehend SUPERUSER und nimmst es danach wieder weg, aber der Weg oben ist der sauberere.
Schritt 3: Cache einrichten
Immich erwartet einen Redis-kompatiblen Cache. Valkey erfüllt das:
pkg install valkey
sysrc valkey_enable=YES
service valkey start
Schritt 4: Immich installieren und konfigurieren
pkg install immich
Die Verbindungsdaten kommen in eine eigene Datei. Eine Vorlage liegt bei:
cp /usr/local/etc/immich.env.sample /usr/local/etc/immich.env
chown root:wheel /usr/local/etc/immich.env
chmod 600 /usr/local/etc/immich.env
Inhalt anpassen, das Passwort ist das aus Schritt 2:
DB_HOSTNAME=localhost
DB_USERNAME=immich
DB_DATABASE_NAME=immich
DB_PASSWORD=<dein Passwort>
REDIS_HOSTNAME=localhost
Die Datei liest das rc-Skript als root, der Dienstbenutzer braucht keinen Zugriff darauf. Deshalb Modus 0600 und Eigentümer root.
Schritt 5: Dienst starten
Der Server lauscht ab Werk auf allen Adressen, und sein Worker öffnet zusätzlich einen zweiten Port auf einer zufälligen Nummer. Binde ihn darum auf die Loopback-Adresse und setze einen Reverse Proxy davor:
sysrc immich_server_enable=YES
sysrc immich_server_host=127.0.0.1
service immich_server start
Soll das Medienverzeichnis woanders liegen, etwa auf einem eigenen Dataset:
sysrc immich_server_media_location=/tank/immich
Ein Blick ins Log zeigt, ob die Migrationen durchgelaufen sind:
tail -f /var/log/immich/immich_server.log
Danach ist die Oberfläche auf Port 2283 erreichbar. Der erste Aufruf fragt nach dem Administrator-Konto.
Vor dem ersten Import eine Einstellung setzen: unter Administration > Settings > Storage Template ein Schema aktivieren, zum Beispiel nach Jahr und Monat. Ohne das legt Immich die Dateien unter Zufallsnamen ab, und die Bibliothek ist ohne die Datenbank nicht mehr lesbar. Nachträglich lässt sich das umstellen, dann muss Immich aber den gesamten Bestand umsortieren.
Schritt 6: Maschinelles Lernen
Ohne diesen Dienst fehlen Volltextsuche in Bildern, Gesichtserkennung, Dublettensuche und Texterkennung:
pkg install immich-ml
sysrc immich_ml_enable=YES
service immich_ml start
Der Dienst lauscht auf der Loopback-Adresse, Port 3003, und das rc-Skript des Servers zeigt von sich aus dorthin. Prüfe trotzdem in der Oberfläche unter Administration > Settings > Machine Learning, dass dort http://127.0.0.1:3003 steht. Die Voreinstellung aus dem Container-Umfeld lautet http://immich-machine-learning:3003, und solange das drinsteht, läuft schlicht nichts.
Die Modelle lädt der Dienst beim ersten Gebrauch herunter und legt sie unter /var/db/immich-ml ab. Die Berechnung läuft auf der CPU, eine Grafikbeschleunigung gibt es unter FreeBSD nicht, weil onnxruntime sie hier nicht unterstützt. Für eine überschaubare Sammlung reicht das gut, der erste Durchlauf über eine große Bibliothek braucht aber Geduld.
Feinheiten wie die Anzahl der Arbeitsprozesse oder die Haltezeit der Modelle gehören in /usr/local/etc/immich-ml.env.
Schritt 7: Reverse Proxy mit TLS
Immich selbst bringt kein TLS mit, und die Apps wollen eine verschlüsselte Verbindung. Ein Beispiel für nginx:
server {
listen 443 ssl;
server_name photos.example.org;
ssl_certificate /usr/local/etc/letsencrypt/live/photos.example.org/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/photos.example.org/privkey.pem;
client_max_body_size 50G;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
location / {
proxy_pass http://127.0.0.1:2283;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Die drei Punkte, die man beim ersten Versuch vergisst: eine großzügige client_max_body_size, sonst scheitern Videos beim Upload; die Upgrade-Header, sonst bleibt die Oberfläche auf dem Ladebildschirm hängen, weil Immich WebSockets nutzt; und reichlich Zeit in den Timeouts, weil große Dateien lange am Stück übertragen werden.
Schritt 8: Bestand importieren
Für den ersten großen Import ist die Kommandozeile der bequemere Weg als die App:
pkg install immich-cli
Anmelden mit einem API-Schlüssel aus der Oberfläche (Account Settings > API Keys):
immich login https://photos.example.org/api <API-Schlüssel>
Dann rekursiv hochladen, Dubletten erkennt der Server selbst:
immich upload --recursive /pfad/zu/den/bildern
Mit --album landen die Dateien direkt in einem Album, mit --dry-run siehst du vorher, was passieren würde.
Schritt 9: Freigaben ins Internet, ohne den Server zu öffnen
Wer Links an Familie oder Kunden verschickt, muss dafür nicht die ganze Instanz erreichbar machen. Der Public Proxy bedient ausschließlich die in Immich freigegebenen Alben:
pkg install immich-public-proxy
cp /usr/local/etc/immich-public-proxy.env.sample /usr/local/etc/immich-public-proxy.env
In die Datei trägst du ein, wo der Server intern steht und unter welcher Adresse der Proxy von außen erreicht wird:
IMMICH_URL=http://127.0.0.1:2283
PUBLIC_BASE_URL=https://photos.example.org
sysrc immich_public_proxy_enable=YES
service immich_public_proxy start
Er lauscht auf Port 3000 und gehört genauso hinter einen Reverse Proxy mit TLS. Der Immich-Server selbst bleibt dabei aus dem Internet unerreichbar.
Betrieb
Sicherung. Sichere beides, Datenbank und Medienverzeichnis. Die in Immich eingebaute Datenbanksicherung schreibt in einen Unterordner des Medienverzeichnisses und enthält die Bilder nicht. Einen solchen Abzug zurückzuholen geht so, und die Erweiterungen müssen vor dem Einlesen existieren:
su -l postgres -c "createdb -O immich immich"
su -l postgres -c "psql -q -d immich -c 'CREATE EXTENSION vector'"
su -l postgres -c "psql -q -d immich -c 'CREATE EXTENSION vchord CASCADE'"
gzip -dc immich-db-backup-<zeitstempel>.sql.gz | su -l postgres -c "psql -q -d immich"
Aktualisieren. Immich führt Schema-Migrationen beim Start automatisch aus. Also vorher sichern, und einen Rückschritt auf eine ältere Version sieht Upstream nicht vor.
Nach einem PostgreSQL-Hauptversionswechsel müssen pgvector und VectorChord neu gebaut werden, sonst findet der Server seine Erweiterungen nicht.
Nach einem Node-Hauptversionswechsel baust du www/immich neu, damit die nativen Module zur Laufzeitversion passen.
Logs liegen unter /var/log/immich und werden über /usr/local/etc/newsyslog.conf.d/immich.conf rotiert.
Zwei Fallen, die Zeit kosten
HDR-Videos. Immich baut seine Filterkette für HDR um tonemapx, einen Filter, den das normale multimedia/ffmpeg nicht mitbringt. Der Port zieht deshalb multimedia/jellyfin-ffmpeg und setzt FFMPEG_PATH und FFPROBE_PATH im rc-Skript darauf. Wer stattdessen das normale ffmpeg erzwingt, bekommt für HDR-Aufnahmen überhaupt kein Vorschaubild, ohne Fehlermeldung in der Oberfläche. Eine andere Variante trägst du über dieselben beiden Variablen in immich.env ein.
Die Lernmodelle und die CPU. Der erste vollständige Durchlauf über eine gewachsene Bibliothek beschäftigt die Maschine lange. Plane das in eine Nacht, und halte den Dienst nicht für defekt, nur weil die Suche am Anfang wenig findet.