Intermediaire 9 min de lecture · 1 796 mots

Paperless-ngx : zéro papier, tout indexé et cherchable sur ton serveur

Pendant des années, j’ai conservé mes relevés bancaires, mes factures EDF et mes documents administratifs dans une boîte à chaussures. Deux boîtes à chaussures, à vrai dire. Le jour où je n’ai plus retrouvé une attestation d’assurance demandée par mail 48 heures plus tôt, j’ai décidé que c’était fini. J’avais déjà Paperless-ngx sur la liste des services à déployer sur heighliner – le déménagement dans la boîte à chaussures numérique a pris deux soirées.

Contexte et contraintes

Le besoin est simple : ingérer des documents scannés ou reçus en PDF, les indexer par OCR, et les retrouver en quelques secondes via une recherche full-text. Pas besoin d’une GED d’entreprise – juste quelque chose de fiable qui tourne sans supervision.

L’infrastructure cible : LXC 111 sur heighliner (Proxmox VE 8.3), Debian 12, 2 vCPU, 2 GB RAM, Docker installé à l’intérieur. Le stockage des documents originaux passe par le NAS Synology DS414 via NFS – le partage est monté à /mnt/synology/paperless dans le LXC, et c’est depuis là que le dossier de consommation est exposé.

La stack retenue :

  • Paperless-ngx 2.x – le service principal, OCR + indexation + interface web
  • Gotenberg – conversion DOCX, PPTX, XLSX en PDF avant ingestion
  • Tika – extraction de texte depuis les formats Office sans passer par Gotenberg
  • Redis – broker de tâches pour les workers OCR
  • PostgreSQL 15 – base de données principale (préféré à SQLite au-delà de quelques centaines de documents)
  • Montage NFS et structure de répertoires

    Avant de toucher à Docker, je configure le montage NFS dans /etc/fstab du LXC :

192.168.40.10:/volume1/paperless  /mnt/synology/paperless  nfs  defaults,_netdev,rw  0  0

Le VLAN Stockage (40) est celui utilisé pour joindre le DS414 depuis les LXC et VMs. L’adresse 192.168.40.10 est l’IP fixe du NAS sur ce segment.

Structure locale dans le LXC :

/opt/paperless/
├── consume/          -> /mnt/synology/paperless/consume  (symlink ou bind mount)
├── data/             -> /mnt/synology/paperless/data
├── media/            -> /mnt/synology/paperless/media
├── export/           -> /mnt/synology/paperless/export
└── docker-compose.yml

Le répertoire consume/ est surveillé en continu par Paperless. Tout fichier déposé là est ingéré et supprimé après traitement. Ne jamais y poser un fichier qui n’est pas destiné à l’import.

Docker Compose

version: "3.9"

services:
  broker:
    image: redis:7
    restart: unless-stopped
    volumes:
      - redisdata:/data

  db:
    image: postgres:15
    restart: unless-stopped
    environment:
      POSTGRES_DB: paperless
      POSTGRES_USER: paperless
      POSTGRES_PASSWORD_FILE: /run/secrets/pg_password
    volumes:
      - pgdata:/var/lib/postgresql/data
    secrets:
      - pg_password

  gotenberg:
    image: gotenberg/gotenberg:8
    restart: unless-stopped
    command:
      - gotenberg
      - --chromium-disable-javascript=true
      - --chromium-allow-list=file:///tmp/.*

  tika:
    image: ghcr.io/paperless-ngx/tika:latest
    restart: unless-stopped

  webserver:
    image: ghcr.io/paperless-ngx/paperless-ngx:2
    restart: unless-stopped
    depends_on:
      - db
      - broker
      - gotenberg
      - tika
    ports:
      - "8010:8000"
    volumes:
      - /opt/paperless/data:/usr/src/paperless/data
      - /opt/paperless/media:/usr/src/paperless/media
      - /opt/paperless/export:/usr/src/paperless/export
      - /opt/paperless/consume:/usr/src/paperless/consume
    environment:
      PAPERLESS_REDIS: redis://broker:6379
      PAPERLESS_DBHOST: db
      PAPERLESS_DBNAME: paperless
      PAPERLESS_DBUSER: paperless
      PAPERLESS_DBPASS_FILE: /run/secrets/pg_password
      PAPERLESS_TIKA_ENABLED: 1
      PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
      PAPERLESS_TIKA_ENDPOINT: http://tika:9998
      PAPERLESS_OCR_LANGUAGE: fra+eng
      PAPERLESS_OCR_MODE: skip_noarchive
      PAPERLESS_TIME_ZONE: Europe/Paris
      PAPERLESS_FILENAME_FORMAT: "{created_year}/{correspondent}/{title}"
      USERMAP_UID: 1000
      USERMAP_GID: 1000
    secrets:
      - pg_password

