SSD Nodes Learn Hosting plans →
Gidsen Matt ConnorDoor Matt Connor · Bijgewerkt 2026-08-28

Paperless-ngx installeren op een VPS met Docker Compose

Leer hoe u Paperless-ngx zelf host op een VPS. Deze handleiding behandelt de Docker Compose configuratie, PostgreSQL instellingen, OCR talen en het veilig inrichten van backups.

Wat u bouwt

Paperless-ngx op een VPS verandert een map met gescande documenten in een doorzoekbaar archief. U plaatst een PDF in een gecontroleerde map, de server voert OCR (optische tekenherkenning) uit, extraheert de tekst, schat een datum en een correspondent in en archiveert het bestand. De installatie bestaat uit één Docker Compose-bestand met vier services. Alles daarna is configuratie, en deze handleiding besteedt daar het grootste deel van de tijd aan, omdat installaties daar meestal mislukken. Het is geen fotobibliotheek: OCR en het inschatten van correspondenten doen niets voor een map met vakantiefoto's in JPEG-formaat; plaats die daarom in een fotoserver die daarvoor is gebouwd en gebruik Paperless alleen voor documenten.

Paperless-ngx is de door de community onderhouden fork van het oorspronkelijke Paperless-project. Het is gratis, zelfgehost en slaat uw documenten op schijf op als gewone bestanden. U raakt daardoor nooit de toegang tot uw eigen archief kwijt. Als u het op een VPS uitvoert in plaats van op een computer thuis, zijn uw scans overal bereikbaar zonder een poort op uw thuisrouter open te stellen. Het werkt bovendien goed samen met een privé-Nextcloud-instantie voor bestanden die niet op papier staan. Hetzelfde geldt voor de desktop waarop uw scanner is aangesloten. Met een eigen RustDesk-relay op die VPS kunt u die computer vanaf een andere locatie bedienen zonder ook daarvoor een opening in de router te maken.

Wat de stack daadwerkelijk uitvoert

Het officiële compose-bestand start vier containers. Het begrijpen van de functie van elk onderdeel maakt de logs leesbaar.

  • webserver: de paperless-ngx image zelf. Deze voert de webinterface, de API, de consumer die uw invoermap bewaakt en de Celery-taakwerkers die OCR uitvoeren uit.
  • db: PostgreSQL. Deze bevat metadata, tags, correspondenten en de tabellen voor de full-text zoekindex. De PDF-bestanden zelf worden hier niet in opgeslagen.
  • broker: Valkey, een Redis-compatibele key-value store. Dit fungeert als de takenwachtrij tussen het webproces en de werkers.
  • gotenberg en tika: optioneel, alleen aanwezig in de -tika compose-varianten. Deze converteren Office-documenten (.docx, .xlsx, .odt) naar PDF zodat paperless ze kan indexeren.

Sinds juli 2026 zet het postgres compose-bestand de versies docker.io/library/postgres:18 en docker.io/valkey/valkey:9-alpine vast en haalt het de applicatie op van ghcr.io/paperless-ngx/paperless-ngx:latest.

Vereisten

  • Een Ubuntu 24.04 KVM VPS met sudo-toegang, waarop Docker met de Compose-plugin al is geïnstalleerd. Als dit nieuw voor u is, begin dan bij de basisprincipes van Docker Compose voor een VPS en keer daarna terug.
  • Een domeinnaam met een A-record dat naar de VPS wijst. Paperless weigert te werken op een hostnaam die niet is geconfigureerd, dus dit is eerder van belang dan u wellicht verwacht.
  • Geheugen is de werkelijke beperkende factor. PostgreSQL, Valkey, gunicorn en een Tesseract OCR-worker passen bij licht gebruik samen in 2 GB. Reserveer 4 GB als u van plan bent een achterstand van honderden scans te importeren, omdat OCR bij een groot PDF-bestand met meerdere pagina's een piek in het geheugengebruik veroorzaakt die ertoe kan leiden dat de kernel de worker beëindigt via de out-of-memory killer.
  • Schijfruimte: uw archief wordt dubbel opgeslagen, namelijk als het originele bestand en als een PDF-archief met OCR. Reken daarom op ongeveer het dubbele van de grootte van uw scans.

