Paperless-ngx
A document management system that digitizes and organizes physical and digital documents.
Paperless-ngx is an open-source document management system that scans, OCRs, tags, and indexes your documents for full-text search. It automatically classifies documents using machine learning and organizes them into a searchable archive. Self-hosting eliminates subscription costs and keeps sensitive documents off third-party servers.
Alternatives considered
Cloud Hosted
| Tool | Open Source | Free Tier | Monthly Cost |
|---|---|---|---|
| DocuWare | No | No | Enterprise |
| Google Drive | No | Limited | From $2.99/mo |
Self Hosted
| Tool | Open Source | Full Features | Notes |
|---|---|---|---|
| Papermerge | Yes | Yes | Simpler DMS with OCR |
Installation
Architecture
- Deployment: Single
paperlessdeployment in thepaperlessnamespace - Images:
paperlessngx/paperless-ngx:2.20.13,valkey/valkey:9.0.3-alpine(both digest-pinned) - Cache/Queue: Valkey for Celery task queue
- Database: CNPG PostgreSQL cluster with Longhorn-encrypted PVCs
- Storage: Two Longhorn PVCs —
paperless-data-encryptedandpaperless-media-encrypted - Networking: HTTPRoute via internal gateway
Security
- Runs as
runAsUser: 999,runAsNonRoot: true,allowPrivilegeEscalation: false, capabilities dropped - All PVCs encrypted at rest via SOPS-managed keys
Updates
Managed by Renovate. Both images are digest-pinned.
Data Management
- Database: CNPG PostgreSQL cluster (Longhorn-encrypted PVCs)
- PVCs:
paperless-data-encrypted(documents/config) andpaperless-media-encrypted(scanned files), both Longhorn-encrypted and annotatedk8up.io/backup: "true" - Backups: k8up
Schedulebacks up CNPG and Longhorn PVCs to Hetzner S3. CNPG annotated withk8up.io/backupcommand: pg_dump.
User Management
No OIDC configured. Users managed through the Paperless-ngx admin UI. Registration and superuser settings configurable via env vars.
Configuration Management
- Database credentials and secret key from SOPS-encrypted secret
- Database host/name/user injected from CNPG-generated secret
- Longhorn encryption keys from SOPS-encrypted secrets
Administration
Usage
Upload or scan documents via the web UI, email ingestion, or the mobile app. Paperless-ngx OCRs each document and enables full-text search. Use tags, document types, and correspondents to organize your archive. The consumption folder monitors for new files to process automatically.
Cluster-specific deviations from the above live in the per-cluster README — see k8s/apps/talos/paperless/README.md.
Cluster Deployment
Paperless-ngx — Talos cluster
Cluster-specific notes only. General product info, "why we use it", and alternatives live in docusaurus/docs/apps/paperless.mdx.
Deviations from defaults
Defaults live in docusaurus/docs/apps/paperless.mdx — document anything this cluster does differently here, with a one-line reason.
- Image:
paperlessngx/paperless-ngx:2.20.15@sha256:6c86cad803970ea782683a8e80e7403444c5bf3cf70de63b4d3c8e87500db92f - Image:
valkey/valkey:9.1.0-alpine@sha256:a35428eba9043cc0b79dbe54100f0c92784f2de00ad09b01182bfb1c5c83d1bd
Rendered manifests (kustomize build)
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
kustomize.toolkit.fluxcd.io/force: enabled
labels:
app: paperless
name: paperless
namespace: paperless
spec:
replicas: 1
selector:
matchLabels:
app: paperless
homepage: active
ingress: public
strategy:
rollingUpdate: null
type: Recreate
template:
metadata:
labels:
app: paperless
homepage: active
ingress: public
spec:
containers:
- env:
- name: PAPERLESS_DBENGINE
value: postgresql
- name: PAPERLESS_DBHOST
valueFrom:
secretKeyRef:
key: host
name: cnpg-app
- name: PAPERLESS_DBNAME
valueFrom:
secretKeyRef:
key: dbname
name: cnpg-app
- name: PAPERLESS_DBUSER
valueFrom:
secretKeyRef:
key: user
name: cnpg-app
- name: PAPERLESS_DBPASS
valueFrom:
secretKeyRef:
key: password
name: cnpg-app
- name: TZ
value: America/Anchorage
- name: PAPERLESS_PORT
value: '8000'
- name: PAPERLESS_REDIS
value: redis://valkey:6379
envFrom:
- secretRef:
name: paperless
image: paperlessngx/paperless-ngx:2.20.15@sha256:6c86cad803970ea782683a8e80e7403444c5bf3cf70de63b4d3c8e87500db92f
livenessProbe:
failureThreshold: 5
httpGet:
path: /
port: 8000
initialDelaySeconds: 15
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 5
name: paperless
ports:
- containerPort: 8000
name: web
protocol: TCP
readinessProbe:
failureThreshold: 5
httpGet:
path: /
port: 8000
initialDelaySeconds: 15
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 2
volumeMounts:
- mountPath: /usr/src/paperless/data
name: paperless-data
- mountPath: /usr/src/paperless/media
name: paperless-media
- mountPath: /data
name: storage-volume
terminationGracePeriodSeconds: 60
volumes:
- name: paperless-data
persistentVolumeClaim:
claimName: paperless-data-encrypted
- name: paperless-media
persistentVolumeClaim:
claimName: paperless-media-encrypted
- emptyDir: {}
name: storage-volume
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: valkey
namespace: paperless
spec:
replicas: 1
selector:
matchLabels:
app: valkey
serviceName: valkey
template:
metadata:
labels:
app: valkey
spec:
containers:
- args:
- valkey-server
image: valkey/valkey:9.1.0-alpine@sha256:a35428eba9043cc0b79dbe54100f0c92784f2de00ad09b01182bfb1c5c83d1bd
livenessProbe:
initialDelaySeconds: 10
periodSeconds: 10
tcpSocket:
port: 6379
name: valkey
ports:
- containerPort: 6379
name: client
readinessProbe:
initialDelaySeconds: 3
periodSeconds: 5
tcpSocket:
port: 6379
resources:
limits:
memory: 512Mi
requests:
cpu: 50m
memory: 128Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
privileged: false
volumeMounts:
- mountPath: /conf
name: conf
- mountPath: /data
name: data
securityContext:
fsGroup: 1000
fsGroupChangePolicy: OnRootMismatch
runAsGroup: 1000
runAsNonRoot: true
runAsUser: 999
seccompProfile:
type: RuntimeDefault
volumes:
- emptyDir: {}
name: conf
- emptyDir: {}
name: data