volumes:
  redisdata:
  pgdata:

secrets:
  pg_password:
    file: ./secrets/pg_password.txt

PAPERLESS_OCR_LANGUAGE: fra+eng charge les modèles Tesseract pour le français et l’anglais. PAPERLESS_OCR_MODE: skip_noarchive évite de ré-OCRiser les PDF qui ont déjà une couche texte – gain de temps significatif sur les factures reçues directement en PDF depuis les portails en ligne.

Lancement :

docker compose -f /opt/paperless/docker-compose.yml up -d
docker compose -f /opt/paperless/docker-compose.yml exec webserver createsuperuser

L’interface web est disponible sur http://192.168.30.111:8010 depuis le VLAN Serveurs. Traefik sur LXC 103 expose le service via HTTPS avec un certificat Let’s Encrypt – paperless.arewel.com pointe dessus.

Types de documents et correspondants

Avant d’importer quoi que ce soit, je structure les métadonnées depuis l’interface Settings :

Types de documents créés :

  • Facture
  • Relevé bancaire
  • Contrat
  • Administratif
  • Santé
  • Correspondants créés :

  • EDF
  • Banque (nom de l’établissement masqué ici)
  • Impôts
  • Assurance
  • Médecin
  • Ces entrées sont ensuite utilisées par les règles de correspondance automatiques.

    Règles de correspondance automatiques

    C’est la partie la plus utile – et celle qui demande le plus de tâtonnements. Paperless-ngx peut analyser le texte OCRisé d’un document et appliquer automatiquement des métadonnées si certaines conditions sont remplies.

    Configuration depuis Settings > Matching Algorithms :

    Règle « EDF »

  • Critère : contient le mot EDF OU Électricité de France
  • Action : Type = Facture, Tag = Énergie, Correspondant = EDF
  • Règle « Relevé bancaire »

  • Critère : contient IBAN ET (débit OU crédit OU solde)
  • Action : Type = Relevé bancaire
  • Règle « Fiscal »

  • Critère : contient Déclaration ET impôts (OU Direction générale des finances)
  • Action : Tag = Fiscal, Correspondant = Impôts
  • Règle « Santé »

  • Critère : contient Sécurité sociale OU Assurance maladie OU ordonnance
  • Action : Type = Santé, Tag = Santé
  • Ces règles fonctionnent en post-traitement OCR. Elles ne sont pas infaillibles – un document mal OCRisé passera au travers – mais sur mes 340 documents importés, environ 280 ont été classés automatiquement sans intervention manuelle.

    Commence par importer une dizaine de documents manuellement, observe les textes OCRisés dans l’interface (onglet « Content » d’un document), et construis les règles sur ce que Tesseract produit réellement – pas sur ce que tu penses que le texte devrait être.

    Workflow d’import

    Deux méthodes utilisées en parallèle :

    Import via le scanner Brother (ADF)

    Mon imprimante Brother avec chargeur automatique génère des PDF à 300 DPI directement. Je configure un dossier de numérisation réseau sur le DS414 via SMB, puis un script cron dans le LXC copie les nouveaux fichiers vers /opt/paperless/consume/ toutes les 5 minutes :

    #!/bin/bash
    rsync -av /mnt/synology/scan-inbox/ /opt/paperless/consume/ 
      --remove-source-files 
      --include="*.pdf" 
      --exclude="*"
    

    Paperless détecte l’arrivée du fichier, lance l’OCR dans les secondes qui suivent, et supprime le fichier source après ingestion réussie.

    Import manuel de l’existant

    Pour la boîte à chaussures historique, j’ai scanné les documents un par un avec l’ADF puis déposé les PDFs dans le dossier consume. 340 documents sur deux soirées, en triant physiquement au fur et à mesure.

    Ce qui a coincé : l’OCR sur les vieux scans

    Le premier lot de documents que j’ai tenté d’importer était une pile de factures EDF des années 2015-2019, scannées à l’époque avec un scanner à plat réglé par défaut à 100 DPI. Résultat dans Paperless : du texte illisible, des caractères aléatoires, aucune règle de correspondance déclenchée.

    Tesseract a besoin d’un minimum de 200 DPI pour produire un OCR exploitable – 300 DPI est la valeur recommandée. En dessous, le modèle de reconnaissance de caractères ne dispose pas de suffisamment d’information pour distinguer un l d’un 1 ou un O d’un 0.

    J’ai tenté une amélioration par prétraitement avec ImageMagick pour remonter la résolution artificielle à 300 DPI :

    convert -density 300 -units PixelsPerInch input.pdf output-upscaled.pdf
    

    Sur certains documents, ça améliore marginalement le résultat. Sur d’autres, non. La conclusion pratique : les documents scannés avant 2020 avec du matériel grand public restent partiellement illisibles par OCR. Je les ai tagués manuellement ocr-faible et j’ai accepté la limite.

    Les scans récents depuis l’ADF Brother à 300 DPI, eux, donnent des résultats parfaits – Tesseract reconnaît le texte imprimé avec un taux d’erreur quasiment nul sur des documents typiques (polices sans-serif, fond blanc).

    Recherche full-text et API REST

    L’indexeur Whoosh intégré à Paperless-ngx construit un index full-text sur l’ensemble des contenus OCRisés. La recherche dans l’interface web est quasi instantanée même avec 340 documents.

    Quelques requêtes types que j’utilise :

  • EDF 2024 – toutes les factures EDF de l’année
  • IBAN solde – relevés bancaires
  • type:Santé date:[2025 TO 2026] – documents médicaux sur les 18 derniers mois
  • L’API REST est disponible sur /api/ avec authentification par token. Je l’utilise depuis une automatisation Home Assistant pour vérifier si une facture donnée a été importée ce mois-ci. Exemple :

    curl -H "Authorization: Token " 
      "https://paperless.arewel.com/api/documents/?correspondent__name=EDF&created__date__gte=2026-01-01"
    

    Le token d’API Paperless donne un accès complet à tous les documents. Ne le stocker ni dans un fichier de config en clair ni dans un dépôt Git.

    Résultats mesurés

    Après deux soirées d’import (dont une consacrée à scanner physiquement les vieilles factures) :

  • 340 documents importés et indexés
  • Stockage total : 1,2 GB (documents originaux + thumbnails + base PostgreSQL)
  • Temps de recherche observé : inférieur à 5 secondes dans tous les cas testés
  • Taux de classification automatique : environ 82 % des documents sans intervention manuelle
  • Documents avec OCR dégradé (scans anciens à 100 DPI) : environ 40, tous tagués ocr-faible
  • La RAM du LXC reste confortable : en charge normale (aucun document en cours d’import), les conteneurs consomment environ 600 MB au total. Pendant l’OCR d’un PDF de 10 pages, ça monte à 900 MB – dans les limites allouées.

    Sauvegarde

    Paperless dispose d’une commande d’export qui produit un ZIP contenant les documents originaux, la base de données et les thumbnails :

    docker compose -f /opt/paperless/docker-compose.yml exec webserver 
      document_exporter /usr/src/paperless/export --use-filename-format
    

    Ce répertoire /opt/paperless/export/ est inclus dans le job Proxmox Backup Server qui sauvegarde le LXC 111 chaque nuit. En plus, une copie hebdomadaire du répertoire data/ et media/ va directement sur le DS414 via un script rsync – au cas où PBS serait lui-même inaccessible.

    L’étape suivante est d’automatiser l’export ZIP de Paperless dans un job cron et de le faire versionner sur le NAS – pour avoir un historique des états de la base, indépendant de la sauvegarde LXC complète.


    slug: paperless-ngx-gestion-documents
    meta: Paperless-ngx 2.x sur LXC Proxmox : OCR Tesseract fr+en, dossier consume NFS Synology, règles automatiques, 340 documents indexés en deux soirées. (~154 chars)
    tags: paperless-ngx, OCR, GED, Proxmox, Docker, Tesseract, NAS, Synology, homelab

    Une remarque, un retour ?

    Cet article est vivant - corrections, contre-arguments et retours de production sont les bienvenus. Trois canaux, choisissez celui qui vous convient.

    Laisser un commentaire