De officiële compose-bestanden ophalen

Er is een interactief installatieprogramma beschikbaar:

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

Dit programma stelt u vragen en schrijft de bestanden voor u. Handmatige installatie vereist slechts vier commando's en zorgt ervoor dat u precies weet waar alles staat; dit is de gewenste werkwijze voor een server die u zelf onderhoudt.

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

De varianten bevinden zich in dezelfde map: docker-compose.sqlite.yml, docker-compose.mariadb.yml en een -tika-versie van elk. Kies postgres voor een nieuwe installatie. SQLite volstaat voor enkele honderden documenten, maar de index voor full-text search wordt aanzienlijk trager dan bij PostgreSQL.

Het bestand .env bevat één regel: COMPOSE_PROJECT_NAME=paperless. Deze naam wordt het voorvoegsel voor elke container en elk volume. Verwijder dit bestand dus niet, om te voorkomen dat docker compose down -v uw data niet meer kan vinden.

Configureer docker-compose.env vóór de eerste start

Twee instellingen zijn niet optioneel. Genereer de geheime sleutel met het commando dat in de documentatie van het project staat:

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

Bewerk vervolgens 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 wordt geleverd met de letterlijke waarde change-me. Deze ondertekent sessiecookies; als u deze waarde laat staan, kan iedereen die de standaardwaarde kent een sessie vervalsen. Stel deze in vóór de eerste start, omdat het wijzigen ervan achteraf alle gebruikers uitlogt.

PAPERLESS_URL is de instelling die u een uur werk bespaart. Paperless is een Django-applicatie en Django valideert de Host-header van elk verzoek. Stel PAPERLESS_URL in en de applicatie vult automatisch ALLOWED_HOSTS, CORS_ALLOWED_HOSTS en CSRF_TRUSTED_ORIGINS voor u in. Laat u dit leeg en wijst u een domein naar de server, dan geeft elke pagina een Bad Request (400)-foutmelding met DisallowedHost in het containerlogboek. Noteer de waarde zonder afsluitende slash en zonder pad.

USERMAP_UID en USERMAP_GID bepalen de gebruiker waaronder de container wordt uitgevoerd. Laat deze overeenkomen met uw eigen account, wat u kunt controleren met id -u en id -g. Als deze niet overeenkomen, zijn bestanden die u naar de consume-map kopieert onleesbaar voor de consumer en toont het logboek een toegangsrechtenfout in plaats van een import.

Start de stack en maak de eerste gebruiker aan

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

createsuperuser vraagt om een gebruikersnaam, een e-mailadres en een wachtwoord. Er is geen standaard login; als u deze stap overslaat, komt u op een inlogpagina die geen enkele invoer accepteert. Wacht tot de logregel aangeeft dat de server luistert op poort 8000 voordat u de browser gebruikt. De allereerste start voert ook databasemigraties uit, wat een minuut of twee in beslag neemt.

Controleer het lokaal voordat u een domein toevoegt:

curl -I http://127.0.0.1:8000

Een 302 redirect naar /accounts/login/ betekent dat de stack correct functioneert.

Plaats HTTPS ervoor

Het standaard compose-bestand publiceert 8000:8000, wat bindt aan elke interface. Op een publieke VPS betekent dit dat uw volledige documentarchief via onversleuteld HTTP wordt geserveerd aan iedereen die het adres vindt. Wijzig de poortregel zodat deze alleen aan loopback bindt:

    ports:
      - "127.0.0.1:8000:8000"

