Paperless-ngx on Docker: Self-Host Your Document Management System (2026 Guide)

Paperless-ngx on Docker: Self-Host Your Document Management System (2026 Guide)

Stop Drowning in Paper: What Paperless-ngx Actually Does

Paper documents are a quiet homelab tax. Bills, warranties, medical records, insurance forms — they pile up, and when you need one, it’s never where you think it is. Paperless-ngx is an open-source document management system that turns that pile into a searchable, tagged, versioned archive you can access from any browser.

The core workflow is simple: scan or photograph a document, drop it in an inbox folder (or email it in), and Paperless-ngx runs OCR on it, classifies it automatically, and files it. Two weeks later when you need that appliance warranty, you search “Samsung warranty” and it’s there in under two seconds. No folders, no filenames to remember, no filing cabinet.

For anyone already running a Docker stack — and if you’ve followed our Docker beginner’s guide — Paperless-ngx is a Saturday afternoon project that pays dividends for years. This guide covers a production-ready deployment with PostgreSQL, Redis, and Tika for superior document parsing, plus the scanner and mobile integrations that make the workflow actually stick.

Architecture Overview

Paperless-ngx runs as several cooperating containers:

  • paperless-ngx: The main web app and task worker (Django/Celery)
  • postgres: Document metadata, tags, correspondents, and full-text search index
  • redis: Task queue between the web process and background workers
  • gotenberg: Converts Office documents, HTML, and other formats to PDF before OCR
  • tika: Apache Tika extracts text from complex formats (DOCX, XLSX, email) that basic OCR misses

This is more containers than a typical homelab service, but each one is lightweight and the combination gives you reliable parsing of nearly any document format you’ll encounter.

Prerequisites

  • Docker Engine 24+ and Docker Compose v2
  • 2+ GB free RAM (Tika and Gotenberg add about 300 MB each at peak)
  • Storage: plan for 1–2 GB per 1,000 scanned pages at 300 DPI
  • A domain or LAN hostname for accessing the web UI

Directory Setup

mkdir -p ~/docker/paperless/{data,media,export,consume,postgres-data}
cd ~/docker/paperless
  • consume/: Drop documents here; Paperless watches this folder and imports automatically
  • media/: Stores the processed document files (PDFs with embedded text)
  • data/: Application state (search index, classifier model)
  • export/: Output directory for bulk exports

Docker Compose Configuration

Create docker-compose.yml:

version: "3.8"

services:
  broker:
    image: redis:7-alpine
    container_name: paperless_redis
    restart: unless-stopped
    volumes:
      - ./redis-data:/data
    networks:
      - paperless_internal

  db:
    image: postgres:16-alpine
    container_name: paperless_postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: paperless
      POSTGRES_USER: paperless
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - ./postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U paperless"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - paperless_internal

  gotenberg:
    image: gotenberg/gotenberg:8.7.0
    container_name: paperless_gotenberg
    restart: unless-stopped
    command:
      - "gotenberg"
      - "--chromium-disable-javascript=true"
      - "--chromium-allow-list=file:///tmp/.*"
    networks:
      - paperless_internal

  tika:
    image: apache/tika:2.9.2.1
    container_name: paperless_tika
    restart: unless-stopped
    networks:
      - paperless_internal

  webserver:
    image: ghcr.io/paperless-ngx/paperless-ngx:2.13.5
    container_name: paperless
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
      broker:
        condition: service_started
      gotenberg:
        condition: service_started
      tika:
        condition: service_started
    environment:
      PAPERLESS_REDIS: redis://broker:6379
      PAPERLESS_DBENGINE: postgresql
      PAPERLESS_DBHOST: db
      PAPERLESS_DBNAME: paperless
      PAPERLESS_DBUSER: paperless
      PAPERLESS_DBPASS: ${POSTGRES_PASSWORD}
      PAPERLESS_TIKA_ENABLED: 1
      PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
      PAPERLESS_TIKA_ENDPOINT: http://tika:9998
      PAPERLESS_SECRET_KEY: ${SECRET_KEY}
      PAPERLESS_URL: https://${PAPERLESS_DOMAIN}
      PAPERLESS_ALLOWED_HOSTS: ${PAPERLESS_DOMAIN}
      PAPERLESS_OCR_LANGUAGE: eng
      PAPERLESS_CONSUMER_POLLING: 60
      PAPERLESS_FILENAME_FORMAT: "{created_year}/{correspondent}/{title}"
      USERMAP_UID: 1000
      USERMAP_GID: 1000
    volumes:
      - ./data:/usr/src/paperless/data
      - ./media:/usr/src/paperless/media
      - ./export:/usr/src/paperless/export
      - ./consume:/usr/src/paperless/consume
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.paperless.rule=Host(`${PAPERLESS_DOMAIN}`)"
      - "traefik.http.routers.paperless.entrypoints=websecure"
      - "traefik.http.routers.paperless.tls.certresolver=letsencrypt"
      - "traefik.http.services.paperless.loadbalancer.server.port=8000"
    networks:
      - paperless_internal
      - proxy

