Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Paperless-ngx Setup

Self-hosted document management with OCR, running locally via Dockge, reachable at paperless.home.lan through NPM. Not exposed externally.


Docker Compose

Paperless needs Postgres, Redis, and (for Office doc conversion) Gotenberg + Tika alongside the webserver, so it’s kept as one self-contained stack.

services:
  paperless-db:
    image: postgres:18
    container_name: paperless-db
    restart: unless-stopped
    volumes:
      - ${DATA_PATH}/paperless/db:/var/lib/postgresql
    environment:
      - POSTGRES_DB=${PAPERLESS_DB_NAME}
      - POSTGRES_USER=${PAPERLESS_DB_USER}
      - POSTGRES_PASSWORD=${PAPERLESS_DB_PASSWORD}
      - TZ=Europe/Brussels

  paperless-redis:
    image: docker.io/library/redis:7
    container_name: paperless-redis
    restart: unless-stopped
    volumes:
      - ${DATA_PATH}/paperless/redis:/data

  paperless-gotenberg:
    image: docker.io/gotenberg/gotenberg:8.34
    container_name: paperless-gotenberg
    restart: unless-stopped
    command:
      - "gotenberg"
      - "--chromium-disable-javascript=true"
      - "--chromium-allow-list=file:///tmp/.*"

  paperless-tika:
    image: docker.io/apache/tika:3.3.1.0
    container_name: paperless-tika
    restart: unless-stopped

  paperless-webserver:
    image: ghcr.io/paperless-ngx/paperless-ngx:latest
    container_name: paperless-webserver
    restart: unless-stopped
    depends_on:
      - paperless-db
      - paperless-redis
      - paperless-gotenberg
      - paperless-tika
    ports:
      - 8504:8000
    volumes:
      - ${DATA_PATH}/paperless/data:/usr/src/paperless/data
      - ${DATA_PATH}/paperless/media:/usr/src/paperless/media
      - ${DATA_PATH}/paperless/export:/usr/src/paperless/export
      - ${DATA_PATH}/paperless/consume:/usr/src/paperless/consume
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    environment:
      - USERMAP_UID=950
      - USERMAP_GID=950
      - PAPERLESS_TIME_ZONE=Europe/Brussels
      - PAPERLESS_REDIS=redis://paperless-redis:6379
      - PAPERLESS_DBHOST=paperless-db
      - PAPERLESS_DBNAME=${PAPERLESS_DB_NAME}
      - PAPERLESS_DBUSER=${PAPERLESS_DB_USER}
      - PAPERLESS_DBPASS=${PAPERLESS_DB_PASSWORD}
      - PAPERLESS_OCR_LANGUAGE=nld+eng
      - PAPERLESS_OCR_LANGUAGES=nld
      - PAPERLESS_TIKA_ENABLED=1
      - PAPERLESS_TIKA_GOTENBERG_ENDPOINT=http://paperless-gotenberg:3000
      - PAPERLESS_TIKA_ENDPOINT=http://paperless-tika:9998
      - PAPERLESS_URL=http://paperless.home.lan
      - PAPERLESS_SECRET_KEY=${PAPERLESS_SECRET_KEY}
    labels:
      - homepage.group=Homelab
      - homepage.name=Paperless-ngx
      - homepage.icon=paperless-ngx.png
      - homepage.href=http://192.168.50.203:8504/
      - homepage.description=Document management & OCR

Add these to the stack’s .env tab in Dockge, alongside your existing DATA_PATH/CONFIG_PATH:

PAPERLESS_DB_NAME=paperless
PAPERLESS_DB_USER=paperless
PAPERLESS_DB_PASSWORD=<pick a real secret>
PAPERLESS_SECRET_KEY=<generate with the command below>

Generate the secret key on any machine with Python:

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

Paste the output in as PAPERLESS_SECRET_KEY. It just needs to be long and random, not memorable, since it’s used for session tokens.

  • Postgres 18 — mounted at /var/lib/postgresql, not /var/lib/postgresql/data. Starting with 18, the official image stores data in a pg_ctlcluster-style versioned subdirectory under that mount point, so pointing the volume at the old .../data path leaves it looking at an empty, unused mount and the container exits immediately. This matches paperless-ngx’s own current compose file, not an arbitrary pin. Fine for a fresh install; the migration headaches reported for 16→18 only apply if you’re moving an existing database, not starting new
  • Redis, not Valkey — upstream’s current template uses valkey/valkey:9-alpine as the broker now, but Redis still works fine and keeping it matches what the rest of your stack already runs
  • ${DATA_PATH} — from the Dockge .env tab, holds all persistent data (db, media, consume folder, etc.)
  • ${PAPERLESS_DB_NAME} / ${PAPERLESS_DB_USER} / ${PAPERLESS_DB_PASSWORD} — also from the Dockge .env tab, so the real password never sits in the compose file itself; used by both paperless-db and paperless-webserver, and must stay in sync since it’s the same credential on both sides
  • PAPERLESS_OCR_LANGUAGE=nld+eng — OCR runs both languages per document; PAPERLESS_OCR_LANGUAGES=nld triggers the Dutch Tesseract pack to install on first boot, since only a handful of languages ship in the base image
  • Port 8504 — grouped with your other document utilities; double check nothing else already sits there, since the service inventory is only as complete as what’s documented per-file
  • USERMAP_UID/USERMAP_GID — this is Paperless’s own mechanism, distinct from the PUID/PGID convention used by LinuxServer images; Postgres and Redis manage their own internal permissions and shouldn’t get a forced user: directive, same reasoning as the Home Assistant case

First Run

  1. Bring the stack up in Dockge, wait for paperless-webserver to report healthy (first boot installs the Dutch OCR pack, so it takes a minute or two longer than usual)
  2. Create the admin user:
docker exec -it paperless-webserver python3 manage.py createsuperuser
  1. Log in at http://192.168.50.203:8504

NPM Reverse Proxy (local only)

In NPM, add a proxy host:

FieldValue
Domainpaperless.home.lan
Forward host192.168.50.203
Forward port8504
Websockets supportOn

No SSL needed for .home.lan, unless you’re already terminating TLS locally for other services. Update PAPERLESS_URL in the compose file to match whatever hostname you settle on, since Paperless checks the Origin header against it.


Document Intake

Watch folder (SMB drop): Share ${DATA_PATH}/paperless/consume out via a TrueNAS SMB share (or reuse an existing share and symlink it in). Anything dropped there gets consumed and deleted automatically within a few seconds.

Email import (IMAP): This is configured inside the app, not in compose. Log in, go to Settings → E-mail, add the account, then create a rule (mailbox, filter, and what to do with the mail afterward — mark read, move, delete). Paperless polls it periodically once a rule exists.


Troubleshooting

SymptomLikely cause
OCR fails or produces gibberish on Dutch documentsPAPERLESS_OCR_LANGUAGES pack didn’t install; check paperless-webserver logs on first boot, or exec in and run apt list --installed | grep tesseract-ocr-nld
Login redirects or CSRF errors through NPMPAPERLESS_URL doesn’t match the hostname you’re browsing to
Office docs (docx/xlsx) fail to importGotenberg or Tika container isn’t healthy; check both are up before blaming the webserver
Consume folder not picking up filesPermission mismatch — files need to be readable by UID 950 (or whatever USERMAP_UID is set to)