SSD Nodes Learn Hosting plans →
Gidsen Matt ConnorDoor Matt Connor · Bijgewerkt 2026-10-14

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, waarna de server OCR (optische tekenherkenning) uitvoert, de tekst extraheert, een datum en correspondent raadt en het document opslaat. 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 vaak mislukken. Het is geen fotobibliotheek: OCR en het raden van correspondenten hebben geen nut voor een map met vakantiefoto's in JPEG-formaat. Plaats die daarom op een fotoserver die daarvoor is gebouwd en gebruik Paperless alleen voor documenten. Voor video geldt hetzelfde: een verzameling geripte films hoort thuis op een mediaserver, waar iets als een Jellyfin-interface in de stijl van een videotheek uit de jaren 90 het bladeren tot het hoofddoel maakt in plaats van het zoeken.

Paperless-ngx is de onderhouden community-fork van het oorspronkelijke Paperless-project. Het is gratis, zelf-gehost en slaat uw documenten op als gewone bestanden op de schijf, zodat u nooit de toegang tot uw eigen archief verliest. Door het op een VPS te draaien in plaats van op een machine thuis, zijn uw scans overal bereikbaar zonder dat u een poort op uw thuisrouter hoeft open te zetten. Het is een goede combinatie met een eigen Nextcloud-instantie voor bestanden die geen documenten zijn. Dezelfde logica geldt voor de desktop waarop uw scanner is aangesloten, aangezien een eigen RustDesk-relay op die VPS u in staat stelt die machine vanaf een andere locatie te besturen zonder dat u daarvoor een gat in de router hoeft te slaan.

Wat de stack daadwerkelijk uitvoert

Het officiële compose-bestand start vier containers. Het begrijpen van de functie van elke container 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-taskworkers die OCR uitvoeren uit.
  • db: PostgreSQL. Deze bevat metadata, tags, correspondenten en de tabellen voor de full-text zoekindex. Uw PDF-bestanden worden hier niet in opgeslagen.
  • broker: Valkey, een Redis-compatibele key-value store. Dit is de takenwachtrij tussen het webproces en de workers.
  • gotenberg en tika: optioneel, alleen in de -tika compose-varianten. Deze converteren Office-documenten (.docx, .xlsx, .odt) naar PDF zodat paperless ze kan indexeren.

Sinds oktober 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, waarbij Docker met de Compose-plugin al is geïnstalleerd. Als dit nieuw voor u is, begin dan met de Docker Compose-basisprincipes voor een VPS en keer daarna terug.
  • Een domeinnaam met een A-record dat naar de VPS wijst. Paperless accepteert geen aanmeldingen via HTTPS 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, de Granian-webserver 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 op een grote PDF met meerdere pagina's de geheugenpiek veroorzaakt waardoor een worker door de out-of-memory killer van de kernel wordt beëindigd.
  • Schijfruimte: uw archief wordt tweemaal opgeslagen, het originele bestand en een archief-PDF met OCR, dus houd rekening met ongeveer het dubbele van de omvang 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 sessietokens en huidige releases beschouwen die placeholder als niet ingesteld: paperless weigert te starten zolang de sleutel ontbreekt, leeg is of nog steeds change-me bevat, waardoor de stack niet zal opstarten totdat u deze vervangt. Stel deze in vóór de eerste start, omdat het later wijzigen ervan alle gebruikers uitlogt.

PAPERLESS_URL is de instelling die u een uur werk bespaart. Paperless is een Django-applicatie en Django controleert zowel de Host-header als, bij een aanmeldformulier, de herkomst (origin) die de browser rapporteert. Stel PAPERLESS_URL in en de applicatie voegt uw adres toe aan ALLOWED_HOSTS, CORS_ALLOWED_HOSTS en CSRF_TRUSTED_ORIGINS voor u. Laat u dit leeg en wijst u een domein naar de server, dan laadt de aanmeldpagina nog steeds omdat PAPERLESS_ALLOWED_HOSTS standaard * is, maar het verzenden via HTTPS mislukt met CSRF verification failed. Request aborted. omdat uw domein geen vertrouwde herkomst is. Noteer dit 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 controleert 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 toegangs- of rechtenfout 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, vraagt de webinterface aan de eerste persoon die de pagina opent om de superuser aan te maken. Omdat poort 8000 op dat moment nog openstaat naar het internet, bent u dat mogelijk niet zelf. Maak het account daarom direct aan via de shell. Wacht tot de logregel aangeeft dat de server luistert op poort 8000 voordat u de browser gebruikt. De allereerste start voert ook databasemigraties uit; dit duurt een minuut of twee.

