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 apg_ctlcluster-style versioned subdirectory under that mount point, so pointing the volume at the old.../datapath 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-alpineas 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.envtab, holds all persistent data (db, media, consume folder, etc.)${PAPERLESS_DB_NAME}/${PAPERLESS_DB_USER}/${PAPERLESS_DB_PASSWORD}— also from the Dockge.envtab, so the real password never sits in the compose file itself; used by bothpaperless-dbandpaperless-webserver, and must stay in sync since it’s the same credential on both sidesPAPERLESS_OCR_LANGUAGE=nld+eng— OCR runs both languages per document;PAPERLESS_OCR_LANGUAGES=nldtriggers 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 thePUID/PGIDconvention used by LinuxServer images; Postgres and Redis manage their own internal permissions and shouldn’t get a forceduser:directive, same reasoning as the Home Assistant case
First Run
- Bring the stack up in Dockge, wait for
paperless-webserverto report healthy (first boot installs the Dutch OCR pack, so it takes a minute or two longer than usual) - Create the admin user:
docker exec -it paperless-webserver python3 manage.py createsuperuser
- Log in at
http://192.168.50.203:8504
NPM Reverse Proxy (local only)
In NPM, add a proxy host:
| Field | Value |
|---|---|
| Domain | paperless.home.lan |
| Forward host | 192.168.50.203 |
| Forward port | 8504 |
| Websockets support | On |
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
| Symptom | Likely cause |
|---|---|
| OCR fails or produces gibberish on Dutch documents | PAPERLESS_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 NPM | PAPERLESS_URL doesn’t match the hostname you’re browsing to |
| Office docs (docx/xlsx) fail to import | Gotenberg or Tika container isn’t healthy; check both are up before blaming the webserver |
| Consume folder not picking up files | Permission mismatch — files need to be readable by UID 950 (or whatever USERMAP_UID is set to) |