SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-28

Paperless-ngx su VPS: archivia i documenti con Docker

Configura Paperless-ngx su un VPS con Docker Compose: stack Postgres ufficiale, PAPERLESS_URL, cartella consume, lingue OCR, HTTPS e backup affidabili.

Cosa stai creando

Paperless-ngx su un VPS trasforma una cartella di documenti cartacei scansionati in un archivio consultabile. Inserisci un PDF in una directory monitorata; il server esegue l'OCR (riconoscimento ottico dei caratteri), estrae il testo, stima una data e un corrispondente, quindi archivia il documento. L'installazione consiste in un unico file Docker Compose con quattro servizi. Tutto il resto riguarda la configurazione. Questa guida dedica gran parte del contenuto a questa fase, perché è il punto in cui le installazioni presentano più problemi. Paperless-ngx non è una libreria fotografica: l'OCR e la stima del corrispondente non servono per una cartella di immagini JPEG delle vacanze. Per quelle immagini usa un server fotografico progettato per questo scopo e riserva Paperless ai documenti cartacei.

Paperless-ngx è il fork della community mantenuto del progetto Paperless originale. È gratuito, self-hosted e archivia i documenti come file normali su disco, quindi non si perde mai l’accesso al proprio archivio. Eseguirlo su un VPS invece che su un computer di casa rende le scansioni accessibili da qualsiasi luogo senza aprire una porta sul router domestico e si integra bene con un’istanza privata di Nextcloud per i file che non sono cartacei. Lo stesso principio vale per il computer desktop a cui è collegato lo scanner, perché un relay RustDesk personale su quel VPS consente di controllare la macchina da remoto senza dover aprire una porta sul router.

Cosa esegue realmente lo stack

Il file Compose ufficiale avvia quattro container. Sapere quale funzione svolge ciascuno di essi aiuta a interpretare i log.

  • webserver: l’immagine di paperless-ngx. Esegue l’interfaccia web, l’API, il consumer che monitora la cartella di input e i worker delle attività Celery che eseguono l’OCR.
  • db: PostgreSQL. Contiene metadati, tag, corrispondenti e le tabelle dell’indice per la ricerca full-text. Non contiene i file PDF.
  • broker: Valkey, un archivio chiave-valore compatibile con Redis. Funziona da coda delle attività tra il processo web e i worker.
  • gotenberg e tika: opzionali, presenti solo nelle varianti Compose -tika. Convertono i documenti Office (.docx, .xlsx, .odt) in PDF, in modo che paperless possa indicizzarli.

A luglio 2026 il file Compose per PostgreSQL blocca le versioni docker.io/library/postgres:18 e docker.io/valkey/valkey:9-alpine e scarica l’applicazione da ghcr.io/paperless-ngx/paperless-ngx:latest.

Prerequisiti

  • Un VPS KVM con Ubuntu 24.04, accesso sudo e Docker con il plugin Compose già installato. Se questa parte è nuova, inizia da i fondamenti di Docker Compose per un VPS e poi torna qui.
  • Un nome di dominio con un record A che punta al VPS. Paperless rifiuta di rispondere su un hostname che non è stato configurato, quindi questo requisito è importante prima di quanto si possa pensare.
  • La memoria è il vero limite. PostgreSQL, Valkey, gunicorn e un worker Tesseract OCR residenti contemporaneamente rientrano in 2 GB per un uso leggero. Assegna 4 GB se prevedi di importare un arretrato di centinaia di scansioni, perché l'OCR di un PDF multipagina di grandi dimensioni può causare il picco di memoria che porta il kernel a terminare un worker tramite l'out-of-memory killer.
  • Spazio su disco: l'archivio viene memorizzato due volte, come file originale e come PDF di archivio con OCR, quindi considera uno spazio pari a circa il doppio delle dimensioni delle scansioni.

Ottenere i file compose ufficiali

È disponibile un programma di installazione interattivo:

bash -c "$(curl --location --silent --show-error https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"

Pone alcune domande e scrive i file al posto tuo. Eseguirlo manualmente richiede quattro comandi e ti permette di sapere dove si trova ogni elemento. Questo è ciò che serve su un server che dovrai amministrare.

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/.env

Le varianti si trovano nella stessa directory: docker-compose.sqlite.yml, docker-compose.mariadb.yml e una versione -tika per ciascuna di esse. Per una nuova installazione, scegli postgres. SQLite è adatto per qualche centinaio di documenti, ma l'indice per la ricerca full-text diventa lento molto prima di PostgreSQL.

Il file .env contiene una riga, COMPOSE_PROJECT_NAME=paperless. Questo nome diventa il prefisso di ogni container e volume. Non eliminarlo per poi chiederti perché docker compose down -v non riesce a trovare i tuoi dati.

Configurare docker-compose.env prima del primo avvio

Due impostazioni sono obbligatorie. Generare la chiave segreta con il comando documentato dal progetto:

python3 -c "import secrets; print(secrets.token_urlsafe(64))"

Modificare quindi 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=1000