Controleer de werking lokaal voordat u een domein koppelt:

curl -I http://127.0.0.1:8000

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

HTTPS ervoor plaatsen

Het standaard compose-bestand publiceert 8000:8000, wat bindt aan elke interface. Op een publieke VPS betekent dit dat uw volledige documentenarchief via onversleuteld HTTP wordt aangeboden 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 gepubliceerde poort.

Welke proxy u ook gebruikt, de oorsprongcontrole (origin check) van het inlogformulier is waar HTTPS-configuraties vaak falen. Paperless negeert X-Forwarded-Proto tenzij u PAPERLESS_PROXY_SSL_HEADER instelt, wat standaard uitgeschakeld is. Hierdoor behandelt Django elk geproxied verzoek als onversleuteld HTTP, en wordt de https://-oorsprong die uw browser meestuurt alleen geaccepteerd als deze als vertrouwd is gemarkeerd. PAPERLESS_URL ingesteld op het exacte https://-adres dat u in de browser typt, zorgt ervoor dat dit op die lijst komt te staan. Zonder deze instelling krijgt u CSRF verification failed. Request aborted. op een pagina die er verder correct uitziet.

Verhoog ook de uploadlimiet van de proxy. Een scan van 40 MB die via een proxy gaat met een limiet van 1 MB wordt geweigerd voordat paperless deze ontvangt, waarna de browser een algemene uploadfout meldt.

Hoe de consume-map werkt

Het compose-bestand voert een bind-mount uit van ./consume vanuit de compose-directory naar 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 gehele 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 gebruikmaakt van native bestandssysteem-notificaties, die onmiddellijk worden geactiveerd. Die notificaties zijn niet betrouwbaar op een netwerkbestandssysteem. Als uw consume-map een NFS- of SMB-share is zodat een netwerkscanner er naartoe kan schrijven, kunnen bestanden onopgemerkt blijven. 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 verwerkt vervolgens elke vermelde taal op elke pagina, waardoor elke extra taal de CPU-tijd per scan verhoogt. Op een VPS met gedeelde vCPU's kan dit het verschil betekenen 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 langer duurt.

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 -T 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, en -T voorkomt dat Compose vraagt om een terminal die cron niet heeft.

Herstellen gebeurt via 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 back-up door te verifiëren of export/manifest.json bestaat en of het aantal bestanden overeenkomt met uw documentaantal in de interface. Een back-up die u nooit heeft gecontroleerd, is geen back-up. Een nachtelijke export die stilletjes faalt is nog erger, dus laat de cron-job de exit-status pushen naar uw eigen ntfy server zodat u het ontdekt in de week dat het misgaat, in plaats van op de dag dat u moet herstellen.

FAQ

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

Met de standaardbestanden zou dit niet moeten gebeuren, omdat PAPERLESS_ALLOWED_HOSTS standaard is ingesteld op *. Een 400 met DisallowedHost in het containerlogboek betekent dat PAPERLESS_ALLOWED_HOSTS is ingesteld op een lijst waar uw domein niet in voorkomt. Stel PAPERLESS_URL=https://paperless.example.com in binnen docker-compose.env, zonder afsluitende slash; paperless voegt die hostnaam dan toe aan de lijst. U kunt ook de regel PAPERLESS_ALLOWED_HOSTS verwijderen. 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, maar er gebeurt niets. Wat is er mis?

Controleer eerst docker compose logs webserver. Een permissiefout betekent dat USERMAP_UID en USERMAP_GID niet overeenkomen met het account dat eigenaar is van het bestand. Herstel de rechten en maak de container opnieuw aan. Als er helemaal geen logregel verschijnt, is de bestandgebeurtenis nooit aangekomen. Dit gebeurt vaak bij netwerkshares, omdat native bestandssysteemnotificaties daar niet betrouwbaar zijn. Stel PAPERLESS_CONSUMER_POLLING_INTERVAL in op een waarde zoals 30; paperless zal de map dan elke 30 seconden scannen.

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 search 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?

Ruwweg 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, dan staat hetzelfde archief 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 containers converteren die formaten naar PDF, zodat paperless ze kan voorzien van OCR en doorzoekbaar kan maken. Ze voegen echter twee extra actieve containers en enkele honderden megabytes aan geheugengebruik toe. Sla deze over op een kleine server als alles wat u archiveert al een PDF of afbeelding is.

Was dit artikel nuttig?

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