SSD Nodes Learn 8GB RAM — $66/jaar
Gidsen Matt ConnorDoor Matt Connor · Bijgewerkt 2026-08-02

Paperless-ngx op een VPS installeren met Docker Compose

Installeer paperless-ngx op een VPS met de officiële Postgres-stack, PAPERLESS_URL, de consume-map, OCR-talen, HTTPS en betrouwbare back-ups.

Wat u bouwt

Paperless-ngx op een VPS verandert een map met gescande papieren documenten in een doorzoekbaar archief. U plaatst een PDF in een bewaakte map. De server voert er OCR (optical character recognition) op uit, haalt de tekst eruit, bepaalt vermoedelijk een datum en correspondent en archiveert het document. De installatie bestaat uit één Docker Compose-bestand met vier services. Daarna volgt alleen configuratie. Deze handleiding besteedt daar het grootste deel van de tekst aan, omdat installaties juist daar mislukken.

Paperless-ngx is de onderhouden community-fork van het oorspronkelijke Paperless-project. De software is gratis, wordt door uzelf gehost en slaat uw documenten op als gewone bestanden op schijf. Daardoor wordt u nooit de toegang tot uw eigen archief ontzegd. Als u de software op een VPS uitvoert in plaats van op een computer thuis, zijn uw scans overal bereikbaar zonder een poort op uw thuisrouter te openen. Bovendien werkt dit goed samen met een private Nextcloud-instantie voor bestanden die geen papier zijn.

Wat de stack daadwerkelijk uitvoert

Het officiële compose-bestand start vier containers. Als u weet wat elke container doet, kunt u de logboeken beter lezen.

  • webserver: de paperless-ngx-image zelf. Deze voert de webinterface en de API uit. Ook draait deze de consumer die uw invoermap bewaakt en de Celery-taskworkers die OCR uitvoeren.
  • db: PostgreSQL. Deze bevat metagegevens, tags, correspondenten en de tabellen voor de full-text-zoekindex. Uw PDF-bestanden worden hier niet opgeslagen.
  • broker: Valkey, een Redis-compatibele key-value-store. Deze vormt de task queue tussen het webproces en de workers.
  • gotenberg en tika: optioneel, alleen in de -tika-composevarianten. Deze zetten Office-documenten (.docx, .xlsx, .odt) om naar PDF, zodat paperless ze kan indexeren.

In juli 2026 legt het postgres-compose-bestand docker.io/library/postgres:18 en docker.io/valkey/valkey:9-alpine vast en haalt het de applicatie op uit ghcr.io/paperless-ngx/paperless-ngx:latest.

Vereisten

  • Een Ubuntu 24.04 KVM VPS met sudo-toegang en Docker met de Compose-plugin al geïnstalleerd. Als dit nieuw voor u is, begin dan met de basisbeginselen van Docker Compose voor een VPS en ga daarna hier verder.
  • Een domeinnaam met een A-record dat naar de VPS verwijst. Paperless weigert te worden aangeboden op een hostnaam die niet aan Paperless is doorgegeven. Dit is dus eerder van belang dan u waarschijnlijk verwacht.
  • Geheugen is de werkelijke beperking. PostgreSQL, Valkey, gunicorn en één Tesseract OCR-worker passen bij licht gebruik gelijktijdig in 2 GB. Gebruik 4 GB als u een achterstand van honderden scans wilt importeren. OCR op een grote PDF met meerdere pagina's veroorzaakt namelijk de piek in geheugengebruik waardoor de kernel een worker beëindigt met de out-of-memory-killer.
  • Schijfruimte: uw archief wordt tweemaal opgeslagen: het oorspronkelijke bestand en een OCR-archief-PDF. Reserveer daarom ongeveer tweemaal de omvang van uw scans.

De officiële compose-bestanden ophalen

Er is een interactieve installer:

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

De installer stelt vragen en schrijft de bestanden voor u. Handmatig uitvoeren vereist vier opdrachten. U weet dan waar alles staat. Dat is belangrijk op een server die u zelf beheert.

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 staan in dezelfde map: docker-compose.sqlite.yml, docker-compose.mariadb.yml en van elk een -tika-versie. Kies postgres voor een nieuwe installatie. SQLite is geschikt voor enkele honderden documenten, maar de full-text search-index wordt veel eerder traag dan PostgreSQL.

