homelab-blueprint/docker-compose-files/paperless-ngx
Brian Pooe 4eb6d6ee04 feat(paperless-ngx): store documents on the UNAS paperless_data share
Bind data/media/consume/export/trash from PAPERLESS_DATA_ROOT with
create_host_path: false so the stack refuses to start when the NFS
share is not mounted. Wire PAPERLESS_TRASH_DIR.
2026-07-03 18:11:49 +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 feat(paperless-ngx): store documents on the UNAS paperless_data share 2026-07-03 18:11:49 +02:00
template.yaml feat(paperless-ngx): store documents on the UNAS paperless_data share 2026-07-03 18:11:49 +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

All document state lives on the UNAS paperless_data share (see nfs-mounts), NFS-mounted on the Proxmox host and bind-mounted into the LXC at PAPERLESS_DATA_ROOT:

  • data/ — database, search index, classification model.
  • 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 compose binds use create_host_path: false, so the stack refuses to start if the share is not mounted. Only the Redis broker state is in a Docker named volume (paperless_redis_data).

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>