PAPERLESS_SECRET_KEY viene fornito con il valore letterale change-me. Firma i cookie di sessione. Se si lascia il valore predefinito, chiunque lo conosca può falsificare una sessione. Impostarlo prima del primo avvio, perché modificarlo in seguito disconnette tutti gli utenti.

PAPERLESS_URL è l'impostazione che consente di risparmiare un'ora di lavoro. Paperless è un'applicazione Django e Django convalida l'header Host di ogni richiesta. Impostare PAPERLESS_URL: in questo modo vengono compilati automaticamente ALLOWED_HOSTS, CORS_ALLOWED_HOSTS e CSRF_TRUSTED_ORIGINS. Se lo si lascia vuoto, si associa un dominio al server e ogni pagina restituisce Bad Request (400), mentre nel log del container compare DisallowedHost. Specificarlo senza slash finale e senza percorso.

USERMAP_UID e USERMAP_GID impostano l'utente con cui viene eseguito il container. Impostarli in base al proprio account, verificato con id -u e id -g. Se i valori non corrispondono, i file copiati nella cartella consume non sono leggibili dal consumer e il log mostra un errore di permessi invece di avviare un'importazione.

Avvia lo stack e crea il primo utente

docker compose pull
docker compose up -d
docker compose run --rm webserver createsuperuser
docker compose logs -f webserver

createsuperuser richiede un nome utente, un indirizzo email e una password. Non esiste un accesso predefinito. Se salti questo passaggio, visualizzi una pagina di accesso che non accetterà mai alcuna credenziale. Attendi la riga del log che indica che il server è in ascolto sulla porta 8000 prima di provare ad accedere dal browser. Al primo avvio vengono eseguite anche le migrazioni del database, che richiedono uno o due minuti.

Verifica il servizio localmente prima di configurare un dominio:

curl -I http://127.0.0.1:8000

Un reindirizzamento da 302 a /accounts/login/ indica che lo stack funziona correttamente.

Metti HTTPS davanti all'applicazione

Il file Compose predefinito pubblica 8000:8000, associandolo a tutte le interfacce. Su un VPS pubblico, questo espone l'intero archivio di documenti tramite HTTP non cifrato a chiunque trovi l'indirizzo. Modifica la riga della porta in modo che sia associata soltanto al loopback:

    ports:
      - "127.0.0.1:8000:8000"

Termina quindi TLS (Transport Layer Security) in un reverse proxy e inoltra le richieste a 127.0.0.1:8000. Se questa è l'unica applicazione sul server, va bene qualsiasi proxy con un client ACME (Automatic Certificate Management Environment). Se esegui più container dietro un'unica configurazione dei certificati, segui il modello di reverse proxy Traefik per più applicazioni Docker Compose e collega il servizio webserver alla rete del proxy senza pubblicare alcuna porta.

Qualunque proxy utilizzi, deve inviare X-Forwarded-Proto: https. Senza questa intestazione, Django considera la richiesta come arrivata tramite HTTP, il controllo dell'origine nel modulo di accesso non riesce e viene visualizzato CSRF verification failed. Request aborted. in una pagina che appare corretta. L'altra parte della correzione consiste nell'impostare PAPERLESS_URL sull'indirizzo https:// esatto che digiti nel browser.

Aumenta inoltre il limite delle dimensioni degli upload nel proxy. Una scansione da 40 MB attraversa un proxy che limita il corpo delle richieste a 1 MB viene rifiutata prima che paperless possa riceverla, e il browser segnala un errore generico di caricamento.

Come funziona la directory consume

Il file compose monta ./consume dalla directory compose nel container tramite un bind mount. Tutto ciò che vi inserisci viene importato e poi eliminato dalla directory, perché il file risiede ora nel volume media gestito da paperless.

cp ~/scan-2026-07-14.pdf ~/paperless/consume/
docker compose logs -f webserver

Dovresti vedere il consumer acquisire il nome del file, eseguire l'OCR e terminare con una riga che indica che il documento è stato aggiunto. Per una scansione di una pagina, l'intero ciclo richiede pochi secondi. Per un documento lungo, può richiedere un minuto o più.

Due impostazioni modificano il modo in cui vengono individuati i file. PAPERLESS_CONSUMER_RECURSIVE=true fa cercare a paperless anche nelle sottodirectory, mentre PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=true converte il nome di ogni sottodirectory in un tag. Di conseguenza, inserire un file in consume/invoices/2026/ applica i tag invoices e 2026. È il sistema di archiviazione più economico che si possa creare.

Il rilevamento è l'altra parte del processo. Per impostazione predefinita, PAPERLESS_CONSUMER_POLLING_INTERVAL è 0. Paperless usa quindi le notifiche del filesystem del kernel, che vengono generate immediatamente. Queste notifiche non attraversano un filesystem di rete. Se la directory consume è una condivisione NFS o SMB in cui uno scanner di rete può scrivere, i file non vengono mai rilevati. Per risolvere il problema, imposta l'intervallo su un numero positivo di secondi, in modo che paperless esegua la scansione della directory.

Lingue OCR e relativo costo