Het bestand .env bevat één regel: COMPOSE_PROJECT_NAME=paperless. Die naam wordt het voorvoegsel voor elke container en elk volume. Verwijder deze naam dus niet om u daarna af te vragen waarom docker compose down -v uw gegevens niet kan vinden.

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

Twee instellingen zijn verplicht. Genereer de geheime sleutel met de opdracht die het project documenteert:

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 sleutel ondertekent sessiecookies. Als u de standaardwaarde laat staan, kan iedereen die deze waarde kent een sessie vervalsen. Stel de waarde in vóór de eerste start. Als u deze later wijzigt, worden alle gebruikers afgemeld.

PAPERLESS_URL bespaart u een uur werk. Paperless is een Django-toepassing en Django valideert de header Host van elk verzoek. Stel PAPERLESS_URL in. Django vult dan ALLOWED_HOSTS, CORS_ALLOWED_HOSTS en CSRF_TRUSTED_ORIGINS automatisch voor u in. Als u deze instelling leeg laat en een domein naar de server verwijst, retourneert elke pagina Bad Request (400). In het containerlog verschijnt dan DisallowedHost. Gebruik geen afsluitende slash en voeg geen pad toe.

USERMAP_UID en USERMAP_GID bepalen onder welke gebruiker de container wordt uitgevoerd. Stem deze waarden af op uw eigen account. Controleer dit met id -u en id -g. Als de waarden niet overeenkomen, kunnen bestanden die u naar de consume-map kopieert niet door de consumer worden gelezen. Het log toont dan een permissiefout 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 standaardaanmelding. Als u deze stap overslaat, komt u op een aanmeldpagina die geen enkele aanmelding accepteert. Wacht totdat de logregel meldt dat de server op poort 8000 luistert voordat u de browser gebruikt. Bij de allereerste start worden ook databasemigraties uitgevoerd. Dit duurt een of twee minuten.

Controleer de stack lokaal voordat u een domein gebruikt:

curl -I http://127.0.0.1:8000

Een omleiding van 302 naar /accounts/login/ betekent dat de stack correct werkt.

HTTPS ervoor zetten

Het standaard compose-bestand publiceert 8000:8000, dat aan elke interface wordt gebonden. Op een openbare VPS wordt daardoor uw volledige documentenarchief via plain HTTP beschikbaar gesteld aan iedereen die het adres vindt. Wijzig de poortregel zodat deze alleen aan loopback wordt gebonden:

    ports:
      - "127.0.0.1:8000:8000"

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

Welke proxy u ook gebruikt, deze moet X-Forwarded-Proto: https verzenden. Zonder deze header gaat Django ervan uit dat het verzoek via HTTP is binnengekomen. Daardoor mislukt de origin-controle van het aanmeldingsformulier en krijgt u CSRF verification failed. Request aborted. op een pagina die er correct uitziet. De andere helft van deze oplossing is dat PAPERLESS_URL wordt ingesteld op exact het adres https:// dat u in de browser invoert.

Verhoog ook de limiet voor uploads van de proxy. Een scan van 40 MB via een proxy die request bodies beperkt tot 1 MB, wordt afgewezen voordat paperless deze ontvangt. De browser meldt dan een algemene uploadfout.

Hoe de consume-directory werkt

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

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

U ziet dat de consumer de bestandsnaam oppakt, OCR uitvoert en eindigt met een regel waarin wordt gemeld dat het document is toegevoegd. De volledige cyclus duurt enkele seconden voor een scan van één pagina en kan voor een lang document een minuut of langer duren.

Twee instellingen bepalen hoe bestanden worden gevonden. Met PAPERLESS_CONSUMER_RECURSIVE=true zoekt paperless ook in subdirectories. Met PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=true wordt elke naam van een subdirectory een tag. Als u een bestand in consume/invoices/2026/ plaatst, krijgt het de tags invoices en 2026. Dit is het eenvoudigste archiveringssysteem dat u ooit zult bouwen.

Detectie is de andere helft. Standaard is PAPERLESS_CONSUMER_POLLING_INTERVAL ingesteld op 0. Dit betekent dat paperless kernelmeldingen voor het bestandssysteem gebruikt, die onmiddellijk worden gegenereerd. Deze meldingen gaan niet over een netwerkbestandssysteem. Als uw consume-directory een NFS- of SMB-share is waarop een netwerkscanner bestanden kan schrijven, wordt er nooit iets gedetecteerd. Stel in dat geval het interval in op een positief aantal seconden, zodat paperless de directory scant.

