Installer Paperless-ngx sur un VPS avec Docker Compose
Installez Paperless-ngx sur un VPS avec Docker Compose : stack PostgreSQL officielle, PAPERLESS_URL, dossier consume, langues OCR, HTTPS et sauvegardes.
Ce que vous allez construire
Paperless-ngx sur un VPS transforme un dossier de documents numérisés en archive interrogeable. Vous déposez un PDF dans un répertoire surveillé. Le serveur exécute une OCR (reconnaissance optique de caractères), en extrait le texte, déduit une date et un correspondant, puis le classe. L’installation repose sur un seul fichier Docker Compose avec quatre services. Tout le reste concerne la configuration. Ce guide lui consacre l’essentiel de son contenu, car c’est à cette étape que les installations échouent. Paperless-ngx n’est pas une photothèque : l’OCR et la déduction du correspondant ne servent à rien pour un dossier de JPEG de vacances. Placez plutôt ces fichiers dans un serveur de photos adapté et réservez Paperless aux documents. La même distinction s’applique aux vidéos : une collection de films extraits appartient à un serveur multimédia, où une interface comme Jellyfin présentée comme un vidéoclub des années 90 met l’accent sur la navigation plutôt que sur la recherche.
Paperless-ngx est le fork communautaire maintenu du projet Paperless d’origine. Il est gratuit, auto-hébergé et stocke vos documents sous forme de fichiers classiques sur le disque. Vous ne perdez donc jamais l’accès à votre propre archive. L’exécuter sur un VPS plutôt que sur un ordinateur à domicile permet d’accéder à vos numérisations depuis n’importe où, sans ouvrir de port sur le routeur de votre domicile. Il s’associe bien à une instance Nextcloud privée pour les fichiers qui ne sont pas sur papier. Le même principe s’applique à l’ordinateur auquel votre scanner est connecté : un relais RustDesk que vous gérez sur ce VPS vous permet de piloter cette machine à distance, là encore sans ouvrir de port sur le routeur.
Ce que la stack exécute réellement
Le fichier Compose officiel démarre quatre conteneurs. Comprendre le rôle de chacun facilite la lecture des journaux.
webserver: l’image paperless-ngx elle-même. Elle exécute l’interface web, l’API, le consumer qui surveille votre dossier d’entrée et les workers de tâches Celery qui effectuent l’OCR.db: PostgreSQL. Il stocke les métadonnées, les tags, les correspondants et les tables d’indexation de la recherche en texte intégral. Il ne stocke pas vos fichiers PDF.broker: Valkey, un magasin clé-valeur compatible avec Redis. Il fournit la file de tâches entre le processus web et les workers.gotenbergettika: facultatifs, uniquement dans les variantes Compose-tika. Ils convertissent les documents Office (.docx,.xlsx,.odt) en PDF afin que paperless puisse les indexer.
En octobre 2026, le fichier Compose postgres verrouille docker.io/library/postgres:18 et docker.io/valkey/valkey:9-alpine, et récupère l’application depuis ghcr.io/paperless-ngx/paperless-ngx:latest.
Prérequis
- Un VPS KVM Ubuntu 24.04 avec un accès sudo, ainsi que Docker et le plugin Compose déjà installés. Si cette partie est nouvelle pour vous, commencez par les bases de Docker Compose pour un VPS, puis revenez ici.
- Un nom de domaine avec un enregistrement A pointant vers le VPS. Paperless n’acceptera pas une connexion HTTPS sur un nom d’hôte qui ne lui a pas été indiqué. Ce point intervient donc plus tôt que prévu.
- La mémoire est la principale contrainte. PostgreSQL, Valkey, le serveur web Granian et un worker Tesseract OCR peuvent fonctionner simultanément dans 2 GB pour un usage léger. Prévoyez 4 GB si vous comptez importer un stock de plusieurs centaines de scans. L’OCR d’un PDF multipage volumineux provoque le pic de mémoire qui peut faire tuer un worker par l’oom killer du noyau.
- Disque : vos archives sont stockées deux fois, sous forme de fichier original et de PDF d’archive avec OCR. Prévoyez donc environ deux fois la taille de vos scans.
Récupérer les fichiers Compose officiels
Il existe un installateur interactif :
bash -c "$(curl --location --silent --show-error https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"Il pose des questions et écrit les fichiers à votre place. Le faire manuellement prend quatre commandes et vous permet de savoir où se trouve chaque élément. C’est préférable sur un serveur que vous devrez administrer.
mkdir -p ~/paperless && cd ~/paperless
curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/docker-compose.postgres.yml
curl -fsSL -o docker-compose.env https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/docker-compose.env
curl -fsSL -o .env https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/.envLes variantes se trouvent dans le même répertoire : docker-compose.sqlite.yml, docker-compose.mariadb.yml et une version -tika de chacune. Choisissez postgres pour une nouvelle installation. SQLite convient pour quelques centaines de documents, mais l’index de recherche en texte intégral ralentit bien avant PostgreSQL.
Le fichier .env contient une ligne : COMPOSE_PROJECT_NAME=paperless. Ce nom devient le préfixe de chaque conteneur et volume. Ne le supprimez donc pas avant de vous demander pourquoi docker compose down -v ne trouve plus vos données.
Configurer docker-compose.env avant le premier démarrage
Deux paramètres sont obligatoires. Générez la clé secrète avec la commande documentée par le projet :
python3 -c "import secrets; print(secrets.token_urlsafe(64))"Modifiez ensuite docker-compose.env :
PAPERLESS_SECRET_KEY=<the long string you just generated>
PAPERLESS_URL=https://paperless.example.com
PAPERLESS_TIME_ZONE=Europe/Berlin
PAPERLESS_OCR_LANGUAGE=deu+eng
USERMAP_UID=1000
USERMAP_GID=1000PAPERLESS_SECRET_KEY est fourni avec la valeur littérale change-me. Cette clé signe les jetons de session. Les versions actuelles considèrent cet espace réservé comme non défini : paperless refuse de démarrer si la clé est absente, vide ou contient encore change-me. La stack ne démarrera donc pas tant que vous ne l’aurez pas remplacée. Définissez-la avant le premier démarrage, car sa modification ultérieure déconnecte tous les utilisateurs.
PAPERLESS_URL est le paramètre qui vous évite une heure de diagnostic. Paperless est une application Django. Django vérifie à la fois l’en-tête Host et, sur un formulaire de connexion, l’origine indiquée par le navigateur. Définissez PAPERLESS_URL. L’application ajoute alors votre adresse à ALLOWED_HOSTS, CORS_ALLOWED_HOSTS et CSRF_TRUSTED_ORIGINS. Si vous le laissez vide et associez un domaine au serveur, la page de connexion se charge toujours, car PAPERLESS_ALLOWED_HOSTS prend par défaut la valeur *. En revanche, son envoi via HTTPS échoue avec CSRF verification failed. Request aborted., car votre domaine n’est pas une origine approuvée. Indiquez la valeur sans slash final ni chemin.
USERMAP_UID et USERMAP_GID définissent l’utilisateur avec lequel le conteneur s’exécute. Faites-les correspondre à votre propre compte, que vous pouvez vérifier avec id -u et id -g. S’ils ne correspondent pas, les fichiers que vous copiez dans le dossier consume ne sont pas lisibles par le consumer. Le journal affiche alors une erreur de permission au lieu de lancer l’importation.
Démarrez la stack et créez le premier utilisateur
docker compose pull
docker compose up -d
docker compose run --rm webserver createsuperuser
docker compose logs -f webservercreatesuperuser demande un nom d’utilisateur, une adresse e-mail et un mot de passe. Il n’existe pas d’identifiants par défaut. Si vous ignorez cette étape, l’interface web demande à la première personne qui l’ouvre de créer le superutilisateur. Tant que le port 8000 est exposé à Internet, cette personne ne sera peut-être pas vous. Créez donc immédiatement le compte depuis le shell. Attendez la ligne du journal indiquant que le serveur écoute sur le port 8000 avant d’ouvrir le navigateur. Le tout premier démarrage exécute également les migrations de base de données, ce qui prend une à deux minutes.
Vérifiez d’abord le service localement, avant de configurer un domaine :
curl -I http://127.0.0.1:8000Une redirection 302 vers /accounts/login/ indique que la stack fonctionne correctement.
Placer le service derrière HTTPS
Le fichier Compose fourni publie 8000:8000, qui est lié à toutes les interfaces. Sur un VPS public, cela expose l’intégralité de votre archive de documents en HTTP non chiffré à toute personne qui trouve l’adresse. Modifiez la ligne du port pour la lier uniquement à loopback :
ports:
- "127.0.0.1:8000:8000"Terminez ensuite TLS (transport layer security) dans un reverse proxy et transférez les requêtes vers 127.0.0.1:8000. Si cette application est la seule à fonctionner sur le serveur, n’importe quel proxy avec un client ACME (automatic certificate management environment) convient. Si plusieurs conteneurs utilisent une même configuration de certificats, suivez le modèle de reverse proxy Traefik pour plusieurs applications Docker Compose et rattachez le service webserver au réseau du proxy, sans publier de port.
Quel que soit le proxy utilisé, c’est la vérification de l’origine du formulaire de connexion qui pose problème dans les configurations HTTPS. Paperless ignore X-Forwarded-Proto tant que vous ne définissez pas PAPERLESS_PROXY_SSL_HEADER, qui est désactivé par défaut. Django traite donc chaque requête proxifiée comme une requête HTTP, et l’origine https:// envoyée par votre navigateur n’est acceptée que si elle figure dans la liste des origines approuvées. La définition de PAPERLESS_URL avec l’adresse https:// exacte que vous saisissez dans le navigateur ajoute cette origine à la liste. Sans ce réglage, vous obtenez CSRF verification failed. Request aborted. sur une page qui semble correcte.
Augmentez également la limite de taille des uploads du proxy. Un scan de 40 MB envoyé via un proxy qui limite les corps de requête à 1 MB est rejeté avant même que Paperless le reçoive, et le navigateur affiche une erreur d’upload générique.
Fonctionnement du répertoire consume
Le fichier compose monte ./consume dans le conteneur avec un bind mount. Tout fichier que vous y placez est importé, puis supprimé du répertoire, car il se trouve désormais dans le volume media géré par paperless.
cp ~/scan-2026-07-14.pdf ~/paperless/consume/
docker compose logs -f webserverVous devriez voir le consumer détecter le nom du fichier, exécuter l’OCR, puis terminer par une ligne indiquant que le document a été ajouté. Pour un scan d’une page, le cycle complet prend quelques secondes. Pour un document long, il peut prendre une minute ou davantage.
Deux paramètres modifient la manière dont les fichiers sont détectés. PAPERLESS_CONSUMER_RECURSIVE=true demande à paperless de rechercher les fichiers dans les sous-répertoires. PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=true transforme chaque nom de sous-répertoire en tag. Ainsi, déposer un fichier dans consume/invoices/2026/ lui applique les tags invoices et 2026. Vous ne trouverez jamais de système de classement moins coûteux.
La détection constitue l’autre aspect. Par défaut, PAPERLESS_CONSUMER_POLLING_INTERVAL vaut 0. Paperless utilise donc les notifications natives du système de fichiers, qui sont immédiates. Ces notifications ne sont pas fiables avec un système de fichiers réseau. Si votre répertoire consume est un partage NFS ou SMB dans lequel un scanner réseau peut écrire, certains fichiers peuvent rester sans être détectés. Pour corriger ce problème, définissez l’intervalle sur un nombre positif de secondes afin que paperless analyse le répertoire à la place.
Langues OCR et coût associé
PAPERLESS_OCR_LANGUAGE accepte un code Tesseract de trois lettres, avec eng par défaut. Combinez les langues avec un signe plus, comme dans deu+eng. Tesseract traite alors chaque page avec toutes les langues indiquées. Chaque langue supplémentaire augmente donc le temps CPU de chaque analyse. Sur un VPS avec vCPU partagé, cela peut faire passer la durée d’une analyse de dix secondes à une minute. Indiquez uniquement les langues réellement présentes dans vos documents.
L’image inclut l’anglais, l’allemand, l’italien, l’espagnol et le français. Pour toute autre langue, ajoutez-la à PAPERLESS_OCR_LANGUAGES sous forme de liste séparée par des espaces, par exemple PAPERLESS_OCR_LANGUAGES=tur ces, puis redémarrez. Le conteneur télécharge les packs de données Tesseract au démarrage. Le premier démarrage après cette modification est donc plus lent.
Sauvegarder la base de données et les fichiers multimédias
Copier les volumes Docker pendant l’exécution de PostgreSQL produit une sauvegarde qui peut être impossible à restaurer. Paperless fournit son propre exporteur. Celui-ci écrit les documents ainsi qu’un manifeste JSON contenant toutes les métadonnées dans le bind mount ./export :
docker compose exec -T webserver document_exporter ../export --delete --no-progress-bar--delete supprime les fichiers exportés qui ne correspondent plus à un document actuel. Le dossier reste ainsi un miroir au lieu de grossir indéfiniment. --no-progress-bar garde la sortie propre lorsque la commande est exécutée par cron, et -T empêche Compose de demander un terminal que cron ne possède pas.
La restauration s’effectue document_importer depuis ce même dossier sur une stack vierge. Le répertoire d’export est donc le seul élément à conserver en sécurité. Envoyez-le hors site selon un calendrier avec des sauvegardes restic chiffrées et dédupliquées depuis votre VPS, et exécutez d’abord l’export afin que restic ne capture jamais une archive partiellement écrite.
Vérifiez une sauvegarde en contrôlant que export/manifest.json existe et que le nombre de fichiers correspond au nombre de documents affiché dans l’interface. Une sauvegarde que vous n’avez jamais listée n’est pas une sauvegarde. Un export nocturne qui cesse silencieusement de fonctionner est encore plus problématique. Faites donc transmettre par la tâche cron son code de sortie à votre propre serveur ntfy. Vous découvrirez ainsi la panne durant la semaine où elle survient, plutôt que le jour où vous devez restaurer.
FAQ
Pourquoi chaque page renvoie-t-elle « Bad Request (400) » après avoir associé mon domaine à l’application ?
Avec les fichiers par défaut, cela ne devrait pas se produire, car PAPERLESS_ALLOWED_HOSTS est défini par défaut sur *. Un 400 avec DisallowedHost dans le journal du conteneur signifie que PAPERLESS_ALLOWED_HOSTS a été défini sur une liste qui exclut votre domaine. Définissez PAPERLESS_URL=https://paperless.example.com dans docker-compose.env, sans slash final, et paperless ajoute ce nom d’hôte à la liste, ou supprimez la ligne PAPERLESS_ALLOWED_HOSTS. Exécutez ensuite docker compose up -d pour recréer le conteneur. Modifier uniquement le fichier d’environnement ne suffit pas, car le conteneur en cours d’exécution conserve l’environnement avec lequel il a été démarré.
J’ai déposé un PDF dans le dossier consume et rien ne s’est produit. Quel est le problème ?
Vérifiez d’abord docker compose logs webserver. Une erreur de permission signifie que USERMAP_UID et USERMAP_GID ne correspondent pas au compte propriétaire du fichier. Corrigez-les, puis recréez le conteneur. L’absence totale de ligne dans le journal signifie que l’événement de fichier n’est jamais arrivé. Cela se produit sur les partages réseau, car les notifications natives du système de fichiers n’y sont pas fiables. Définissez PAPERLESS_CONSUMER_POLLING_INTERVAL sur une valeur telle que 30. paperless analysera alors le dossier toutes les 30 secondes.
Puis-je utiliser paperless-ngx avec SQLite au lieu de PostgreSQL ?
Oui, docker-compose.sqlite.yml est pris en charge et utilise moins de mémoire, ce qui convient à un petit VPS. Le compromis devient visible lorsque l’archive grossit : la recherche en texte intégral et les modifications groupées des tags ralentissent sensiblement lorsque le nombre de documents atteint plusieurs milliers. Une migration ultérieure nécessite un export et un import. Choisissez donc PostgreSQL dès maintenant si vous prévoyez de continuer à faire croître l’archive.
Quel espace disque une archive de scans nécessite-t-elle réellement ?
Comptez environ deux fois la taille de vos fichiers source. Paperless conserve l’original sans le modifier et stocke un second PDF traité par OCR avec une couche de texte interrogeable, ainsi que de petites vignettes. Un scan de 200 KB contenant uniquement du texte reste peu volumineux. Un scan couleur de 30 MB d’un long contrat occupe environ 60 MB. Ajoutez le répertoire d’export si vous le conservez sur le même disque : la même archive occupe alors trois fois sa taille initiale sur le disque.
Ai-je besoin des conteneurs Tika et Gotenberg ?
Seulement si vous voulez indexer des fichiers Word, Excel ou OpenDocument avec vos PDF. Ils convertissent ces formats en PDF afin que paperless puisse les traiter par OCR et les rechercher. Ils ajoutent aussi deux conteneurs supplémentaires en cours d’exécution et quelques centaines de mégaoctets de mémoire. Vous pouvez donc vous en passer sur une petite machine si tous les fichiers que vous archivez sont déjà des PDF ou des images.