Pourquoi Paperless-ngx pour votre GED self-hosted
Paperless-ngx est la fork communautaire la plus active de Paperless — un système de gestion électronique de documents (GED) qui indexe vos PDF et images scannées, en extrait le texte via OCR, et vous permet de les retrouver par mot-clé, date, correspondant ou tag.
Par rapport aux alternatives (Mayan EDMS, OpenDocMan), Paperless-ngx se distingue par sa simplicité d'installation (Docker Compose en moins de 10 minutes), son interface web écrite en Angular, et son support de l'OCR multilingue via Tesseract.
Le projet est maintenu par sa communauté et publie des releases régulières — vérifiez la version courante sur la page des releases GitHub avant d'installer. La branche courante est la série 3.x ; si vous partez d'une instance 2.x déjà en service, lisez la section « Mettre à jour de 2.x vers 3.x » plus bas avant de toucher à votre docker-compose.yml : la v3 rend obligatoires deux réglages qui étaient déduits ou implicites, et change plusieurs comportements par défaut.
Cas d'usage typiques :
- Archive des factures fournisseurs pour une micro-entreprise ou une PME
- Dossier médical personnel ou familial (ordonnances, analyses, courriers)
- Gestion des documents d'une association ou d'une copropriété
- Archive des contrats et des baux pour une agence immobilière
Prérequis et choix du VPS
Paperless-ngx est plus gourmand qu'il n'y paraît, surtout à l'import. Le traitement OCR d'un PDF multi-pages sollicite intensément le CPU — c'est la principale source de timeouts sur les petites configurations.
Configuration minimale recommandée :
- CPU : 2 vCPU (le traitement OCR est mono-thread par tâche, mais plusieurs tâches peuvent tourner en parallèle)
- RAM : 2 Go minimum ; 4 Go pour un usage confortable
- Stockage : SSD, 20 Go minimum pour l'application + prévoir l'espace pour vos documents
- OS : Debian 12 ou Ubuntu 22.04/24.04
Ce qui crée des timeouts sur les petits VPS : les PDFs scannés à haute résolution (300+ DPI) ou comportant de nombreuses pages (50+) peuvent dépasser le PAPERLESS_WORKER_TIMEOUT par défaut (1 800 secondes). La section dédiée plus bas couvre la résolution.
Installation avec Docker Compose
L'installation officielle recommandée utilise Docker Compose avec trois services : webserver (l'application), broker (la file de tâches) et db (la base PostgreSQL).
⚠️ Téléchargez les TROIS fichiers, pas seulement le docker-compose.yml. C'est le piège d'installation le plus coûteux de cette page, parce qu'il échoue en silence. Le compose officiel déclare env_file: docker-compose.env : les réglages du conteneur sont lus dans ce fichier-là. Le .env du répertoire, lui, ne porte qu'une seule ligne — COMPOSE_PROJECT_NAME=paperless — et sert à Docker Compose pour nommer le projet et préfixer les volumes ; il n'est pas injecté dans les conteneurs. Écrire vos PAPERLESS_* dans .env ne produit aucune erreur : le conteneur démarre, l'interface répond, et aucun de vos réglages n'est appliqué. L'OCR retombe alors sur l'anglais — exactement le piège que la section suivante décrit.
# Répertoire de travail
mkdir -p /opt/paperless && cd /opt/paperless
# Les TROIS fichiers officiels
BASE=https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose
curl -fsSL $BASE/docker-compose.postgres.yml -o docker-compose.yml
curl -fsSL $BASE/docker-compose.env -o docker-compose.env
curl -fsSL $BASE/.env -o .env
# Générer la clé AVANT d'écrire quoi que ce soit : un heredoc QUOTÉ (<<'EOF') n'exécute
# aucune substitution, la ligne serait écrite littéralement et le « secret » serait public.
SECRET_KEY=$(openssl rand -hex 32)
# Le gabarit livre PAPERLESS_SECRET_KEY=change-me : on la REMPLACE, on ne la duplique pas.
sed -i "s|^PAPERLESS_SECRET_KEY=.*|PAPERLESS_SECRET_KEY=$SECRET_KEY|" docker-compose.env
# Le reste des réglages va dans le MÊME fichier (heredoc non quoté : $VAR est bien remplacé)
cat >> docker-compose.env <<EOF
PAPERLESS_URL=https://paperless.votre-domaine.com
PAPERLESS_TIME_ZONE=Europe/Paris
PAPERLESS_OCR_LANGUAGE=fra+eng+ara
PAPERLESS_OCR_LANGUAGES=fra ara
EOF
docker compose pull
docker compose up -dPAPERLESS_DBENGINE, PAPERLESS_DBHOST et PAPERLESS_REDIS sont déjà posés dans le bloc environment: du compose officiel : ne les redéclarez pas dans docker-compose.env. En revanche, si vous écrivez votre propre compose, PAPERLESS_DBENGINE=postgresql est obligatoire depuis la v3 (voir la section migration) : sans lui, Paperless part sur SQLite.
L'interface est accessible sur le port 8000 après environ 60 secondes de démarrage, et vous invite à créer le compte administrateur à la première connexion. Si vous préférez le faire en ligne de commande — installation scriptée, ou reprise d'accès plus tard :
docker compose exec webserver createsuperuserConfigurez ensuite votre reverse proxy (nginx ou Traefik) pour exposer le service avec TLS. Si vous en placez un devant Paperless, lisez le paragraphe sur PAPERLESS_TRUSTED_PROXIES dans la section migration : en v3, un proxy mal déclaré fait échouer le login en 403.
Configuration OCR multilingue
L'OCR est le cœur de Paperless-ngx. Sa configuration détermine la qualité de l'indexation et la capacité à retrouver vos documents.
Langues Tesseract disponibles : le paramètre PAPERLESS_OCR_LANGUAGE accepte les codes de langue Tesseract séparés par +. Pour un usage franco-marocain :
PAPERLESS_OCR_LANGUAGE=fra+ara+eng # langues utilisées à l'OCR
PAPERLESS_OCR_LANGUAGES=fra ara # paquets Tesseract à INSTALLER (séparés par des espaces)⚠️ Les deux variables sont nécessaires, et c'est le second piège coûteux de cette page. PAPERLESS_OCR_LANGUAGE vaut eng par défaut et ne fait que *choisir* la langue ; pour toute langue non présente dans l'image, la doc impose de renseigner aussi PAPERLESS_OCR_LANGUAGES (liste séparée par des espaces, pas par des +) sur les déploiements Docker. Sans elle, l'OCR retombe silencieusement sur l'anglais : vos documents sont indexés, mais le texte français et arabe est illisible — et rien dans l'interface ne le signale.
L'image embarque déjà l'anglais, l'allemand, l'italien, l'espagnol et le français : seul le reste est à installer. Garder aussi en tête que Tesseract consomme nettement plus de CPU avec plusieurs langues activées — n'en déclarez que ce que vous scannez réellement.
Mode OCR : PAPERLESS_OCR_MODE contrôle quand l'OCR est appliqué. Quatre valeurs existent — et skip, qu'on lit souvent, n'en fait pas partie :
- auto (défaut) : Paperless regarde via pdftotext si le PDF porte déjà du texte ; s'il y en a assez, l'OCR est sauté pour ce document, sinon il tourne normalement. C'est l'option la plus sûre sur un fonds mixte
- redo : ré-OCRise toutes les pages et tente de remplacer les couches de texte existantes — utile quand le scanner a produit un OCR médiocre. Peut échouer sur certains documents (formulaires) ; le texte d'origine est alors conservé
- force : rastérise le document, transforme le texte en image et pose l'OCR par-dessus. Marche partout, mais le fichier grossit et le texte est moins net au zoom
- off : n'invoque jamais l'OCR ; pour les PDF le texte est extrait par pdftotext seul, et les images ressortent sans texte
⚠️ Si vous arrivez d'une instance 2.x, skip et skip_noarchive existaient et ont été retirés en v3. Une valeur retirée n'est pas honorée en silence : Paperless journalise un avertissement au démarrage et applique le défaut. La v3 sépare en deux réglages ce que skip mélangeait — quand faire l'OCR (PAPERLESS_OCR_MODE) et quand produire l'archive PDF/A (PAPERLESS_ARCHIVE_FILE_GENERATION, valeurs auto par défaut, always, never). La section migration donne la table de correspondance.
Pour la plupart des usages, laissez auto : il évite de re-traiter les PDF natifs (contrats, factures générées par un logiciel) sans que vous ayez rien à régler.
Gestion des dates de document : le problème et la solution
La détection automatique des dates est l'une des fonctionnalités les plus puissantes de Paperless-ngx — et l'une des plus frustrantes quand elle ne fonctionne pas. Par défaut, Paperless-ngx essaie de détecter la date du document dans son contenu textuel et son nom de fichier. Plusieurs raisons peuvent mener à une date incorrecte ou absente.
Problème 1 : La date est en format non reconnu. Paperless-ngx détecte les formats courants (DD/MM/YYYY, YYYY-MM-DD, etc.) mais peut rater des formats ambigus (08/09/2025 : le 8 septembre ou le 9 août ?).
Solution : vérifier — et non « configurer » — l'ordre de lecture. PAPERLESS_DATE_ORDER vaut déjà DMY par défaut, c'est-à-dire jour, mois, année : le poser explicitement ne change donc rien et ne résout pas une ambiguïté. Ce réglage ne sert qu'à s'en écarter, par exemple pour un fonds de documents américains :
PAPERLESS_DATE_ORDER=MDY # MM/DD/YYYY — à ne poser QUE si vos documents sont datés ainsiPour un fonds mixte, aucun ordre global ne peut être correct : c'est le nommage des fichiers (problème 3 ci-dessous) qui tranche.
Problème 2 : Le document a plusieurs dates et la mauvaise est choisie. Par exemple, une facture qui mentionne la date de la prestation ET la date d'émission — Paperless-ngx prend la première trouvée.
Solution : utiliser le champ de date manuel dans l'interface web pour corriger les documents mal datés, ou configurer des règles de correspondance (Matching rules) qui assignent une date depuis le nom de fichier.
Problème 3 : La date de création du fichier est utilisée à la place. Quand aucune date n'est trouvée dans le contenu, Paperless-ngx utilise la date de modification du fichier comme fallback — ce qui peut être très trompeur pour des documents anciens scannés récemment.
Solution : nommer les fichiers d'import avec la date du document (YYYY-MM-DD_nom-document.pdf) — Paperless-ngx détecte ce format dans le nom de fichier avant d'analyser le contenu.
Résolution des timeouts sur VPS entrée de gamme
Sur un VPS avec 1 ou 2 vCPU, le traitement OCR de documents volumineux peut dépasser le timeout par défaut et laisser le document en statut « En attente de traitement » indéfiniment.
Diagnostic : vérifier les logs du worker :
docker compose logs celery --tail=50Les lignes SoftTimeLimitExceeded confirment un timeout.
Résolution — quatre leviers :
1. Augmenter le timeout des tâches :
PAPERLESS_WORKER_TIMEOUT=3600 # 1 heure (défaut : 1800 s)⚠️ Le nom exact compte : PAPERLESS_WORKER_TIMEOUT. Un réglage mal orthographié n'est pas rejeté par Paperless — il est ignoré en silence, et les timeouts continuent exactement comme avant, ce qui se lit à tort « le correctif n'a pas marché ».
2. Réduire la résolution d'import. Si vous scannez vous-même les documents, 200 DPI est suffisant pour un OCR de qualité — 300 DPI double le temps de traitement sans amélioration perceptible pour du texte standard.
3. Limiter le traitement parallèle :
PAPERLESS_TASK_WORKERS=1 # tâches en parallèle (défaut : 1)
PAPERLESS_THREADS_PER_WORKER=1 # pages traitées en parallèle sur UN documentNon défini, PAPERLESS_THREADS_PER_WORKER vaut max(floor(nb_cœurs / PAPERLESS_TASK_WORKERS), 1). La règle upstream à ne pas franchir : le produit TASK_WORKERS × THREADS_PER_WORKER ne doit pas dépasser le nombre de cœurs, sinon l'instance devient extrêmement lente. Beaucoup de workers = beaucoup de documents en parallèle ; beaucoup de threads = un gros document traité plus vite.
Sur un VPS 2 vCPU, traiter deux documents en parallèle peut provoquer des timeouts que traiter le même volume séquentiellement éviterait.
4. Optimiser le préprocessing PDF :
PAPERLESS_OCR_USER_ARGS={"optimize": 1, "pdfa-image-compression": "jpeg"}Ceci compresse les images dans les PDFs avant traitement, réduisant la charge mémoire et CPU.
Mettre à jour de 2.x vers 3.x
La série 3.x de Paperless-ngx apporte des changements structurels qui rendent la migration in-place obligatoire, et ajoute des conditions préalables qui n'existaient pas sur la branche 2.x.
Le prérequis qu'on oublie : la montée en v3 n'est supportée que depuis la 2.20.15. Si vous tournez sur une version antérieure, montez d'abord en 2.20.15, puis seulement en 3.x. Basculer directement l'étiquette de l'image d'une 2.14 vers une 3.x n'est pas un chemin supporté, et rien ne vous en avertira au démarrage.
L'export/import entre versions (document_exporter puis document_importer sur une instance vierge) n'est pas supporté — et la règle upstream est plus large qu'on ne le croit : elle vaut entre deux versions quelconques, pas seulement entre majeures, parce qu'un export contient une image exacte de la base. En pratique, document_importer prévient (Version mismatch: Currently 3.1.x, importing 2.20.15. Continuing, but import may fail.) puis échoue sur KeyError: 'show_on_dashboard' — un champ du modèle SavedView qui existait en 2.20.15 et n'existe plus dans le schéma 3.x. La seule voie sûre est de laisser les migrations s'appliquer sur la base existante.
Procédure recommandée :
1. Faire une sauvegarde complète avant toute chose (voir la section dédiée ci-dessous).
2. Arrêter les services : docker compose down.
3. Passer en revue dans docker-compose.env et docker-compose.yml les réglages que la v3 rend obligatoires ou retire (détaillés juste après).
4. Pointer l'image du docker-compose.yml sur la version 3.x cible et relancer : docker compose up -d — les migrations Django s'appliquent automatiquement au démarrage du webserver.
5. Surveiller les logs : docker compose logs webserver --tail=100 — une migration qui échoue affiche l'erreur et bloque le démarrage.
PAPERLESS_SECRET_KEY devient obligatoire. Il existait auparavant une clé interne par défaut ; en v3 elle doit être déclarée, et Paperless refuse de démarrer sans elle. Réutiliser l'ancienne valeur conserve les sessions et les jetons signés ; en poser une nouvelle les invalide tous. C'est un choix à faire sciemment, pas un détail de configuration.
PAPERLESS_DBENGINE devient obligatoire avec PostgreSQL ou MariaDB. En v2, le moteur était déduit de la présence de PAPERLESS_DBHOST ; en v3 il doit être explicite, et la valeur par défaut est sqlite. Les valeurs acceptées sont sqlite, postgresql et mariadb — rien d'autre. C'est le piège de migration numéro un : sans ce réglage, l'instance démarre sur une base SQLite vide, votre PostgreSQL est intact mais plus personne ne le lit, et l'écran d'accueil vous annonce zéro document.
# v2 (PostgreSQL déduit de PAPERLESS_DBHOST)
PAPERLESS_DBHOST: db
# v3 (le moteur doit être explicite)
PAPERLESS_DBENGINE: postgresql
PAPERLESS_DBHOST: dbPAPERLESS_OCR_MODE=skip disparaît. Les valeurs skip et skip_noarchive sont retirées, et une variable retirée n'est pas honorée en silence : un avertissement est journalisé au démarrage. Conserver le comportement de la v2 demande de répartir l'intention entre deux réglages désormais indépendants :
# v2 : sauter l'OCR si du texte est présent, mais toujours archiver
PAPERLESS_OCR_MODE=skip
# v3 : équivalent
PAPERLESS_OCR_MODE=auto
PAPERLESS_ARCHIVE_FILE_GENERATION=alwaysIncompatibilité Redis → Valkey. Depuis la v3, le compose officiel embarque Valkey comme broker (valkey/valkey:9-alpine), et non plus Redis : ce n'est pas un choix marginal, c'est ce que vous récupérez dès que vous remplacez votre compose par le gabarit du projet. Si votre volume de broker a été créé par une version récente de Redis, Valkey refuse de le charger et le conteneur entre en boucle de redémarrage sur Can't handle RDB format version 15 (ou 13, selon l'origine), pendant que le webserver attend un broker qui ne vient jamais. La solution est de supprimer le volume du broker avant de basculer : il ne contient que des tâches en file, aucune donnée persistante critique.
docker compose down
docker volume rm paperless_redisdata # le préfixe vient de COMPOSE_PROJECT_NAME
docker compose up -dL'index de recherche se reconstruit tout seul. La v3 remplace Whoosh par Tantivy, et le format est incompatible : l'index est régénéré depuis zéro au premier démarrage — ce qui explique un premier lancement plus long. Sous Docker, le conteneur exécute document_index reindex --if-needed à chaque démarrage, donc aucune action manuelle n'est requise. Si malgré tout la consommation échoue sur Schema error: 'An index exists but the schema does not match.' (cas rapporté sur une instance ayant tourné la bêta 3.0), forcez une reconstruction propre :
docker compose exec webserver document_index reindex --recreateAttention aussi à la syntaxe de recherche : note: devient notes.note: et custom_field: devient custom_fields.value:. Les vues enregistrées portant un préfixe explicite sont migrées automatiquement, mais une recherche sans préfixe qui trouvait auparavant le contenu d'une note ne le trouvera plus.
Trois changements de comportement qui surprennent. L'historique des tâches est effacé pendant la mise à jour : les tâches passées, échouées ou acquittées ne réapparaîtront pas. La v3 ne rejette plus les doublons par défaut — elle les accepte et permet de les repérer dans l'interface ; si vous comptiez sur ce rejet, réactivez-le avec PAPERLESS_CONSUMER_DELETE_DUPLICATES=true. Enfin, derrière un reverse proxy, la limitation de débit du login détermine désormais autrement l'IP du client : si la connexion renvoie 403 Forbidden après la mise à jour, déclarez la chaîne avec PAPERLESS_TRUSTED_PROXIES, et au besoin PAPERLESS_ALLAUTH_TRUSTED_PROXY_COUNT — le nombre de sauts dans X-Forwarded-For, qui n'est pas forcément le nombre d'IP configurées.
À propos de MariaDB. Elle reste supportée (PAPERLESS_DBENGINE=mariadb), même si le projet recommande PostgreSQL pour les nouvelles installations. Les échecs de migration sur Debian 12 qui circulent dans les forums ne venaient pas du pilote : ils venaient d'installations bare-metal où la nouvelle version avait été dépaquetée par-dessus l'ancienne. Les fichiers de migration périmés restés sur le disque font échouer manage.py migrate sur un NodeNotFoundError. Le remède est de supprimer l'arbre de sources précédent (src/, static/) avant de déployer, pas de changer de moteur de base de données — et dans un déploiement Docker comme celui de ce guide, ce scénario ne peut pas se produire, puisque l'image est remplacée en bloc.
Le courrier après la mise à jour. La commande qui force la relève du courrier est mail_fetcher, sans argument : elle traite tous les comptes et toutes les règles configurés.
docker compose exec webserver mail_fetcherSi un flux de courrier cesse de produire des documents après la mise à jour, regardez d'abord l'erreur de la tâche dans l'interface : les cas rapportés pointaient vers l'index de recherche (le Schema error ci-dessus), pas vers les identifiants. Vérifiez ensuite que le compte conserve ses autorisations IMAP ; si vous utilisez un jeton OAuth, cochez la case indiquant que le mot de passe est en réalité un jeton.
Avant toute mise à jour majeure, testez la procédure sur une copie de votre environnement. Avec Docker Compose, cela revient à copier votre répertoire /opt/paperless sur un second VPS, à pointer un sous-domaine de test, et à y appliquer la mise à jour. Vous pouvez ensuite valider que vos documents, tags et correspondants sont intacts avant d'intervenir sur la production.
Sauvegarde automatique de Paperless-ngx
Paperless-ngx gère deux types de données critiques : la base de données PostgreSQL (métadonnées, tags, correspondants, règles) et les fichiers documents. Les deux doivent être sauvegardés — l'un sans l'autre ne restaure rien d'utilisable.
Script de sauvegarde quotidien :
#!/bin/bash
BACKUP_DIR="/backups/paperless/$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
# Dump PostgreSQL
docker compose exec -T db pg_dump -U paperless paperless \
| gzip > "$BACKUP_DIR/db.sql.gz"
# Export natif Paperless (inclut config et documents)
docker compose exec -T webserver document_exporter ../export
tar -czf "$BACKUP_DIR/export.tar.gz" /opt/paperless/export/
# Rotation — garder 14 jours
find /backups/paperless -maxdepth 1 -type d -mtime +14 -exec rm -rf {} +
echo "Sauvegarde terminée : $BACKUP_DIR"Planifier ce script en cron (0 3 * * * pour 3h du matin), et vérifier que les backups arrivent bien sur un stockage externe au VPS (rsync vers un bucket S3 ou un autre serveur). Le -T derrière exec évite l'erreur « the input device is not a TTY » quand le script tourne sans terminal.
⚠️ Un export ne remplace pas une sauvegarde de volumes, et ne sert pas de pont entre versions : il ne se réimporte que dans la même version de Paperless-ngx que celle qui l'a produit. Pour une restauration après incident, gardez donc aussi une copie des volumes Docker, ou notez la version exacte à côté de l'export.
Mise à jour et maintenance
Paperless-ngx publie des releases régulières. La mise à jour est simple avec Docker Compose :
# Tirer la nouvelle image
docker compose pull
# Redémarrer les services (les migrations de base de données s'appliquent automatiquement)
docker compose up -d
# Vérifier que tout est OK
docker compose logs webserver --tail=20Avant chaque mise à jour majeure : lire le CHANGELOG sur GitHub — les versions majeures (v2.x → v3.x) peuvent nécessiter des étapes de migration supplémentaires, et parfois une version intermédiaire obligatoire, comme la 2.20.15 avant la v3. La section dédiée ci-dessus détaille les incompatibilités connues de la série 3.x.
Surveillance : Paperless-ngx n'expose pas d'endpoint /metrics à lui. Ce qui existe est Flower, le moniteur des tâches Celery, activé en définissant PAPERLESS_ENABLE_FLOWER ; c'est Flower qui exporte des métriques exploitables par Prometheus, en plus de montrer les tâches en cours, en file et terminées. C'est le bon endroit pour repérer un traitement qui s'arrête en silence — le symptôme d'un timeout OCR, précisément.
⚠️ Un détail qui change après une montée en v3 : l'historique des tâches ayant été effacé par la migration, une liste vide au premier démarrage n'est pas le signe d'une panne. Ce sont les tâches postérieures à la mise à jour qui font foi.