Paperless-ngx Server Documentation
Overview
Paperless-ngx is a document management system running on the paperless server. It is deployed using Podman with systemd Quadlet units (rootful, managed by root via systemd). The stack consists of three containers: the Paperless-ngx web application, a PostgreSQL database, and a Redis cache — all communicating over a private internal Podman network.
URL: https://paperless.home.helix9.org
Authentication: Authentik SSO (OpenID Connect)
OCR Languages: German (primary), English
Architecture
┌─────────────────────────────────┐
│ paperless server │
│ │
Internet/LAN │ ┌────────────────────────────┐ │
──────────────:8000──►│ │ paperless-webserver │ │
│ │ (paperless-ngx:latest) │ │
│ └──────────┬─────────────────┘ │
│ │ paperless-internal │
│ ┌──────────┴──────────────────┐ │
│ │ paperless-internal.network │ │
│ └──────────┬──────────┬───────┘ │
│ │ │ │
│ ┌──────────▼──┐ ┌───▼─────────┐ │
│ │ paperless-db│ │paperless- │ │
│ │ (postgres:18│ │redis (redis:│ │
│ │ │ │8) │ │
│ └─────────────┘ └─────────────┘ │
│ │
│ Storage: /HDD/paperless/ │
└─────────────────────────────────┘
Infrastructure
| Component | Details |
|---|---|
| OS disk | 10 GB SSD (btrfs, ~23% used) |
| Data disk | 15 TB HDD mounted at /HDD (~24% used / ~3.5 TB) |
| Container runtime | Podman (rootful, systemd Quadlet) |
| Container auto-update | Enabled via AutoUpdate=registry |
Systemd Quadlet Units
The containers are defined in /etc/containers/systemd/ as Quadlet .container and .network files. Systemd generates service units from these at boot.
Services
| Service | Description | Status |
|---|---|---|
paperless-webserver.service | Main application | Running |
paperless-db.service | PostgreSQL 18 | Running |
paperless-redis.service | Redis 8 cache | Running |
paperless-internal-network.service | Internal Podman network | Active |
Manage with:
# Status
systemctl status paperless-webserver paperless-db paperless-redis
# Restart individual service
systemctl restart paperless-webserver
# Restart full stack
systemctl restart paperless-db paperless-redis paperless-webserver
# Logs
journalctl -u paperless-webserver -f
journalctl -u paperless-db -f
Quadlet File Locations
/etc/containers/systemd/
├── paperless-internal.network # Private network definition
├── paperless-webserver.container # Main app
├── paperless-db.container # PostgreSQL
└── paperless-redis.container # Redis
paperless-webserver.container
[Unit]
Description=Paperless-ngx
After=network-online.target paperless-redis.service paperless-db.service
Wants=network-online.target
Requires=paperless-redis.service paperless-db.service
[Container]
Image=ghcr.io/paperless-ngx/paperless-ngx:latest
AutoUpdate=registry
ContainerName=paperless-ngx
Volume=/HDD/paperless/data:/usr/src/paperless/data
Volume=/HDD/paperless/media:/usr/src/paperless/media
Volume=/HDD/paperless/export:/usr/src/paperless/export
Volume=/HDD/paperless/consume:/usr/src/paperless/consume
EnvironmentFile=/srv/podman/paperless/docker-compose.env
Environment=PAPERLESS_REDIS=redis://paperless-redis:6379
Environment=PAPERLESS_DBHOST=paperless-db
Network=paperless-internal.network
PublishPort=8000:8000
[Service]
Restart=always
[Install]
WantedBy=multi-user.target
paperless-db.container
[Unit]
Description=Paperless PostgreSQL
After=network-online.target
Wants=network-online.target
[Container]
Image=docker.io/library/postgres:18
AutoUpdate=registry
ContainerName=paperless-db
Volume=paperless_pgdata:/var/lib/postgresql
EnvironmentFile=/srv/podman/paperless/docker-compose.env
Environment=POSTGRES_DB=paperless
Environment=POSTGRES_USER=paperless
Environment=POSTGRES_PASSWORD=<see /srv/podman/paperless/docker-compose.env>
Network=paperless-internal.network
[Service]
Restart=always
[Install]
WantedBy=multi-user.target
paperless-redis.container
[Unit]
Description=Paperless Redis
After=network-online.target
Wants=network-online.target
[Container]
Image=docker.io/library/redis:8
AutoUpdate=registry
ContainerName=paperless-redis
Volume=paperless_redisdata:/data
Network=paperless-internal.network
[Service]
Restart=always
[Install]
WantedBy=multi-user.target
Configuration
The main configuration file is at /srv/podman/paperless/docker-compose.env.
Key Settings
| Variable | Value |
|---|---|
PAPERLESS_URL | https://paperless.home.helix9.org |
PAPERLESS_TIME_ZONE | Europe/Berlin |
PAPERLESS_OCR_LANGUAGE | deu (German primary) |
PAPERLESS_OCR_LANGUAGES | deu en |
PAPERLESS_FILENAME_FORMAT | {created_year}-{created_month}-{created_day} - ASN{asn:05} - {title} |
Barcode / ASN Scanner Settings
Paperless is configured to automatically read ASN barcodes from scanned documents:
| Variable | Value |
|---|---|
PAPERLESS_CONSUMER_ENABLE_BARCODES | true |
PAPERLESS_CONSUMER_ENABLE_ASN_BARCODE | true |
PAPERLESS_CONSUMER_ASN_BARCODE_PREFIX | ASN |
PAPERLESS_CONSUMER_BARCODE_SCANNER | ZXING |
PAPERLESS_CONSUMER_BARCODE_UPSCALE | 1.5 |
When a document is dropped into the consume folder, Paperless scans for a QR code with the ASN prefix and assigns the ASN automatically.
SSO (Authentik)
Login is handled via Authentik (OpenID Connect). The Authentik application is at:
https://auth.home.helix9.org/application/o/paperless/
Credentials and OIDC client configuration are stored in /srv/podman/paperless/docker-compose.env.
Storage Layout
All data lives under /HDD/paperless/ on the large HDD:
/HDD/paperless/
├── consume/ # Drop documents here for automatic import
├── data/ # Internal app data (index, ML model, celery schedule)
├── export/ # Manual export outputs
└── media/
└── documents/ # Stored document files
Podman named volumes (for database/redis):
paperless_pgdata # PostgreSQL data
paperless_redisdata # Redis persistence
Consuming Documents
Drop PDF/image files into /HDD/paperless/consume/ — Paperless automatically picks them up, runs OCR, detects ASN barcodes, and files the document.
From the paperless server:
cp document.pdf /HDD/paperless/consume/
Container Auto-Updates
All three containers have AutoUpdate=registry set. Podman's auto-update mechanism checks the registry for newer images and updates automatically.
Manual update:
podman auto-update
Check container status:
podman ps # run as root
ASN Label Printer
An ASN (Archive Serial Number) label printer is set up on the paperless server to print physical labels that get attached to physical documents before scanning. The label contains a QR code and human-readable ASN number.
Hardware: Brother QL-570 USB thermal label printer
Labels: 62×29mm die-cut labels
Location: /home/marko/asn-label-printer/
How It Works
- Run
print-asnon the paperless server to print the next available label - Attach the label to a physical document
- Scan the document into Paperless (via consume folder or scanner)
- Paperless reads the ASN QR code and assigns the matching ASN to the document
- The file is stored as:
YYYY-MM-DD - ASNxxxxx - <title>
Files
/home/marko/asn-label-printer/
├── asn_printer.py # Main script
├── config.json # Counter and printer settings
├── requirements.txt # Python dependencies
├── setup.sh # Initial setup script
├── verify.sh # Hardware/software verification script
├── preview.png # Last generated preview image
└── tests/
└── test_label.py # Test suite
Configuration (config.json)
{
"asn_counter": 101,
"printer_model": "QL-570",
"printer_backend": "pyusb",
"label_size": "62x29",
"asn_prefix": "ASN",
"asn_digits": 5
}
The asn_counter auto-increments with each successful print. ASNs are formatted as ASN00001 through ASN99999.
Usage
The shell is configured to activate the venv and cd into the directory on login. A print-asn alias is available:
# Print next label (auto-increments counter)
print-asn
# Check current counter / next ASN
print-asn --status
# Print a specific ASN (does NOT increment counter)
print-asn -n 42
# Print a range of labels (does NOT increment counter)
print-asn --range 1 50
# Generate a preview PNG without printing
print-asn --preview
# Reset counter to a specific value
print-asn --reset 100
# Use from outside (e.g., via SSH)
ssh paperless "cd ~/asn-label-printer && source venv/bin/activate && ./asn_printer.py"
Label Layout
┌──────────────────────────────┐
│ │
│ ┌─────────┐ │
│ │ █ █ █ │ │
│ │ QR Code│ │
│ │ █ █ █ │ │
│ └─────────┘ │
│ │
│ ASN00101 │
│ │
└──────────────────────────────┘
- QR code: 180×180px, error correction level M, contains plain
ASNxxxxxtext - Text: 56pt bold, centered below QR code
- Total label: 696×271px (62mm×29mm at 300 DPI), 1-bit B&W
Python Dependencies
brother-ql==0.9.4
qrcode[pil]==7.4.2
Pillow>=11.0.0
USB Permissions
A udev rule grants non-root access to the Brother printer:
/etc/udev/rules.d/99-brother-ql.rules:
SUBSYSTEM=="usb", ATTR{idVendor}=="04f9", ATTR{idProduct}=="2028", MODE="0666"
Verification
Run ~/asn-label-printer/verify.sh to check USB connection, udev rules, Python environment, and dependencies:
cd ~/asn-label-printer && bash verify.sh
Troubleshooting
Paperless containers not starting
journalctl -u paperless-webserver --since "1 hour ago"
journalctl -u paperless-db --since "1 hour ago"
systemctl status paperless-webserver paperless-db paperless-redis
Check disk space
df -h /HDD
df -h /
Access container shell
# Run as root on paperless server
podman exec -it paperless-ngx bash
podman exec -it paperless-db psql -U paperless
ASN printer not found
cd ~/asn-label-printer
lsusb | grep Brother # should show 04f9:2028
bash verify.sh # full verification
source venv/bin/activate
./asn_printer.py --status # check counter
If pyusb backend fails, change printer_backend to linux_kernel in config.json:
{
"printer_backend": "linux_kernel"
}
Counter out of sync with Paperless
If the printer counter and Paperless ASN index get out of sync:
# Check next ASN in Paperless UI: Settings → ASN
# Then reset counter to match
cd ~/asn-label-printer
source venv/bin/activate
./asn_printer.py --reset <next_asn_number>