
Your cluster probably has dead RBAC roles, orphaned Secrets, and Services pointing at Pods that don't exist anymore — and nothing is telling you.
None of that breaks anything today. It just sits there, until the day someone reuses a stale ServiceAccount token, or a bad rollout quietly routes traffic nowhere because the Service it depends on has no matching Pods left. By the time you notice, it's an outage or a security review finding, not a five-minute cleanup.
Popeye is a free, open-source tool built by the same person behind k9s, and its whole job is finding that kind of rot before it becomes a problem.
What makes it different from a linter
Popeye doesn't look at your YAML. It connects to a live cluster, read-only, and checks what's actually running against what's actually being used. It never changes anything — no mutations, no admission webhooks, nothing to approve in a change-management ticket. You can point it at production without anyone's permission slip.
What it actually checks:
- ConfigMaps, Secrets, ServiceAccounts, PersistentVolumes, PersistentVolumeClaims, and RBAC Roles/ClusterRoles that nothing references anymore
- Services with no matching Pods or no healthy Endpoints — the silent-outage pattern
- Pods missing liveness/readiness probes, missing resource limits, or running on an unpinned
latestimage tag - CPU and memory allocation versus actual usage, when
metrics-serveris installed
Every check rolls up into a single cluster score from 0 to 100, broken down by namespace and by resource type, so you have one number to watch over time instead of scrolling a wall of warnings.
Running it
Popeye ships as a single binary — no cluster-side install, no CRDs, nothing to deploy before you can use it. Point it at a kubeconfig and scan:
# scan every namespace, ConfigMaps only
./popeye -A -s cm
# scan every namespace, everything Popeye knows how to check
./popeye -AThe output below is illustrative — the exact resource names and counts will be whatever your own cluster has accumulated, which is the whole point of running it yourself rather than taking someone's word for it:
SCORE 72%
----------------------------------------------------------------
· Checking ConfigMaps...................................✅
· default/legacy-feature-flags
‼️ [POP-105] Unused ConfigMap
· Checking ServiceAccounts..............................✅
· kube-system/old-ci-deploy-sa
‼️ [POP-304] Unused ServiceAccount
· Checking Services......................................✅
· staging/payments-worker
💥 [POP-503] No Endpoints associated with Service
That last line — a Service with no Endpoints — is the one worth stopping on. It means something upstream (an Ingress, another Service, a client config) still points at payments-worker, and whatever's sending traffic there is going nowhere, silently.
Reading the score, not just the total
72% on its own doesn't tell you much. What matters is which category is dragging it down, and whether that number moves. A score that holds steady at 85% across deploys means your team is cleaning up after itself. A score that drifts down 2-3 points a month means debt is accumulating faster than anyone's paying it off — and you'd never see that trend from a single one-off scan.
Making it watch the cluster instead of you
Popeye exports to HTML, JSON, YAML, JUnit, and Prometheus, which means it fits into the same pipelines you already have rather than needing its own dashboard. Run it as a CronJob and push the report to S3 for a running history:
apiVersion: batch/v1
kind: CronJob
metadata:
name: popeye-weekly-scan
namespace: platform-tools
spec:
schedule: "0 6 * * 1" # every Monday at 06:00
jobTemplate:
spec:
template:
spec:
serviceAccountName: popeye-readonly
restartPolicy: OnFailure
containers:
- name: popeye
image: derailed/popeye:latest
command:
- /bin/sh
- -c
- >
popeye -A -o json -f /tmp/report.json &&
aws s3 cp /tmp/report.json
s3://your-bucket/popeye/$(date +%Y-%m-%d).jsonGive popeye-readonly a ClusterRole scoped to get/list — Popeye never needs write access, and you shouldn't grant it any. A week of JSON reports in S3 turns "is this getting worse?" from a guess into a chart.
Try it on your own cluster
Grab the binary from Popeye's GitHub releases, point it at a context you already have read access to, and run ./popeye -A. Fifteen minutes from now you'll either have a clean bill of health or a short, concrete list of exactly what to clean up first — not a vague sense that "the cluster probably has some cruft."

Experienced DevOps Engineer proficient in Python, Docker, Kubernetes, CI/CD, and cloud technologies. Specialized in automating processes and building scalable infrastructure for seamless development and deployment.