PAPERLESS_OCR_LANGUAGE accetta il codice Tesseract di tre lettere, eng per impostazione predefinita. Combina le lingue con un segno più, ad esempio deu+eng. Tesseract prova quindi ciascuna lingua e conserva il risultato migliore. Ogni lingua aggiuntiva moltiplica il tempo CPU impiegato per ogni pagina. Su una VPS con vCPU condivisa, questo può fare la differenza tra una scansione completata in dieci secondi e una completata in un minuto. Elenca solo le lingue in cui sono effettivamente scritti i documenti.

L'immagine include inglese, tedesco, italiano, spagnolo e francese. Per qualsiasi altra lingua, aggiungila a PAPERLESS_OCR_LANGUAGES come elenco separato da spazi, ad esempio PAPERLESS_OCR_LANGUAGES=tur ces, quindi riavvia il servizio. Il container scarica i pacchetti di dati di Tesseract all'avvio, quindi il primo avvio dopo questa modifica è più lento.

Esegui il backup del database e dei file multimediali

La copia dei volumi Docker mentre PostgreSQL è in esecuzione produce un backup che potrebbe non essere ripristinabile. Paperless include un proprio exporter, che scrive i documenti e un manifest JSON con tutti i metadati nel bind mount ./export:

docker compose exec webserver document_exporter ../export --delete --no-progress-bar

--delete rimuove i file esportati che non corrispondono più a un documento attuale, in modo che la directory resti un mirror invece di crescere indefinitamente. --no-progress-bar mantiene pulito l'output quando il comando viene eseguito da cron.

Il ripristino viene eseguito document_importer sulla stessa directory in uno stack appena creato. Di conseguenza, l'unico elemento da conservare in sicurezza è la directory di esportazione. Trasferiscila periodicamente fuori sede con backup restic cifrati e deduplicati dal tuo VPS ed esegui prima l'esportazione, così restic non acquisisce un archivio scritto solo parzialmente.

Verifica un backup controllando che export/manifest.json esista e che il numero di file corrisponda al numero di documenti mostrato nell’interfaccia. Un backup di cui non hai mai verificato l’elenco non è un backup. Un’esportazione notturna che inizia a non riuscire senza segnalazioni è ancora peggio. Configura quindi il job cron affinché invii il proprio stato di uscita al tuo server ntfy: scoprirai il problema nella settimana in cui si verifica, invece che il giorno in cui dovrai ripristinare i dati.

FAQ

Perché ogni pagina restituisce "Bad Request (400)" dopo aver associato il dominio?

Django ha rifiutato l'header Host perché il dominio non è presente in ALLOWED_HOSTS. Imposta PAPERLESS_URL=https://paperless.example.com in docker-compose.env, senza slash finale, quindi esegui docker compose up -d per ricreare il container. La modifica del solo file env non produce alcun effetto, perché il container in esecuzione conserva l'ambiente con cui è stato avviato.

Ho inserito un PDF nella cartella consume, ma non è successo nulla. Qual è il problema?

Controlla prima docker compose logs webserver. Un errore di autorizzazione indica che USERMAP_UID e USERMAP_GID non corrispondono all'account proprietario del file; correggi quindi questi valori e ricrea il container. L'assenza completa di righe nel log indica che l'evento relativo al file non è mai arrivato. Questo accade sulle condivisioni di rete, perché le notifiche del kernel non le attraversano. Imposta PAPERLESS_CONSUMER_POLLING_INTERVAL su un valore come 30; paperless eseguirà invece la scansione della cartella ogni 30 secondi.

Posso eseguire paperless-ngx con SQLite invece di PostgreSQL?

Sì, docker-compose.sqlite.yml è supportato e usa meno memoria, quindi è adatto a un VPS di piccole dimensioni. Lo svantaggio diventa evidente quando l'archivio cresce: la ricerca full-text e le modifiche collettive ai tag rallentano sensibilmente quando i documenti diventano migliaia. Una migrazione successiva richiede un'operazione di export e import. Se prevedi che l'archivio continui a crescere, scegli PostgreSQL fin dall'inizio.

Quanto spazio su disco richiede realmente un archivio di scansioni?

All'incirca il doppio delle dimensioni dei file originali. Paperless conserva l'originale senza modificarlo e memorizza un secondo PDF elaborato con OCR e livello di testo ricercabile, oltre a piccole miniature. Una scansione di solo testo da 200 KB resta di dimensioni ridotte. Una scansione a colori da 30 MB di un contratto lungo occupa circa 60 MB. Se conservi la directory di export sullo stesso disco, aggiungila al calcolo: lo stesso archivio occuperà tre volte lo spazio originale.

Sono necessari i container Tika e Gotenberg?

Solo se vuoi indicizzare file Word, Excel o OpenDocument insieme ai PDF. Questi container convertono i formati in PDF, permettendo a paperless di eseguire l'OCR e indicizzarli. Aggiungono inoltre altri due container in esecuzione e alcune centinaia di megabyte di memoria. Puoi quindi ometterli su un server di piccole dimensioni se archivi esclusivamente PDF o immagini.

#paperless-ngx#documents#self-hosting#docker#ocr