Beëindig vervolgens TLS (transport layer security) in een reverse proxy en stuur het verkeer door naar 127.0.0.1:8000. Als dit de enige applicatie op de server is, volstaat elke proxy met een ACME (automatic certificate management environment) client. Als u meerdere containers achter één certificaatconfiguratie draait, volg dan het Traefik reverse proxy-patroon voor meerdere Docker Compose-applicaties en koppel de webserver-service aan het proxynetwerk zonder enige gepubliceerde poort.

Welke proxy u ook gebruikt, deze moet X-Forwarded-Proto: https meesturen. Zonder deze header denkt Django dat het verzoek via HTTP is binnengekomen, mislukt de origin-controle op het inlogformulier en krijgt u CSRF verification failed. Request aborted. op een pagina die er verder correct uitziet. Het andere deel van deze oplossing is dat PAPERLESS_URL moet worden ingesteld op exact het https://-adres dat u in de browser typt.

Verhoog ook de uploadlimiet van de proxy. Een scan van 40 MB die door een proxy gaat die de body beperkt tot 1 MB, wordt geweigerd voordat paperless deze ooit ziet, waarna de browser een algemene uploadfout meldt.

Hoe de consume-map werkt

Het compose-bestand koppelt ./consume via een bind-mount vanuit de compose-directory aan de container. Alles wat u daar plaatst, wordt geïmporteerd en vervolgens uit de map verwijderd, omdat het bestand nu in het media-volume onder beheer van paperless staat.

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

U zou moeten zien dat de consumer de bestandsnaam oppikt, OCR uitvoert en eindigt met een regel die meldt dat het document is toegevoegd. De volledige cyclus duurt enkele seconden voor een scan van één pagina en kan een minuut of langer duren voor een lang document.

Twee instellingen veranderen de manier waarop bestanden worden gevonden. PAPERLESS_CONSUMER_RECURSIVE=true zorgt ervoor dat paperless in submappen zoekt, en PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=true verandert elke submapnaam in een tag, dus het plaatsen van een bestand in consume/invoices/2026/ voorziet het van de tags invoices en 2026. Dat is het goedkoopste archiefsysteem dat u ooit zult bouwen.

Detectie is de andere helft. Standaard is PAPERLESS_CONSUMER_POLLING_INTERVAL ingesteld op 0, wat betekent dat paperless kernel-bestandssysteemnotificaties gebruikt, die onmiddellijk worden geactiveerd. Deze notificaties werken niet over een netwerkbestandssysteem. Als uw consume-map een NFS- of SMB-share is zodat een netwerkscanner er naartoe kan schrijven, wordt er niets gedetecteerd. De oplossing is om het interval in te stellen op een positief aantal seconden, zodat paperless de map periodiek scant.

OCR-talen en de bijbehorende kosten

PAPERLESS_OCR_LANGUAGE vereist een Tesseract-code van drie letters, waarbij eng de standaardwaarde is. Combineer talen met een plusteken, zoals in deu+eng. Tesseract probeert vervolgens elke taal en behoudt het beste resultaat; elke extra taal vermenigvuldigt dus de CPU-tijd die per pagina wordt verbruikt. Op een VPS met gedeelde vCPU's is dit het verschil tussen een scan die in tien seconden klaar is en een scan die een minuut duurt. Vermeld alleen de talen waarin uw documenten daadwerkelijk zijn geschreven.

De image bevat standaard Engels, Duits, Italiaans, Spaans en Frans. Voor alle andere talen voegt u de taal toe aan PAPERLESS_OCR_LANGUAGES als een door spaties gescheiden lijst, bijvoorbeeld PAPERLESS_OCR_LANGUAGES=tur ces, en start u de container opnieuw op. De container downloadt de Tesseract-datapakketten bij het opstarten, waardoor de eerste keer opstarten na deze wijziging trager verloopt.

Maak een back-up van de database en de media

Het kopiëren van Docker-volumes terwijl PostgreSQL actief is, levert een back-up op die mogelijk niet kan worden hersteld. Paperless bevat een eigen exporter, die documenten samen met een JSON-manifest van alle metadata naar de ./export bind mount schrijft:

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

