Stack Immich (serveur + machine learning + PostgreSQL + Valkey) derrière un Traefik existant, avec sauvegarde borgwarehouse vérifiée et script de mise à jour.
- Docker et le plugin
compose, etmake. - Un Traefik déjà en place, avec un réseau Docker externe nommé
frontendet un certresolver nommémyresolver— c'est la stack TraefikV3. (Si tes noms diffèrent, adapte leslabelset le blocnetworksdudocker-compose.yml.) - Un enregistrement DNS A pointant le domaine vers le serveur.
borginstallé sur l'hôte (apt install borgbackup), et un serveur borgwarehouse joignable.- De la place : Immich stocke les originaux plus les miniatures et les vidéos transcodées. Compter large.
Un make sans argument liste tout ce qu'on peut faire sur cette stack.
Traefik coupe toute requête dont la lecture entière, corps compris, dépasse
60 secondes — c'est le défaut de readTimeout, vérifiable sur la version
installée :
docker exec traefik traefik --help | grep -A3 respondingtimeoutsSur une connexion mobile, une vidéo de quelques centaines de Mo dépasse déjà ce
délai : l'envoi échoue sans explication côté client. Le réglage n'est donc pas
cosmétique. C'est une modification de la configuration statique de Traefik,
donc dans l'autre dépôt (TraefikV3/traefik.yml), sur l'entryPoint websecure :
entryPoints:
websecure:
address: ":443"
http:
middlewares:
- crowdsec@file
transport:
respondingTimeouts:
readTimeout: 600s # défaut 60 s : c'est celui-là qui coupe les envois
idleTimeout: 600s # défaut 180 sRedémarrer Traefik ensuite (docker compose restart traefik dans son dossier) :
la configuration statique n'est pas rechargée à chaud. Le redémarrage coupe
brièvement toutes les stacks derrière ce Traefik — quelques secondes. Une
config statique invalide empêche Traefik de démarrer du tout : vérifier
docker ps et tester un domaine existant juste après.
git clone git@github.com:CoopCodeCommun/Immich.git /opt/immich
cd /opt/immich
# Copier l'exemple et remplir les variables
cp env_example .env
openssl rand -hex 32 # pour DB_PASSWORD
nano .env
# Lancer
make up && make logsLe premier démarrage est long : PostgreSQL initialise son cluster, puis Immich joue toutes ses migrations de schéma. Plusieurs minutes sur un disque lent.
make ps # les quatre conteneurs doivent être "healthy"Puis se rendre sur https://<DOMAIN> pour créer le compte administrateur.
Faire cette étape tout de suite : tant que le compte n'existe pas, n'importe
qui arrivant sur cette URL peut le créer.
Enfin, configurer la sauvegarde : make init (voir plus bas). Une stack sans
sauvegarde n'est pas en production.
Toute la configuration d'infrastructure passe par le .env. Après modification,
recréer le conteneur concerné :
docker compose up -dDB_PASSWORD, DB_USERNAME et DB_DATABASE_NAME ne doivent plus jamais
changer une fois la base initialisée : ça modifierait la configuration de
connexion sans modifier ce que la base attend réellement, et Immich ne pourrait
plus s'y connecter.
DB_PASSWORD ne doit contenir que des lettres et des chiffres (A-Za-z0-9) :
Immich construit une URL de connexion, et un caractère spécial non échappé la
casse.
L'image PostgreSQL est celle d'Immich (ghcr.io/immich-app/postgres), qui
embarque les extensions vectorielles nécessaires à la recherche. Une image
PostgreSQL standard ne convient pas.
Immich n'expose pas son SMTP en variables d'environnement. Il n'y a donc rien à
mettre dans le .env : les emails se règlent une fois la stack debout, dans
l'interface d'administration.
Ils servent aux invitations d'utilisateurs, aux réinitialisations de mot de passe et aux notifications d'albums partagés.
- Se connecter avec le compte administrateur
- Administration → Settings → Notification settings → Email
- Renseigner l'hôte, le port, l'utilisateur, le mot de passe, l'adresse d'expéditeur
- Send test email avant de sauvegarder — c'est le seul moyen de savoir que ça marche
Ces réglages vivent dans la base de données : ils sont donc couverts par la sauvegarde, et reviennent tels quels après une restauration.
Ports usuels : 465 en TLS implicite, 587 en STARTTLS. Si l'envoi échoue en
silence, l'hébergeur bloque peut-être le port 25 ou 465 en sortie.
Deux dossiers, créés par Docker au premier démarrage :
| Variable | Défaut | Contenu | Sauvegardé ? |
|---|---|---|---|
UPLOAD_LOCATION |
./library |
Les photos et vidéos, les miniatures, les profils | Oui, sauf thumbs/ et encoded-video/ |
DB_DATA_LOCATION |
./postgres |
Les fichiers PostgreSQL | Non — remplacé par le dump SQL |
UPLOAD_LOCATION accepte un chemin absolu : les photos peuvent vivre sur un
autre disque que la stack, le script de sauvegarde suit. DB_DATA_LOCATION, en
revanche, ne doit pas être sur un partage réseau (NFS, SMB) : PostgreSQL ne
le supporte pas.
Sous UPLOAD_LOCATION, Immich range :
| Dossier | Quoi | Perte acceptable ? |
|---|---|---|
library/ upload/ profile/ |
Les originaux | Non. Irremplaçables |
thumbs/ encoded-video/ |
Miniatures et transcodages | Oui : un job les refabrique |
backups/ |
Les dumps automatiques d'Immich | Oui : on fait le nôtre, et on le vérifie |
make backup dépose un pg_dump dans le dossier de la stack, puis pousse tout
le nécessaire dans une archive borg unique : le dump, les originaux, le .env
et le docker-compose.yml. Une archive suffit à remonter Immich de zéro. Détails
et exclusions dans scripts/README.md.
Une seule commande configure toute la sauvegarde vers le borgwarehouse de Code Commun (https://borgwarehouse.codecommun.coop/, SSH sur le port 2226) :
command -v borg || sudo apt install borgbackup
make initElle enchaîne : génération de la clé SSH dédiée (sur borgwarehouse, une clé =
un dépôt), création du dépôt via l'API, tirage d'une passphrase, borg init,
export de la clé, pose du cron, première sauvegarde et vérification.
Deux choses te seront demandées :
Un token API borgwarehouse (Account → Integrations), qui permet de créer le
dépôt automatiquement. Le générer avec la permission create uniquement :
c'est le seul appel que fait make init (POST /api/v1/repositories), tout le
reste — init, create, prune, list — passant par SSH avec la clé dédiée.
Un token create-only qui fuiterait ne permettrait ni de lister ni de supprimer
les dépôts, au pire d'en créer des parasites.
Le token n'est jamais stocké. Il peut être saisi au clavier, ou passé le temps d'une exécution :
BW_API_TOKEN=xxxxxxxx make initDe confirmer que tu as mis la passphrase au coffre. Le script affiche la
passphrase, la clé exportée et l'adresse du dépôt, puis attend un OUI. Ce n'est
pas une formalité : sans ces éléments, les archives sont un bloc chiffré
définitivement illisible. C'est le seul maillon que la sauvegarde ne peut pas se
sauvegarder elle-même. Coffre-fort numérique, tout de suite.
Le quota demandé est en Go. Mesurer d'abord ce qu'il y a à sauvegarder :
du -sh library/library library/upload library/profilemake init est rejouable. API injoignable, borg init raté, Ctrl-C en plein
milieu : relance-le, il reprend ce qui existe. Le seul cas où il refuse, c'est
quand le dépôt contient déjà des archives — régénérer une passphrase les
rendrait illisibles.
make checkIl répond à la seule question qui compte — est-ce restaurable ? — sans rien restaurer : fraîcheur de la dernière archive, présence du dump et des originaux, et surtout dump non tronqué (un dump coupé en plein vol a une taille crédible, se trouve bien dans l'archive, et ne se restaure pas).
Sortie en code non nul si quoi que ce soit cloche : utilisable tel quel dans un monitoring.
Ce que make check ne teste pas : ta copie de coffre-fort. Il ouvre le dépôt
avec le .env de la machine, pas avec la passphrase que tu as archivée ailleurs
— or c'est celle-là, et elle seule, qui servira le jour où le serveur aura brûlé.
Vérifie une fois, depuis une autre machine, qu'un borg list passe avec les
éléments du coffre.
C'est borgwarehouse qui envoie un mail si le dépôt ne reçoit plus rien
(alerte réglée par make init sur 25 h pour une sauvegarde quotidienne). Un cron
qui échoue en silence, c'est un backup qui n'existe pas : make check dit que la
dernière sauvegarde est bonne, l'alerte BWH prévient qu'il n'y en a plus.
7 jours glissants, 30 quotidiennes, 12 hebdomadaires, puis toutes les mensuelles et annuelles.
make updateIl rejoue une sauvegarde avant le docker compose pull, puis attend qu'Immich
repasse au vert (et sort en erreur en affichant les logs si ce n'est pas le cas).
L'ordre n'est pas décoratif : au premier démarrage d'une nouvelle version, Immich joue ses migrations de schéma, et elles ne sont pas réversibles. Si l'une d'elles se passe mal, la seule issue est de revenir à l'image précédente et de restaurer la base. D'où le dump vieux de trois minutes plutôt que de la veille.
IMMICH_VERSION est épinglé sur v3 : les correctifs et versions mineures
arrivent au pull, jamais une majeure par surprise. Le passage à v4, le jour
venu, sera une modification explicite du .env — après lecture des
release notes, qu'Immich publie
avec les manœuvres à faire.
Une archive contient tout : le dump SQL, les originaux, le .env et le
docker-compose.yml.
# Sur la nouvelle machine, avec borg installé.
# La clé SSH du dépôt n'est PAS dans l'archive (voir plus bas) : en générer une
# nouvelle et l'ajouter au dépôt depuis l'interface borgwarehouse.
ssh-keygen -t ed25519 -N '' -f ~/.ssh/immich_restore_ed25519
cat ~/.ssh/immich_restore_ed25519.pub # → à ajouter au dépôt sur BWH
export BORG_REPO='ssh://borgwarehouse@…/./xxxxxxxx'
export BORG_PASSPHRASE='…' # depuis le coffre-fort
export BORG_RSH="ssh -i ~/.ssh/immich_restore_ed25519 -oIdentitiesOnly=yes"
borg list # choisir l'archive
borg extract --list "$BORG_REPO::<archive>"
# L'extraction recrée l'arborescence absolue d'origine, sans le / initial :
# opt/immich/ <- le dossier complet, .env compris
cd opt/immich/ # ou déplacer le dossier où on veutPuis démarrer la base seule, y injecter le dump, et seulement ensuite lancer Immich :
docker compose up -d --wait database # --wait : rend la main quand la base est prete
sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" \
scripts/pg-dump-<prefix>/immich.sql \
| docker compose exec -T database sh -c 'PGPASSWORD="$POSTGRES_PASSWORD" exec psql \
--dbname="$POSTGRES_DB" --username="$POSTGRES_USER" \
--single-transaction --set ON_ERROR_STOP=on'
docker compose up -d
docker compose logs -f immich-serverLe sed n'est pas décoratif : pg_dump neutralise le search_path en tête de
dump, et les extensions vectorielles d'Immich ne se recréent pas dans ces
conditions. C'est la procédure officielle Immich.
--single-transaction --set ON_ERROR_STOP=on fait échouer la restauration en
bloc à la première erreur, plutôt que de laisser une base à moitié remplie qui
aurait l'air de marcher.
Enfin, dans Administration → Jobs, relancer Generate thumbnails et Transcode videos : ces dossiers ne sont pas sauvegardés, ils se refabriquent.
cd /opt/immich
docker compose stop immich-server immich-machine-learning # on laisse la base debout
borg extract --list "$BORG_REPO::<archive>" # extrait dans le dossier courant
rsync -a --delete <chemin-extrait>/library/library/ ./library/library/
rsync -a --delete <chemin-extrait>/library/upload/ ./library/upload/
rsync -a --delete <chemin-extrait>/library/profile/ ./library/profile/Puis le même bloc sed | psql que ci-dessus, et docker compose up -d.
La clé SSH du dépôt (scripts/.ssh/) : on ne met pas la clé du coffre dans
le coffre. En cas de perte totale, en générer une nouvelle et l'ajouter au dépôt
depuis l'interface borgwarehouse.
Doivent donc vivre dans un coffre-fort numérique, sans quoi la sauvegarde est irrécupérable :
- la passphrase du dépôt borg,
- la clé du dépôt exportée (
borg key export), - l'identifiant du dépôt (ex.
c7a620ed).
Un conteneur reste unhealthy au premier up — c'est presque toujours le
temps : PostgreSQL initialise son cluster, puis Immich joue ses migrations.
docker compose logs -f immich-server et laisser tourner. Les données
persistent : un simple docker compose up -d repart de là.
Erreur de connexion à la base — vérifier qu'aucune des variables DB_* n'a
été modifiée depuis l'initialisation de la base, et que DB_PASSWORD ne contient
que des lettres et des chiffres.
Traefik ne route pas — vérifier que le réseau frontend existe
(docker network ls) et que le conteneur y est bien attaché
(docker inspect immich_server).
Certificat non émis — le DNS doit pointer sur le serveur avant le premier
démarrage, sinon le certresolver échoue et retente avec un délai qui grandit à
chaque essai. Corriger le DNS puis docker compose restart côté Traefik.
Un gros upload échoue depuis un mobile — voir le réglage respondingTimeouts
de Traefik, en haut de ce README.
La recherche intelligente ne trouve rien — le machine learning télécharge ses modèles au premier usage, puis il faut lancer Smart Search dans Administration → Jobs pour indexer la bibliothèque existante. C'est long sur une grosse bibliothèque, et ça mange du CPU.
make backup dit « une sauvegarde est déjà en cours » — le verrou flock
fait son travail : le cron tourne au même moment. Attendre, ou regarder
$HOME/.immich-backup-<prefix>.log.
Ces options s'ajoutent le jour où le besoin se présente :
- Accélération matérielle du transcodage (
hwaccel.transcoding.yml) et de l'inférence (hwaccel.ml.yml) — dépendent du matériel du serveur. Les blocsextendssont déjà en place, commentés, dans ledocker-compose.yml. - OAuth / OIDC — Immich sait déléguer l'authentification à un Keycloak. Ça se règle entièrement dans l'interface d'administration, rien à changer ici.
- Bibliothèques externes — monter un dossier de photos existant en lecture
seule, qu'Immich indexe sans le déplacer. Demande un volume supplémentaire sur
immich-server. - Port local exposé — la stack n'écoute que derrière Traefik. Pour un accès
LAN direct, rajouter
ports: ['2283:2283']surimmich-server.