Paperless-ngx on the homelab
One Paperless-ngx instance, SQLite, and Valkey in the paperless-ngx namespace. Documents, database, index, and inbox are on one CephFS PVC. The app is served at https://paper.brunner.ninja through Traefik. Back up the PVC and the paperless-secret-key Secret together.
Install
Run ./install.sh from any directory. It creates the namespace and random, persistent application and OIDC secrets on first install, creates the Authentik OIDC application, checks the manifests with a server dry run, applies them, then waits for both deployments. Open the URL and sign in with the authentik button. Drop scanned files in the web UI or into /usr/src/paperless/consume on the PVC.
Change settings in app.yaml, then run ./test.sh and ./install.sh. The Paperless image is pinned there. The CephFS inbox uses polling because network filesystems may not deliver file notifications.
Paperless prefers the 24-core arschrock node for OCR. This is a scheduler preference, so it can run on an Odroid when arschrock is unavailable. OCR is capped at four concurrent pages and the pod has a 4 GiB memory limit; using the host's default 24 OCR workers caused an out-of-memory restart while editing a large PDF. The control-plane taint is tolerated only by Paperless, not Valkey. A node failure may briefly interrupt OCR while Kubernetes reschedules the single Paperless pod.
The 20 Gi PVC uses rook-cephfs-ec, whose CephFS data pool is erasure coded with three data and two coding chunks across hosts. CephFS metadata uses three replicas. The claim can be expanded if high-resolution scans outgrow it; erasure coding does not replace backups.
CI/CD
./test.sh checks syntax and the deployment manifest. ./deploy.sh updates the existing workloads, services, and ingress using app.yaml. The GitLab and Gitea workflows call these same scripts. Bootstrap once with ./create-ci-kubeconfig.sh, then save its single output line as the protected CI variable / Gitea Actions secret KUBE_CONFIG_BASE64. Do not commit it. The deployer has named-object read and patch permissions and cannot read Secrets. Run ./install.sh manually for first installation or changes to storage.yaml.
The Git remote is gitea@brunner.ninja:feedc0de/paperless-ngx-deployment.git. No credentials or generated kubeconfig are committed. To publish changes: git add . && git commit -m 'Deploy Paperless-ngx' && git push origin main.
Authentik login
./setup-authentik.sh creates the OAuth2/OpenID Connect provider, application, and Paperless Users and Paperless Admins groups in Authentik, adds feedc0de to both groups, and stores the client secret in the paperless-oidc Kubernetes Secret. It preserves that secret on later runs. ./install.sh invokes it automatically. To grant others access, add them to Paperless Users in Authentik's web UI; use Paperless Admins only for people who should see and administer all documents. Paperless syncs those Authentik group claims at login, including administrator status. Existing Paperless users can link their Authentik account from My Profile. Local login stays enabled as a recovery option. The callback is https://paper.brunner.ninja/accounts/oidc/authentik/login/callback/ per Authentik's Paperless guide.
The first-visit create-user form is Paperless's bootstrap screen. Since the initial local account was created, link it from My Profile → Connect new social account before signing in with Authentik. At subsequent Authentik logins, the Paperless Admins claim grants administrator status.
Recovery
The PVC has a Retain storage class. Do not delete it on uninstall. Restore the PVC and the application Secret together. SQLite requires a consistent backup; stop the Paperless deployment or use its document exporter before copying the database.