networks:
  paperless_internal:
    driver: bridge
  proxy:
    external: true

Environment Variables

Create .env:

PAPERLESS_DOMAIN=paperless.yourdomain.com
POSTGRES_PASSWORD=strong_random_password_here
SECRET_KEY=another_long_random_string_here

Generate both random strings:

openssl rand -hex 32  # for POSTGRES_PASSWORD
openssl rand -hex 32  # for SECRET_KEY

The PAPERLESS_FILENAME_FORMAT setting controls how files are stored on disk. The format {created_year}/{correspondent}/{title} means your media folder gets a clean hierarchy like 2026/Comcast/Internet Bill March.pdf. This makes manual browsing of the storage directory legible even without the web UI.

Launching Paperless-ngx

cd ~/docker/paperless
docker compose up -d
docker compose logs -f webserver

On first boot, Paperless runs Django migrations and builds the initial search index. This takes 2–3 minutes. Once you see “Listening at: http://0.0.0.0:8000”, create your superuser:

docker exec -it paperless python3 manage.py createsuperuser

Then browse to your domain and log in.

First-Time Configuration

Before importing anything, set up the taxonomy that makes search powerful:

Correspondents

Think of these as senders: Comcast, IRS, Aetna, Wells Fargo, Amazon. Paperless can auto-assign correspondents using rules. Go to Settings → Correspondents → Add and create the ones you bill or hear from regularly.

Document Types

Invoice, Bank Statement, Medical Record, Warranty, Contract, Tax Return. Keep this list short — 6–10 types is enough for most households.

Tags

Use tags for cross-cutting concerns: tax-relevant, medical, vehicle, home, recurring. A document can have multiple tags but only one type and one correspondent.

Auto-classification Rules

This is where the real power lives. Go to Settings → Mail and Document Settings → Document Matching. Create a rule: if the document contains “Comcast” or “XFINITY”, set Correspondent = Comcast, Type = Invoice, add tag = recurring. Once you have 10–15 rules, 80% of your documents will be classified automatically on ingest.

Getting Documents In

The Consume Folder

The ./consume directory is watched every 60 seconds. Drop any PDF, JPEG, PNG, TIFF, or HEIC file there and Paperless imports it. Map this folder as a network share via Samba for easy drag-and-drop from Windows:

# In /etc/samba/smb.conf
[paperless-consume]
   path = /home/user/docker/paperless/consume
   writable = yes
   valid users = youruser

Email Ingestion

Paperless can monitor an IMAP mailbox and import PDF attachments. Create a dedicated email address (e.g., documents@yourdomain.com) and configure it under Settings → Mail. Forward your bills and statements there. This alone eliminates 80% of manual scanning for most people.

# In Paperless web UI: Settings → Mail Account
Host: imap.gmail.com
Port: 993
Username: documents@gmail.com
Password: your-app-password
Folder: INBOX
Action after processing: Mark as read

Mobile Scanning

The iOS and Android Paperless apps connect directly to your instance. For scanning on the go, use the open-source Docus (iOS) or Paperless Mobile (Android) — both support direct upload to your server’s REST API. On iOS, the built-in Files app + document scanner works well with a Shortcuts automation that uploads PDFs to your consume folder via WebDAV.

Storage and Backup Strategy

Documents stored in Paperless are the kind of thing you don’t want to lose. Your backup needs to cover both the Postgres database and the media files:

# Crontab entry for daily backups
# Dump the database
0 2 * * * docker exec paperless_postgres pg_dump -U paperless paperless | gzip > ~/backups/paperless-db-$(date +\%Y\%m\%d).sql.gz

# Export all documents to export/ directory (Paperless's own format)
30 2 * * * docker exec paperless python3 manage.py document_exporter /usr/src/paperless/export --use-filename-format

# Rsync export to a backup location
0 3 * * * rsync -av ~/docker/paperless/export/ user@backup-server:/backups/paperless/

The document_exporter command writes each document as a PDF alongside a JSON sidecar containing all metadata (tags, correspondent, dates). This format is portable — you can rebuild the Paperless database from an export directory even if you lose everything else. That’s meaningfully different from just backing up the Postgres dump, which is useless without the media files.

If you’re running Ansible for homelab management, wrapping these backup steps in a playbook and scheduling it via cron is a clean pattern — your backup logic stays version-controlled and auditable.

Integrating with Uptime Kuma

Paperless exposes a health endpoint at /api/ui_settings/ that returns HTTP 200 when the app is healthy. Add it to Uptime Kuma as an HTTP(S) monitor to get alerted if the service goes down or the container restarts unexpectedly. The URL to monitor:

https://paperless.yourdomain.com/api/ui_settings/

Set an expected status code of 200 and a check interval of 5 minutes. You’ll know instantly if Paperless becomes unavailable rather than discovering it the hard way when you need a document urgently.

The Automatic Classifier

After you’ve manually classified 50–100 documents, Paperless trains a built-in machine learning classifier on your taxonomy. Enable it under Settings → Document Classifier. From that point forward, new documents are pre-classified based on patterns learned from your existing archive — the confidence threshold controls how aggressively it auto-assigns tags and types.

The model retrains automatically in the background as your archive grows. It runs locally, uses no external API, and stores the model in your ./data directory. This is meaningfully different from cloud-based scanning apps that upload your documents to train a shared model — your documents and patterns stay entirely on your hardware.

Sharing Access: Multi-User and Read-Only Setups

Paperless-ngx supports multiple user accounts. If you share a household, create separate accounts for each person under Settings → Users. Documents can be made private (visible only to the owner) or shared with all users. A common setup: one account owns the primary archive with full edit rights, a second account has read-only access for another household member who needs to look up documents but shouldn’t be filing or deleting anything.

For external read-only access from a phone without exposing the full UI, use the REST API directly. The /api/documents/ endpoint returns paginated document metadata with presigned download URLs:

curl -H "Authorization: Token YOUR_API_TOKEN" \
  "https://paperless.yourdomain.com/api/documents/?query=car+insurance&ordering=-created"

The API token lives under Settings → My Profile in the web UI. This is the same endpoint mobile apps use — so any custom tooling you build against it works identically.

Performance Tuning

For typical homelab use (scanning 10–30 documents per day), the default configuration is fine. If you’re doing a large initial import — say, digitizing years of paper — a few tweaks speed things up significantly:

# In docker-compose.yml under webserver environment:
PAPERLESS_TASK_WORKERS: 2        # Default is 1; add a worker for parallel OCR
PAPERLESS_THREADS_PER_WORKER: 2  # OCR threads per worker
PAPERLESS_OCR_PAGES: 0           # OCR all pages (default is 0 = all, set to 1 for cover-only)

Each worker uses about 300–500 MB RAM during active OCR. With 2 workers and 2 threads each, expect 1–2 GB additional RAM usage during bulk import. Throttle back to 1 worker after the initial import if RAM is tight.

Webhooks and Automation Hooks

Paperless-ngx fires configurable webhooks on document events. Under Settings → Workflows, you can trigger an HTTP POST to any URL when a document is added, updated, or matched by a specific rule. Practical uses:

  • POST to a Slack or Telegram bot when a document tagged tax-relevant arrives
  • Trigger a backup script when the document count exceeds a threshold
  • Notify a shared household channel when a bill arrives from a specific correspondent

The webhook payload includes document ID, title, tags, correspondent, and a link directly to the document in the UI. A two-minute workflow in something like Huginn or a simple Flask endpoint can route these to wherever your team notices alerts.

Troubleshooting Common Issues

Documents stuck in consume queue: Check docker compose logs webserver | grep ERROR. Common causes: file permissions (the container runs as UID 1000, so chown -R 1000:1000 ~/docker/paperless/consume), unsupported file format, or Tika not responding.

OCR produces garbled text: Low-quality scans below 150 DPI produce poor OCR. Scan at 300 DPI minimum. The Tesseract OCR engine built into Paperless handles English well; add additional language packs via PAPERLESS_OCR_LANGUAGE: eng+deu if needed.

Email ingestion not working: For Gmail, use an App Password, not your account password. Enable IMAP in Gmail Settings and ensure “Less secure apps” restrictions are handled via App Passwords in your Google account security settings.

Postgres fills disk: The full-text search index grows over time. Run docker exec paperless python3 manage.py search_index --reindex periodically to rebuild it, which reclaims space from stale entries.

Closing Thoughts

Paperless-ngx is one of the few homelab services that changes how you interact with physical reality. Once it’s running and you’ve ingested your backlog, the paper stops accumulating — because there’s a system for it. Warranties get scanned the day they arrive. Tax documents get forwarded to the email inbox. The stack of “file this eventually” disappears.

The setup is more involved than a single-container app, but the Postgres + Redis + Tika stack is stable, well-documented, and battle-tested. Pair it with a proper backup strategy and you have a personal document archive that’s more reliable than any filing cabinet and more searchable than any cloud scanner app.

Start with your most recent year of bills and work backwards. Once you see the first successful OCR result come back with the right correspondent and document type automatically assigned, you’ll understand why this is one of those services that earns its place in the homelab.

Enjoying this post?

Get more guides like this delivered straight to your inbox. No spam, just tech and trails.