OCR-talen en de kosten ervan

PAPERLESS_OCR_LANGUAGE accepteert een drieletterige Tesseract-code, standaard eng. Combineer talen met een plusteken, zoals in deu+eng. Tesseract probeert vervolgens elke taal en behoudt het beste resultaat. Elke extra taal vermenigvuldigt daardoor de CPU-tijd die voor elke pagina nodig is. Op een VPS met gedeelde vCPU maakt dat het verschil tussen een scan die in tien seconden wordt voltooid en een scan die een minuut duurt. Vermeld alleen de talen waarin uw documenten daadwerkelijk zijn geschreven.

De image bevat Engels, Duits, Italiaans, Spaans en Frans. Voeg voor andere talen de taal toe aan PAPERLESS_OCR_LANGUAGES als een door spaties gescheiden lijst, bijvoorbeeld PAPERLESS_OCR_LANGUAGES=tur ces, en start de container opnieuw. De container downloadt de Tesseract-datapakketten bij het opstarten. De eerste keer opstarten na deze wijziging duurt daarom langer.

Maak een back-up van de database en de media

Als u de Docker-volumes kopieert terwijl PostgreSQL actief is, krijgt u mogelijk een back-up die niet kan worden teruggezet. Paperless bevat een eigen exporter die documenten plus een JSON-manifest met 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 meer overeenkomen met een huidig document. Zo blijft de map een mirror in plaats van onbeperkt te groeien. --no-progress-bar houdt de uitvoer schoon wanneer dit vanuit cron wordt uitgevoerd.

Herstellen gebeurt met document_importer vanuit diezelfde map op een nieuwe stack. Daardoor is de exportmap het enige onderdeel dat u veilig moet bewaren. Stuur deze volgens een schema naar een externe locatie met versleutelde, gededupliceerde restic-back-ups vanaf uw VPS en voer de export eerst uit. Zo legt restic nooit een gedeeltelijk geschreven archief vast.

Controleer een back-up door na te gaan of export/manifest.json bestaat en of het aantal bestanden overeenkomt met het aantal documenten in de interface. Een back-up waarvan u nooit een lijst hebt opgevraagd, is geen back-up.

FAQ

Waarom retourneert elke pagina "Bad Request (400)" nadat ik mijn domein eraan heb gekoppeld?

Django heeft de Host-header geweigerd 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. Alleen het env-bestand bewerken 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 gebeurde niets. Wat is er aan de hand?

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. Corrigeer deze waarden en maak de container opnieuw aan. Als er helemaal geen logregel verschijnt, is de bestandsgebeurtenis niet aangekomen. Dit gebeurt bij netwerkshares, omdat kernelmeldingen deze niet overschrijden. Stel PAPERLESS_CONSUMER_POLLING_INTERVAL in op bijvoorbeeld 30. paperless scant de map dan elke 30 seconden.

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

Ja, docker-compose.sqlite.yml wordt ondersteund en gebruikt minder geheugen. Dat is geschikt voor een kleine VPS. Het verschil wordt merkbaar naarmate uw archief groeit: zoeken in volledige tekst en bulkbewerkingen van tags worden merkbaar trager bij duizenden documenten. Later migreren vereist een export en een import. Kies daarom nu PostgreSQL als u verwacht dat het archief blijft groeien.

Hoeveel schijfruimte heeft een archief met scans werkelijk nodig?

Reken op ongeveer tweemaal de grootte van de bronbestanden. Paperless bewaart het origineel ongewijzigd en slaat daarnaast een tweede OCR-PDF op met een doorzoekbare tekstlaag en kleine miniaturen. 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 de exportmap erbij op als u deze op dezelfde schijf bewaart. Hetzelfde archief neemt dan drie keer zoveel schijfruimte in beslag.

Heb ik de Tika- en Gotenberg-containers nodig?

Alleen als u Word-, Excel- of OpenDocument-bestanden naast uw PDF's wilt laten indexeren. Ze converteren deze indelingen naar PDF, zodat paperless ze met OCR kan verwerken en doorzoeken. Ze voegen ook twee actieve containers en enkele honderden megabytes aan geheugenverbruik toe. Sla ze daarom over op een kleine server als alles wat u archiveert al een PDF of afbeelding is.

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