--delete verwijdert geëxporteerde bestanden die niet langer overeenkomen met een huidig document, zodat de map een spiegel blijft in plaats van oneindig te groeien. --no-progress-bar houdt de output schoon wanneer dit via cron wordt uitgevoerd.

Herstellen gebeurt met document_importer tegen diezelfde map op een nieuwe stack, wat betekent dat de exportmap het enige is dat u veilig moet bewaren. Verstuur deze volgens een schema naar een externe locatie met versleutelde, ontdubbelde restic-back-ups vanaf uw VPS, en voer de export eerst uit zodat restic nooit een half geschreven archief vastlegt.

Controleer een backup door na te gaan of export/manifest.json bestaat en of het aantal bestanden overeenkomt met het aantal documenten in de interface. Een backup die u nooit hebt opgesomd, is geen backup. Nog erger is een nachtelijke export die ongemerkt mislukt. Laat de cronjob daarom de exitstatus doorgeven aan uw eigen ntfy-server. Dan merkt u in de week waarin de export uitvalt dat er een probleem is, in plaats van op de dag waarop u moet herstellen.

FAQ

Waarom geeft elke pagina "Bad Request (400)" terug nadat ik mijn domein ernaar heb verwezen?

Django weigert de Host-header omdat uw domein niet in ALLOWED_HOSTS staat. Stel PAPERLESS_URL=https://paperless.example.com in docker-compose.env in, zonder afsluitende slash, en voer daarna docker compose up -d uit om de container opnieuw aan te maken. Het enkel bewerken van het env-bestand heeft geen effect, omdat de actieve container de omgeving behoudt waarmee deze is gestart.

Ik heb een PDF in de consume-map geplaatst en er gebeurt niets. Wat is er mis?

Controleer eerst docker compose logs webserver. Een rechtenfout betekent dat USERMAP_UID en USERMAP_GID niet overeenkomen met het account dat eigenaar is van het bestand; corrigeer deze en maak de container opnieuw aan. Als er helemaal geen logregel verschijnt, is de bestandgebeurtenis nooit aangekomen; dit gebeurt bij netwerkshares omdat kernel-notificaties deze niet overschrijden. Stel PAPERLESS_CONSUMER_POLLING_INTERVAL in op bijvoorbeeld 30, zodat paperless de map elke 30 seconden scant.

Kan ik paperless-ngx draaien met SQLite in plaats van PostgreSQL?

Ja, docker-compose.sqlite.yml wordt ondersteund en verbruikt minder geheugen, wat geschikt is voor een kleine VPS. Het nadeel wordt merkbaar naarmate uw archief groeit: full-text zoeken en het in bulk bewerken van tags worden merkbaar trager bij duizenden documenten. Later migreren vereist een export en een import, dus kies nu voor PostgreSQL als u verwacht dat het archief blijft groeien.

Hoeveel schijfruimte heeft een archief met scans daadwerkelijk nodig?

Ongeveer twee keer de grootte van uw bronbestanden. Paperless laat het origineel ongewijzigd en slaat een tweede OCR-PDF op met een doorzoekbare tekstlaag, plus kleine thumbnails. Een scan van 200 KB met alleen tekst blijft klein. Een kleurenscan van 30 MB van een lang contract neemt ongeveer 60 MB in beslag. Tel daar de exportmap bij op als u deze op dezelfde schijf bewaart, en hetzelfde archief staat drie keer op de schijf.

Heb ik de Tika- en Gotenberg-containers nodig?

Alleen als u wilt dat Word-, Excel- of OpenDocument-bestanden naast uw PDF's worden geïndexeerd. Deze zetten die formaten om naar PDF zodat paperless ze kan voorzien van OCR en doorzoekbaar kan maken. Ze voegen ook twee extra actieve containers en een paar honderd megabyte aan geheugenverbruik toe, dus sla deze over op een kleine server als alles wat u archiveert al een PDF of afbeelding is.

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