--- apiVersion: v1 kind: PersistentVolumeClaim metadata: annotations: # Never let ArgoCD prune this PVC: it holds the DB backups. argocd.argoproj.io/sync-options: Prune=false name: researchowl-backups namespace: researchowl spec: accessModes: - ReadWriteOnce resources: requests: # DB is ~220MB and growing; 7 daily copies ≈ 1.6Gi, so 2Gi would fill up. storage: 5Gi --- apiVersion: batch/v1 kind: CronJob metadata: name: researchowl-db-backup namespace: researchowl labels: app: researchowl spec: schedule: "0 3 * * *" timeZone: "Europe/Madrid" concurrencyPolicy: Forbid successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 3 jobTemplate: spec: backoffLimit: 2 activeDeadlineSeconds: 600 template: metadata: labels: app: researchowl-db-backup spec: restartPolicy: Never containers: - name: backup image: keinos/sqlite3:latest command: - /bin/sh - -c - | set -eu STAMP=$(date +%Y%m%d) DEST="/backups/researchowl-${STAMP}.db" echo "Backing up /data/researchowl.db -> ${DEST}" sqlite3 -cmd ".timeout 30000" /data/researchowl.db ".backup '${DEST}'" RESULT=$(sqlite3 "${DEST}" "PRAGMA integrity_check;") if [ "${RESULT}" != "ok" ]; then echo "Integrity check FAILED: ${RESULT}" rm -f "${DEST}" exit 1 fi echo "Integrity check ok" # Retention: keep the 7 most recent backups for f in $(ls -1t /backups/researchowl-*.db | tail -n +8); do echo "Pruning old backup ${f}" rm -f "${f}" done ls -lh /backups/ volumeMounts: # NOT readOnly: the source DB runs in WAL mode and sqlite readers # must be able to touch the -shm/-wal sidecar files. The job only # ever reads the DB (.backup); it never modifies application data. - name: data mountPath: /data - name: backups mountPath: /backups resources: requests: memory: "64Mi" cpu: "50m" limits: memory: "256Mi" cpu: "500m" volumes: # Same RWO local-path PVC as the bot: the PV's node affinity pins this # pod to the node where the data lives, so co-mounting is safe. - name: data persistentVolumeClaim: claimName: researchowl-data - name: backups persistentVolumeClaim: claimName: researchowl-backups