homelab-blueprint/docker-compose-files/paperless-ngx
Brian Pooe cc6cdebba2 fix(paperless-ngx): keep SQLite database and index on local disk
Only media/consume/export/trash stay on the UNAS share; SQLite over
NFS risks lock contention and corruption.
2026-07-03 18:38:50 +02:00
..
.env.sample feat(paperless-ngx): store documents on the UNAS paperless_data share 2026-07-03 18:11:49 +02:00
README.md fix(paperless-ngx): keep SQLite database and index on local disk 2026-07-03 18:38:50 +02:00
template.yaml fix(paperless-ngx): keep SQLite database and index on local disk 2026-07-03 18:38:50 +02:00

Paperless-ngx

Document management system that scans, indexes, and archives paper documents with full-text OCR search. Runs with the bundled SQLite database and a Redis task broker.

Persistent Data

Split between local disk and the UNAS paperless_data share (see nfs-mounts):

  • paperless_data named volume (local disk) — SQLite database, search index, and classification model. Kept off NFS because SQLite file locking over NFS risks corruption and slow queries.
  • Share dirs, NFS-mounted on the Proxmox host and bind-mounted into the LXC at PAPERLESS_DATA_ROOT:
    • media/ — original and archived documents.
    • consume/ — watched for new documents; safe to empty once ingested.
    • export/ — target for document_exporter backups.
    • trash/ — deleted documents until emptied.

The share binds use create_host_path: false, so the stack refuses to start if the share is not mounted. Redis broker state is in the paperless_redis_data named volume.

Installation

  1. Create .env if it does not exist:

    cp -n .env.sample .env
    
  2. Set the required values in .env:

    TZ=Africa/Johannesburg
    PAPERLESS_URL=https://paperless-ngx.home.example.com
    PAPERLESS_SECRET_KEY=<random-secret>   # openssl rand -base64 48
    PAPERLESS_DATA_ROOT=/mnt/unas/paperless_data
    PUID=1000
    PGID=1000
    
    DOCKERLOGGING_MAXFILE=3
    DOCKERLOGGING_MAXSIZE=10m
    

    PAPERLESS_URL must match the URL used in the browser, otherwise CSRF checks fail behind the reverse proxy.

  3. Ensure the share is bind-mounted into the LXC and owned by the container user (host-side UID for an unprivileged LXC is LXC_IDMAP_BASE + PUID):

    # On the Proxmox host
    pct set <vmid> -mp0 /mnt/unas/paperless_data,mp=/mnt/unas/paperless_data
    mkdir -p /mnt/unas/paperless_data/{data,media,consume,export,trash}
    chown -R 101000:101000 /mnt/unas/paperless_data
    
  4. Deploy with make:

    make deploy-paperless
    

    Manual equivalent:

    ./substitute_env.sh docker-compose-files/paperless-ngx/template.yaml docker-compose.paperless.yml .env
    docker compose -f docker-compose.paperless.yml config --quiet
    docker compose -f docker-compose.paperless.yml up -d
    
  5. Create the first user, unless PAPERLESS_ADMIN_USER is set in .env:

    docker exec -it paperless createsuperuser
    
  6. Verify the containers and open http://<host-ip>:8000:

    docker compose -f docker-compose.paperless.yml ps
    docker compose -f docker-compose.paperless.yml logs --tail=100 paperless
    

Reverse Proxy

The Caddyfile proxies paperless-ngx.<domain> to this host on port 8000 with header_up Host {host} so CSRF origin checks pass. After changing the port, update docker-compose-files/caddy/Caddyfile_template and run make reload-caddy.

Backup

Run the built-in exporter, then back up PAPERLESS_DATA_ROOT/export (it contains documents plus a manifest that restores tags, correspondents, and metadata):

docker exec paperless document_exporter /usr/src/paperless/export --zip

Useful Commands

# Tail logs
docker compose -f docker-compose.paperless.yml logs -f paperless

# Retrain the document classifier
docker exec paperless document_create_classifier

# Re-run OCR on a document
docker exec paperless document_archiver --overwrite --document <id>