Immich
En bref
Niveau : avancé
Durée : 45 minutes
Prérequis : Traefik et Authentik en place ; 4 Go de mémoire sur la VM
Niveau de test : reconstitué
Immich, c'est quoi ?
Immich est une galerie de photos et de vidéos auto-hébergée : sauvegarde automatique depuis le téléphone, albums partagés, recherche par visage, par lieu ou par contenu. C'est l'alternative libre aux services de photos en ligne. Il se compose de plusieurs conteneurs qui travaillent ensemble.
| Élément | Rôle |
|---|---|
| Serveur Immich | Interface web, API, envoi et traitement des fichiers |
| Machine learning | Reconnaissance de visages et recherche par contenu |
| PostgreSQL (image Immich) | Base de données, avec les extensions de recherche vectorielle |
| Redis (Valkey) | File des tâches de fond et cache |
Fichier immich.json |
Configuration déclarative, dont la connexion SSO |
Concrètement, la page suit le même modèle que Nextcloud : un fichier Compose et des labels Traefik. Deux différences. Le SSO est déclaré dans un fichier de configuration lu au démarrage, et non par une commande après coup. Et le premier compte administrateur est créé par le rôle, pour éviter qu'un inconnu s'inscrive le premier.
Prérequis
- Traefik publie
photos.penhoat.example(variableclient_subdomains.immich) et Authentik répond sursso.penhoat.example. immichfigure dansnebuleuse_appset dansnebuleuse_catalogue.- Deux secrets existent dans le Vault du client :
vault_immich_db_password(déjà prévu) etvault_immich_admin_password(à ajouter). - La VM dispose d'au moins 4 Go de mémoire : la reconnaissance de visages est gourmande.
vault_immich_admin_password complète la liste de la page « Inventaire et variables ». Valeur d'exemple : FICTIF-admin-motdepasse-6.
Arborescence du rôle
roles/immich/
├── defaults/main.yml
├── tasks/main.yml
└── templates/
├── docker-compose.yml.j2
└── immich.json.j2
Variables
# roles/immich/defaults/main.yml
# Version d'Immich : "v2" suit la version majeure 2. Immich publie souvent des changements
# incompatibles : lire les notes de version avant toute montée de version.
immich_version: "v2"
# Image PostgreSQL d'Immich. Elle embarque les extensions de recherche vectorielle.
# Le tag change à chaque version : le reprendre dans le docker-compose.yml officiel
# de la version d'Immich choisie.
immich_postgres_image: "ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0"
# Dossiers de travail sur la VM.
immich_dir: /opt/nebuleuse/immich
# Bibliothèque : photos et vidéos des utilisateurs. C'est le dossier qui grossit.
immich_library_dir: "{{ immich_dir }}/library"
# Nom de domaine de l'application.
immich_host: "{{ client_subdomains.immich }}.{{ client_domain }}"
# Secrets : définis dans le Vault, jamais en clair ici.
immich_db_password: "{{ vault_immich_db_password }}"
immich_admin_password: "{{ vault_immich_admin_password }}"
# Réseaux d'où Immich accepte l'en-tête X-Forwarded-For (réseaux privés de Docker).
immich_trusted_proxies: "172.16.0.0/12"
# Réseau Docker partagé avec Traefik.
immich_network: "{{ docker_proxy_network }}"
Le fichier Docker Compose
# roles/immich/templates/docker-compose.yml.j2
# Fichier géré par Ansible : toute modification manuelle sera écrasée.
# Il contient des secrets : accès réservé à root.
# Variables communes au serveur (ancre YAML : un seul endroit à modifier).
x-immich-env: &immich_env
DB_HOSTNAME: database
DB_USERNAME: immich
DB_DATABASE_NAME: immich
DB_PASSWORD: "{{ immich_db_password }}"
REDIS_HOSTNAME: redis
# Active la lecture de la configuration déclarative.
IMMICH_CONFIG_FILE: /config/immich.json
IMMICH_TRUSTED_PROXIES: "{{ immich_trusted_proxies }}"
TZ: Europe/Paris
services:
server:
image: ghcr.io/immich-app/immich-server:{{ immich_version }}
container_name: {{ client_name }}_immich_server
restart: unless-stopped
environment: *immich_env
volumes:
# Bibliothèque des utilisateurs.
- {{ immich_library_dir }}:/data
# Configuration déclarative, en lecture seule.
- {{ immich_dir }}/immich.json:/config/immich.json:ro
- /etc/localtime:/etc/localtime:ro
depends_on:
- redis
- database
networks:
- {{ immich_network }}
- internal
labels:
- "traefik.enable=true"
- "traefik.docker.network={{ immich_network }}"
- "traefik.http.routers.immich-{{ client_name }}.rule=Host(`{{ immich_host }}`)"
- "traefik.http.routers.immich-{{ client_name }}.entrypoints=websecure"
# Port d'écoute du serveur Immich.
- "traefik.http.services.immich-{{ client_name }}.loadbalancer.server.port=2283"
machine-learning:
image: ghcr.io/immich-app/immich-machine-learning:{{ immich_version }}
container_name: {{ client_name }}_immich_ml
restart: unless-stopped
volumes:
# Modèles téléchargés au premier usage : évite de les retélécharger à chaque démarrage.
- {{ immich_dir }}/model-cache:/cache
# Joint uniquement par le serveur, sur le réseau privé.
networks:
- internal
redis:
image: docker.io/valkey/valkey:8-bookworm
container_name: {{ client_name }}_immich_redis
restart: unless-stopped
networks:
- internal
database:
image: {{ immich_postgres_image }}
container_name: {{ client_name }}_immich_db
restart: unless-stopped
environment:
POSTGRES_USER: immich
POSTGRES_DB: immich
POSTGRES_PASSWORD: "{{ immich_db_password }}"
# Détecte la corruption silencieuse des données.
POSTGRES_INITDB_ARGS: "--data-checksums"
volumes:
# La base doit être sur un disque local, jamais sur un partage réseau.
- {{ immich_dir }}/db-data:/var/lib/postgresql/data
networks:
- internal
networks:
{{ immich_network }}:
external: true
internal: {}
Comme pour Authentik et Nextcloud, seul le serveur est sur le réseau proxy. Le service de machine learning, Redis et la base ne sont joignables que depuis le projet.
La configuration déclarative
Immich se configure dans son interface d'administration, mais accepte aussi un fichier JSON lu au démarrage. Ce fichier rend la configuration reproductible pour chaque client. En contrepartie, une fois le fichier actif, les réglages qu'il définit ne sont plus modifiables dans l'interface.
# roles/immich/templates/immich.json.j2
{
"server": {
"externalDomain": "https://{{ immich_host }}"
},
"passwordLogin": {
"enabled": true
},
"oauth": {
"enabled": true,
"issuerUrl": "{{ oidc_issuer }}",
"clientId": "{{ oidc_client_id }}",
"clientSecret": "{{ oidc_client_secret }}",
"scope": "openid email profile",
"signingAlgorithm": "RS256",
"buttonText": "Se connecter avec Authentik",
"autoRegister": true,
"autoLaunch": false,
"storageLabelClaim": "preferred_username"
}
}
Le format JSON n'accepte pas les commentaires : les réglages sont donc expliqués ci-dessous. Le fichier contient le secret client du SSO, d'où le mode 0600 à son dépôt.
| Réglage | Effet |
|---|---|
server.externalDomain |
Adresse publique, utilisée dans les liens de partage |
passwordLogin.enabled |
Garde la connexion par mot de passe : le compte administrateur local reste utilisable à côté du SSO |
oauth.issuerUrl |
Adresse de l'émetteur : Immich y trouve la découverte automatique de la configuration |
oauth.signingAlgorithm |
Algorithme de signature des jetons attendu (voir les pièges) |
oauth.autoRegister |
Crée le compte Immich à la première connexion par le SSO |
oauth.autoLaunch |
Si true, la page de connexion redirige directement vers le SSO |
oauth.storageLabelClaim |
Champ du jeton utilisé comme étiquette de stockage de l'utilisateur |
Déploiement
# roles/immich/tasks/main.yml
- name: Créer les dossiers d'Immich
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: "0755"
loop:
- "{{ immich_dir }}"
- "{{ immich_library_dir }}"
- "{{ immich_dir }}/model-cache"
- "{{ immich_dir }}/db-data"
# La connexion SSO est créée avant le fichier de configuration : elle fournit
# l'identifiant et le secret qui y sont inscrits.
- name: Créer la connexion SSO d'Immich dans Authentik
ansible.builtin.include_tasks: ../../authentik/tasks/oidc_app.yml
vars:
oidc_app_name: Immich
oidc_app_slug: immich
oidc_redirect_uris:
# Connexion depuis le navigateur.
- matching_mode: strict
url: "https://{{ immich_host }}/auth/login"
# Association d'un compte SSO depuis les réglages de l'utilisateur.
- matching_mode: strict
url: "https://{{ immich_host }}/user-settings"
# Application mobile.
- matching_mode: strict
url: "app.immich:///oauth-callback"
- name: Déposer la configuration d'Immich
ansible.builtin.template:
src: immich.json.j2
dest: "{{ immich_dir }}/immich.json"
# Le fichier contient le secret client : root uniquement.
mode: "0600"
register: immich_config
no_log: true
- name: Déposer le fichier Docker Compose d'Immich
ansible.builtin.template:
src: docker-compose.yml.j2
dest: "{{ immich_dir }}/docker-compose.yml"
mode: "0600"
no_log: true
- name: Démarrer Immich
community.docker.docker_compose_v2:
project_src: "{{ immich_dir }}"
state: present
# Immich ne relit son fichier de configuration qu'au démarrage.
- name: Redémarrer Immich si la configuration a changé
community.docker.docker_compose_v2:
project_src: "{{ immich_dir }}"
state: restarted
when: immich_config.changed
# Le premier démarrage applique les migrations de la base : cela peut durer quelques minutes.
- name: Attendre que l'API d'Immich réponde
ansible.builtin.command:
argv:
- curl
- -fsS
- -k
- --resolve
- "{{ immich_host }}:443:127.0.0.1"
- "https://{{ immich_host }}/api/server/ping"
register: immich_ping
until: immich_ping.rc == 0
retries: 60
delay: 5
changed_when: false
- name: Savoir si Immich a déjà un administrateur
ansible.builtin.command:
argv:
- curl
- -sS
- -k
- --resolve
- "{{ immich_host }}:443:127.0.0.1"
- "https://{{ immich_host }}/api/server/config"
register: immich_server_config
changed_when: false
# Le premier compte créé dans Immich devient administrateur : le rôle le crée
# avant que quiconque puisse s'inscrire.
- name: Créer le compte administrateur du client
ansible.builtin.command:
argv: >-
{{ ['curl', '-sS', '-k', '--fail-with-body',
'--resolve', immich_host ~ ':443:127.0.0.1',
'-X', 'POST', '-H', 'Content-Type: application/json',
'--data-binary', (immich_admin_payload | to_json),
'https://' ~ immich_host ~ '/api/auth/admin-sign-up'] }}
vars:
immich_admin_payload:
email: "{{ client_admin_email }}"
password: "{{ immich_admin_password }}"
name: "Administrateur {{ client_name }}"
when: not (immich_server_config.stdout | from_json).isInitialized
no_log: true
Vérification
# Sur la VM : quatre conteneurs "Up".
docker ps --filter name=immich --format '{{.Names}}\t{{.Status}}'
# L'API répond : doit afficher {"res":"pong"}.
curl -sS https://photos.penhoat.example/api/server/ping
# L'instance est initialisée et le SSO est activé.
curl -sS https://photos.penhoat.example/api/server/config | jq '{isInitialized, oauthButtonText}'
# La découverte de l'émetteur répond et indique l'algorithme de signature (attendu : RS256).
curl -sS https://sso.penhoat.example/application/o/immich/.well-known/openid-configuration \
| jq '{issuer, id_token_signing_alg_values_supported}'
Pièges
- Le premier compte créé devient administrateur : si Immich est exposé avant la création du compte, la première personne qui s'inscrit, ou qui se connecte par le SSO, prend ce rôle. Le rôle crée donc l'administrateur dès le démarrage.
- Réglages verrouillés dans l'interface : avec le fichier
immich.json, les rubriques qu'il définit sont en lecture seule. Toute modification passe par le fichier, puis un redémarrage. - Algorithme de signature : Immich attend des jetons signés en RS256. Authentik ne signe en RS256 que si le provider porte une clé de signature. C'est pourquoi la page Authentik associe la clé de signature par défaut à chaque provider.
- Trois adresses de retour : le navigateur, les réglages utilisateur et l'application mobile en utilisent chacun une. Sans la troisième, la connexion fonctionne dans le navigateur mais échoue sur le téléphone.
- Image PostgreSQL : le tag doit correspondre à la version d'Immich. Une image PostgreSQL ordinaire ne contient pas les extensions de recherche, et le serveur ne démarre pas. Un changement de version majeure de PostgreSQL impose une migration, pas un simple changement de tag.
- Base sur un partage réseau : PostgreSQL y est lent et peut se corrompre. Le dossier
db-datareste sur un disque local. - Mémoire et premier lancement : le service de machine learning télécharge ses modèles au premier usage et consomme plusieurs Go. Les premières tâches de reconnaissance sont longues, sans que ce soit une panne.
- La bibliothèque grossit vite : elle croît avec chaque photo et vidéo envoyées. La surveillance de l'espace disque et la sauvegarde du dossier
libraryet de la base sont à prévoir, la sauvegarde de la base seule ne suffit pas. - Montées de version : Immich publie régulièrement des changements incompatibles. Il faut lire les notes de version et sauvegarder avant de changer
immich_version. - Compte SSO et compte existant : Immich rattache la connexion SSO au compte qui porte la même adresse e-mail. L'adresse du compte d'Authentik doit donc être identique à celle du compte Immich.
- Immich ne joint pas le SSO : la découverte de l'émetteur échoue depuis le conteneur. Le mécanisme d'alias réseau de Traefik, décrit dans sa page, évite ce détour par l'adresse publique.
Page suivante : Vaultwarden.
No comments to display
No comments to display