# Secret paperless-ai-config managed via SOPS (argocd/secrets/paperless-ai-secrets.enc.yaml) --- apiVersion: apps/v1 kind: Deployment metadata: name: paperless-ai namespace: paperless labels: app.kubernetes.io/name: paperless-ai spec: replicas: 1 # Recreate, not RollingUpdate: paperless-ai-data is a single RWO PVC. With # RollingUpdate the new pod tries to mount the PVC while the old pod still # holds it -> "Multi-Attach error" / stuck Init:0/1 forever, rollout wedged # at "1 desired, 1 ready, 1 updated, 1 unavailable". Recreate terminates the # old pod first so the PVC detaches before the new one starts. Same pattern # as the Forgejo gitea Deployment. strategy: type: Recreate selector: matchLabels: app.kubernetes.io/name: paperless-ai template: metadata: labels: app.kubernetes.io/name: paperless-ai spec: tolerations: - key: node-role.kubernetes.io/control-plane operator: Exists effect: NoSchedule volumes: - name: paperless-ai-data persistentVolumeClaim: claimName: paperless-ai-data - name: paperless-ai-env-config configMap: name: paperless-ai-env - name: llm-key secret: secretName: paperless-llm-key defaultMode: 0400 # No init container. The token used to be fetched once at pod start with a # client_credentials grant against a web app, which cannot work: a web app has # no such grant, and even a token that was minted would be dead within the hour # while this pod runs for weeks. # # llm-token-vendor runs beside the app instead. It signs a machine-key assertion, # exchanges it for a token, and rewrites /app/data/llm_token.txt at 80% of the # token lifetime. The vendor image keeps reading CUSTOM_API_KEY_FILE unchanged. # The key is re-read on every mint, so a rotation lands without a restart. containers: - name: llm-token-vendor image: forgejo.riotpiao.com/riotpiao-poimen/llm-token-vendor:0.1.0 imagePullPolicy: Always args: - --token-url=https://zitadel.riotpiao.com/oauth/v2/token - --issuer=https://zitadel.riotpiao.com - --user-id=397072668735570071 - --key-id=397072674272051351 - --key-file=/var/run/llm-key/key - --scope=openid urn:zitadel:iam:org:project:id:396757796898471937:aud - --out=/app/data/llm_token.txt volumeMounts: - {name: paperless-ai-data, mountPath: /app/data} - {name: llm-key, mountPath: /var/run/llm-key, readOnly: true} securityContext: allowPrivilegeEscalation: false capabilities: {drop: ["ALL"]} runAsNonRoot: true runAsUser: 65532 seccompProfile: {type: RuntimeDefault} resources: requests: {cpu: 10m, memory: 32Mi} limits: {cpu: 100m, memory: 64Mi} - name: paperless-ai image: clusterzx/paperless-ai:latest env: # Paperless-ngx connection - name: PAPERLESS_API_URL value: "http://paperless.paperless.svc.cluster.local:8000" - name: PAPERLESS_API_TOKEN valueFrom: secretKeyRef: name: paperless-ai-config key: PAPERLESS_API_TOKEN - name: PAPERLESS_USERNAME value: "admin" # LLM API — via public gateway with JWT auth - name: AI_PROVIDER value: "custom" - name: CUSTOM_BASE_URL value: "https://api.riotpiao.com/v1" # Token will be read from file at runtime by the application # The init container fetches it and stores in /app/data/llm_token.txt - name: CUSTOM_API_KEY_FILE value: "/app/data/llm_token.txt" - name: CUSTOM_MODEL value: "qwen2.5:3b-instruct" # Behavior - scan for new documents and tag them - name: SCAN_INTERVAL value: "60" - name: PROCESS_PREDEFINED_DOCUMENTS value: "no" - name: ADD_AI_TAG value: "yes" - name: AI_TAG_NAME value: "ai-processed" - name: USE_PROMPT_TAGS value: "yes" - name: ADD_AI_DESCRIPTIONS value: "yes" volumeMounts: - name: paperless-ai-data mountPath: /app/data - name: paperless-ai-env-config mountPath: /app/data/.env subPath: paperless-ai-config.env resources: requests: cpu: 100m memory: 512Mi limits: cpu: "1" memory: 2Gi