Skip to main content

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

ToolOpen SourceFree TierMonthly Cost
DocuWareNoNoEnterprise
Google DriveNoLimitedFrom $2.99/mo

Self Hosted

ToolOpen SourceFull FeaturesNotes
PapermergeYesYesSimpler DMS with OCR

Installation

Architecture

  • Deployment: Single paperless deployment in the paperless namespace
  • 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-encrypted and paperless-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) and paperless-media-encrypted (scanned files), both Longhorn-encrypted and annotated k8up.io/backup: "true"
  • Backups: k8up Schedule backs up CNPG and Longhorn PVCs to Hetzner S3. CNPG annotated with k8up.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.

Kubernetes Metadata
  • 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