Reclaimerr¶
Jellyfin/Plex disk-space reclamation tool in the media namespace. Identifies and deletes
unwatched or low-rated media, exposing an internal-only web UI via Envoy Gateway. Standard
bjw-s app-template deployment mirroring sibling apps (maintainerr, pulsarr).
Purpose¶
- Reclaim disk by flagging and removing unwatched / low-rated media, integrating with Jellyfin/Plex and the *arr stack (Sonarr/Radarr).
- Single Deployment pod with a small VolSync-backed PVC (
2Gi) at/app/dataholding the app database, logs, and static files. - Jellyfin/Plex and *arr connections are configured through the UI post-deploy, not in git.
Design decisions¶
- bjw-s
app-templatechart (OCI source, tag matching maintainerr/pulsarr): the standardks.yaml+app/(kustomization, ocirepository, helmrelease) layout. No deviation from the namespace pattern. - No ExternalSecret. Reclaimerr auto-generates
JWT_SECRETandENCRYPTION_KEYon first launch and persists them into/app/data(the upstream-recommended path). This is simpler than 1Password management; the cost is that the keys live only in the PVC, so the PVC is the source of truth for sessions and encrypted state. - Internal-only ingress. Inline
route:on theenvoy-internallistener (namespacenetwork) for the single${SECRET_DOMAIN}hostname: theenvoy-internalattachment is what keeps it internal-only. No Cloudflare/external exposure, consistent with maintainerr. COOKIE_SECURE: "true"because Envoy terminates TLS in front of it, andCORS_ORIGINSis the single originhttps://reclaimerr.${SECRET_DOMAIN}so the SPA's API calls are accepted.- Components:
homepage(dashboard tile under theMediagroup,mdi-broomicon) andvolsync(PVC backup). (Gatus monitoring is automatic via the gatus-sidecar chart's HTTPRoute auto-discovery, nogatus/guardedcomponent.)VOLSYNC_CAPACITY: 2GiandVOLSYNC_CACHE_CAPACITY: 8Giare the required substitutes for the volsync component (overriding the 5Gi/10Gi defaults). - Deferred (out of scope): external ingress, pre-seeding
JWT_SECRET/ENCRYPTION_KEYvia 1Password, and aTMDB_API_KEYoverride (the bundled upstream key is fine).
Deploy gotchas¶
readOnlyRootFilesystem: trueneeds a writable/tmp. The container security context locks the root filesystem read-only, so atmpemptyDiris mounted at/tmp. Without it the app fails to write scratch files at runtime./app/datais writable via the VolSync PVC.- Health endpoint is a backend JSON route, not an SPA. All three probes (liveness,
readiness, startup) hit
GET /api/info/healthon port8000, and the Gatus condition is[STATUS] == 200(not a redirecting-SPA check). The Gatus endpoint targets the in-cluster service URL on*.svc.cluster.local.
- Startup probe must allow a slow first boot. First launch generates the JWT/encryption
keys and initialises the DB into the empty PVC; size the startup probe generously
(
failureThreshold: 30,periodSeconds: 5) so it does not CrashLoop before init finishes. - Beta image, pinned by digest. The image is a
0.1.0-betaN@sha256:...tag; Renovate tracks new releases. Keep the digest pin so a moving beta tag cannot silently change the running build. - Component path depth. From
kubernetes/apps/media/reclaimerr/ks.yaml, the component references need four../segments to reachcomponents/. A wrong depth or a missingVOLSYNC_CAPACITYsubstitute fails the flate/flux render.
Operational notes¶
- The PVC is the only state. Losing
/app/dataloses the auto-generatedJWT_SECRETandENCRYPTION_KEY(all sessions invalidated, encrypted config unreadable). Recovery is via the VolSync snapshot/backup of thereclaimerrPVC. There is no 1Password copy of the keys. - Runs unprivileged. Pod security context is
runAsNonRootas UID/GID1000withfsGroup: 1000andfsGroupChangePolicy: OnRootMismatch; the container drops all capabilities and disables privilege escalation. Resources are light (cpurequest10m,memorylimit512Mi). - First-run setup is manual. After the pod is
Ready, browse to the internal hostname and wire up Jellyfin/Plex and *arr connections through the UI; nothing is pre-seeded. - Verify after merge: PVC
Bound, podRunning/Ready=1/1, logs show it listening on:8000with keys generated into/app/data, the HTTPRoute is accepted byenvoy-internal, andGET /api/info/healthreturns a200JSON body via the internal hostname. - Rollback:
flux suspend helmrelease reclaimerr -n mediathen delete the pod; reverting the commits prunes the Kustomization (prune: true) while VolSync snapshots the PVC per the volsync component defaults.