Paperless-ngx is a self-hosted document inbox that:
- Ingests scans and PDFs (via upload, a “consume” folder, or even apps)
- Runs OCR so documents become searchable
- Organizes using tags, correspondents, and document types
- Lets you search your archive like a personal document index
If you want to keep personal paperwork (tax docs, medical, contracts, receipts) under your control, Paperless is the way to go.
Core Services
- Paperless-ngx – the document scanning and index system
- Postgres – database backend
- Redis – queue/cache
- Gotenberg – document conversion (Office/Excel → PDF, etc.)
- Tika – text extraction helpers
- Ollama – local model runtime
- Open WebUI – model management + testing UI + chat
- Paperless-AI – metadata suggestions (tags/titles/etc.)
- Paperless-GPT – vision-model OCR + metadata suggestions
- Dozzle – lightweight log viewer, highly recommended for container troubelshooting
Document Flow
Paperless-ngx ↔ (Paperless-AI / Paperless-GPT) ↔ Ollama
- Paperless stores and indexes your docs.
- Ollama runs the LLMs locally.
- Paperless-AI / Paperless-GPT are add-ons that call Ollama and write results back to Paperless.
- AI can enhance correspondents, tags, and text recognition (OCR)
Prerequisites
- A Linux server (VM, mini PC, NAS — really anything that can run Docker)
- Docker + Docker Compose
- Optional but recommended for vision OCR: (an NVIDIA GPU)
- If using an NVIDIA GPU, be sure you have the proper drivers and NVIDIA Container Toolkit installed
Ports Used in This Guide
This is the exact port map used in the stack below:
| Service | Port | URL |
|---|---|---|
| Paperless-ngx | 8000 | http://<server-ip>:8000 |
| Paperless-AI | 3000 | http://<server-ip>:3000 |
| Open WebUI | 3001 | http://<server-ip>:3001 |
| Paperless-GPT | 3002 | http://<server-ip>:3002 |
| Dozzle (logs) | 8080 | http://<server-ip>:8080 |
Install Nvida GPU
# Set Ubuntu version for Nvidia driver, there are no driver for 25.10 yet, use 24.04 instead
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey |
sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://nvidia.github.io/libnvidia-container/stable/deb/amd64 /" |
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt update -y && sudo apt install -y nvidia-container-toolkit
# Configure Docker for Nvidia
sudo nvidia-ctk runtime configure --runtime=docker
# Restart Docker
sudo systemctl restart docker
# Check Docker see runtime
docker info | grep -i nvidia
# Check to see if Ubuntu see Nvidia GPU
lspci | grep -i nvidia
# Install the recommended driver, e.g.:
sudo apt install -y nvidia-driver-580-open
# Reboot Ubuntu
sudo reboot now
# Test Docker container for Nvidia driver
docker run -it --rm --gpus all ubuntu nvidia-smi
Create the Folder Structure
cd /opt
sudo mkdir -p
paperless/{data,media,export,consume,hooks,trash}
postgres/data
redis/data
gotenberg
tika
paperless-ai/data
open-webui/data
ollama/{data,models}
paperless-gpt/prompts
dozzle/data
sudo chown yourlogin:yourgroup
/opt/dozzle
/opt/gotenberg
/opt/ollama
/opt/open-webui
/opt/paperless
/opt/paperless-ai
/opt/paperless-gpt
/opt/postgres
/opt/redis
/opt/tika -R
Docker Compose File
Create a docker-compose.yml file in /opt and place the contents of the compose file:
# Docker Compose for Paperless-ngx
#version: "3.8"
services:
broker:
image: docker.io/library/redis:8
command:
- /bin/sh
- -c
- redis-server
container_name: paperless-redis
hostname: redis
# mem_limit: 512m
# mem_reservation: 256m
# cpu_shares: 768
security_opt:
- no-new-privileges:true
read_only: true
user: 108:110
healthcheck:
test: ["CMD-SHELL", "redis-cli ping || exit 1"]
volumes:
- ./redis/data:/data
environment:
TZ: Australia/Sydney
restart: unless-stopped
# postgres database for paperless-ngx
db:
image: postgres:18
container_name: paperless-db
hostname: postgres
user: 108:110
security_opt:
- no-new-privileges:true
healthcheck:
# test: ["CMD-SHELL", "pg_isready -U paperless"]
test: ["CMD", "pg_isready", "-q", "-d", "paperless", "-U", "paperless"]
timeout: 45s
interval: 10s
retries: 10
restart: unless-stopped
volumes:
- ./postgres/data:/var/lib/postgresql
environment:
TZ: Australia/Sydney
POSTGRES_DB: paperless
POSTGRES_USER: paperless
POSTGRES_PASSWORD: database_passwd
paperless:
# paperless-ngx main service
image: ghcr.io/paperless-ngx/paperless-ngx:latest
container_name: paperless-web
hostname: paperless-ngx
mem_limit: 10g
cpu_shares: 1024
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD", "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000"]
interval: 30s
timeout: 10s
retries: 5
restart: unless-stopped
depends_on:
broker:
condition: service_healthy
db:
condition: service_healthy
tika:
condition: service_started
gotenberg:
condition: service_started
ports:
- "8010:8000" # Web UI
volumes:
- ./data:/usr/src/paperless/data # Application data
- ./media:/usr/src/paperless/media # Stored documents
- ./export:/usr/src/paperless/export # Exported documents
- ./consume:/usr/src/paperless/consume # Drop folder for new docs
- ./trash:/usr/src/paperless/trash # Trash bin
- ./hooks:/usr/src/paperless/scripts
environment:
PAPERLESS_REDIS: redis://redis:6379
PAPERLESS_DBENGINE: postgres
PAPERLESS_DBHOST: paperless-db
PAPERLESS_DBNAME: paperless
PAPERLESS_DBUSER: paperless
PAPERLESS_DBPASS: database_passwd
# Admin credentials for first login
PAPERLESS_ADMIN_USER: web_username
PAPERLESS_ADMIN_PASSWORD: web_passwd
PAPERLESS_ADMIN_MAIL: admin_email
USERMAP_UID: 108
USERMAP_GID: 110
# OCR language(s) - use ISO 639 codes
# Install additional Tesseract language packs if needed
PAPERLESS_OCR_LANGUAGE: eng
# Multiple languages: PAPERLESS_OCR_LANGUAGE: eng+deu+fra
# OCR mode: skip (only OCR non-searchable PDFs), redo, force
PAPERLESS_OCR_MODE: skip
# Time zone for correct date parsing
PAPERLESS_TIME_ZONE: Australia/Sydney
# URL - important for correct links in the UI
PAPERLESS_URL: https://docs.trendsit.com
PAPERLESS_CSRF_TRUSTED_ORIGINS: https://docs.trendsit.com
PAPERLESS_ALLOWED_HOST: "localhost,10.254.254.30"
# Secret key for Django security
PAPERLESS_SECRET_KEY: your_own_secretkey
# Worker settings
PAPERLESS_TASK_WORKERS: 2
PAPERLESS_THREADS_PER_WORKER: 2
PAPERLESS_OCR_ROTATE_PAGES_THRESHOLD: 6
# IMAP email consumption (optional)
# PAPERLESS_EMAIL_TASK_CRON: "*/10 * * * *"
PAPERLESS_EMPTY_TRASH_DIR: ../trash
# Filename format for stored documents
PAPERLESS_FILENAME_FORMAT: "{created_year}/{correspondent}/{title}"
# Enable barcode processing for automatic document splitting
PAPERLESS_CONSUMER_ENABLE_BARCODES: "true"
PAPERLESS_CONSUMER_ENABLE_ASN_BARCODE: true # If you would like QR-ASN-Codes
# PAPERLESS_CONSUMER_BARCODE_SCANNER: ZXING ' ZXING as Scanner
# Tika and Gotenberg for Office document support
PAPERLESS_TIKA_ENABLED: 1
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
# Gotenberg converts Office documents to PDF
gotenberg:
image: docker.io/gotenberg/gotenberg:latest
container_name: paperless-gotenberg
hostname: gotenberg
security_opt:
- no-new-privileges:true
user: 108:110
command:
- "gotenberg"
- "--chromium-disable-javascript=true"
- "--chromium-allow-list=file:///tmp/.*"
environment:
TZ: Australia/Sydney
restart: unless-stopped
# Tika extracts text from Office documents
tika:
image: docker.io/apache/tika:latest
container_name: paperless-tika
hostname: tika
security_opt:
- no-new-privileges:true
user: 108:110
environment:
TZ: Australia/Sydney
restart: unless-stopped
# open-webui service for LLM interaction
open-webui:
image: ghcr.io/open-webui/open-webui:latest
container_name: open-webui
restart: unless-stopped
environment:
TZ: Australia/Sydney
OLLAMA_BASE_URL: http://ollama:11434
depends_on:
- ollama
ports:
- "3001:8080"
volumes:
- ./open-webui/data:/app/backend/data
# ollama service for local LLMs
ollama:
image: ollama/ollama:latest
container_name: ollama
environment:
TZ: Australia/Sydney
OLLAMA_KEEP_ALIVE: 15m
# NVIDIA_DRIVER_CAPABILITIES=all # comment out if you aren't using NVIDIA GPUs
# NVIDIA_VISIBLE_DEVICES=all # comment out if you aren't using NVIDIA GPUs
OLLAMA_HOST: 0.0.0.0:11434
OLLAMA_MODELS: ollama-models
volumes:
- ./ollama/data/:/root/.ollama
- ./ollama/models:/ollama-models
restart: unless-stopped
# deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: all
# capabilities: [gpu]
# paperless-ai service
paperless-ai:
image: clusterzx/paperless-ai:latest
container_name: paperless-ai
restart: unless-stopped
depends_on:
- ollama
- paperless
ports:
- "3000:3000"
environment:
TZ: Australia/Sydney
PAPERLESS_API_URL: http://paperless:8000/api
PAPERLESS_API_TOKENL: your_paperless_token # change to your paperless API token
PAPERLESS_USERNAME: web_username # change to your paperless username
AI_PROVIDER: ollama
OLLAMA_API_URL: http://ollama:11434
OLLAMA_MODEL: llama3.2:3b
RAG_SERVICE_URL: http://localhost:8000
RAG_SERVICE_ENABLED: true
SCAN_INTERVAL: 30
PAPERLESS_URL: http://paperless:8000
volumes:
- ./paperless-ai/data:/app/data
# paperless-gpt service
paperless-gpt:
image: icereed/paperless-gpt:latest
container_name: paperless-gpt
restart: unless-stopped
depends_on:
- ollama
- paperless
ports:
- "3002:8080"
environment:
TZ: Australia/Sydney
PAPERLESS_BASE_URL: "http://paperless:8000"
PAPERLESS_API_TOKEN: your_api_token # change to your paperless API token
# PAPERLESS_PUBLIC_URL: "http://paperless.mydomain.com" # Optional
LLM_PROVIDER: "ollama"
LLM_MODEL: "llama3.2:3b"
OLLAMA_HOST: "http://ollama:11434"
OLLAMA_CONTEXT_LENGTH: "8192" # Sets Ollama NumCtx (context window)
TOKEN_LIMIT: 1000 # Recommended for smaller models
LLM_LANGUAGE: "English" # Optional, default: English
OCR_PROVIDER: "llm" # Default OCR provider
VISION_LLM_PROVIDER: "ollama" # openai, ollama, mistral, or anthropic
VISION_LLM_MODEL: "minicpm-v:8b" # minicpm-v (ollama) or gpt-4o (openai) or claude-sonnet-4-5 (anthropic/claude)
AUTO_OCR_TAG: "paperless-gpt-ocr-auto"
AUTO_TAG: "paperless-gpt-auto"
MANUAL_TAG: "paperless-gpt-manual"
PDF_OCR_TAGGING: "true"
PDF_OCR_COMPLETE_TAG: "paperless-gpt-ocr-complete"
PDF_UPLOAD: "false"
LOG_LEVEL: "DEBUG"
volumes:
- ./paperless-gpt/prompts:/app/prompts
# optional but helpful log viewer
dozzle:
image: amir20/dozzle:latest
restart: unless-stopped
container_name: dozzle
environment:
TZ: Australia/Sydney
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./dozzle/data:/data
ports:
- "8080:8080"
Start Everything
Inside of the folder with your compose file:
docker compose up -d
Open Firewall Ports
sudo ufw allow 8000/tcp
sudo ufw allow 3000/tcp
sudo ufw allow 3001/tcp
sudo ufw allow 3002/tcp
sudo ufw allow 8080/tcp
sudo ufw allow 11434/tcp
