derailed/popeye
Popeye is a read-only Go utility that scans live Kubernetes clusters and reports misconfigurations, stale or unused resources, port mismatches, probe and image issues, RBAC problems, and resource over/under-allocation when a metrics-server is present. It is configurable with a spinach.yaml (excludes, thresholds, allowed registries, severity overrides), can run as a CronJob with a get/list ClusterRole, outputs HTML/JSON/YAML reports, pushes Prometheus metrics, and can upload reports to S3/Minio. It lints what is actually deployed rather than manifests on disk.
What it is
Popeye (derailed/popeye) is a read-only linter that inspects a live Kubernetes cluster and scores it. The page's operational details matter more than its feature list: how to run it, what it needs, and several traps in the defaults.
What to do
Run it locally against your kubeconfig. Without -n it uses the context namespace, or default, so it will not scan the whole cluster:
popeye -A
popeye -n fred
popeye --context olive
popeye -f spinach.yaml
popeye -n ns1 -s pod,svc --logs noneRun it in a container. --save inside the container writes to the container's /tmp, which is deleted by --rm, so map /tmp out:
docker run --rm -it \
-v $HOME/.kube:/root/.kube \
-e POPEYE_REPORT_DIR=/tmp/popeye \
-v /tmp:/tmp \
quay.io/derailed/popeye --context foo -n bar --save --output-file my_report.txtSave a report to disk, or push it to S3 or Minio. Both S3 and Minio need AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in the environment:
POPEYE_REPORT_DIR=$(pwd) popeye --save --out html --output-file report.html
popeye --s3-bucket s3://my-popeye/fred --s3-region us-west-2 --out json --save --output-file scan.jsonPush Prometheus metrics. This goes through a Pushgateway, not a scrape endpoint:
popeye --push-gtwy-url http://localhost:9091The published gauges are popeye_severity_total, popeye_code_total, popeye_linter_tally_total, popeye_report_errors_total and popeye_cluster_score. A sample Grafana dashboard is in the repo.
Run it in-cluster as a CronJob. The manifests are in the repo's k8s/ directory (kubectl apply -f k8s/popeye). The sample ClusterRole grants only get and list on:
- core resources, including
secretsandconfigmaps - apps, networking, batch and gateway resources
- autoscaling and policy resources
- RBAC resources
metrics.k8s.iopods and nodes
Review that list against your policy, since it includes secrets cluster-wide.
Caveats
- Exit code: Popeye exits non-zero when it finds lint errors. In the CronJob, set
--force-exit-zero, otherwise the pods end in an error state. The sample uses-o yaml --force-exit-zero. - Schedule: The sample schedule
"* */1 * * *"is commented as hourly, but as written it fires every minute for the first hour of each... in practice, check the cron expression before applying it. The page says hourly, but the minute field is*.0 * * * *is the usual hourly form. - Exclude regexes: Exclusion rules need the
rx:prefix to be treated as regex, and a loose regex can hide more resources than intended. The page recommends an occasional unfiltered ("wide open") run to catch new issues. - Exclude semantics: FQNs are
namespace/name, and linter names are lowercase plural resource names (e.g.poddisruptionbudgets). - Allocation checks: Over/under-allocation checks need a metrics-server.
- Registry allowlist: The
registrieslist in spinach.yaml flags any resource pulling from a registry not on it. The sample allowsquay.ioanddocker.io. - Stability: The page calls the project brittle and work in progress. It says it works best with Kubernetes 1.25.x, may fail on older versions or with insufficient RBAC, and that the spinach format and metrics may change.
- Metrics cardinality: When scans are saved to disk and pushed,
popeye_cluster_scoregets an extra label for the artifact. The page itself notes this changes cardinality on every push. - Terminal: It needs a 256-color terminal. Set
TERM=xterm-256coloron Unix if the output looks wrong. - Scope: It covers only a curated set of resource types.
One correction to the schedule item above: the cron field is * */1 * * *, which means every minute during every hour, not once an hour. Despite the page's comment, that would run the job every minute, and concurrencyPolicy: Forbid only prevents overlap.
Organisations named here
- Popeye
- spinach.yaml
Technologies named here
- Kubernetes
- Spinach YAML configuration file
- Kubernetes clusters on Linux, OSX and Windows platforms
- Prometheus pushgateway metrics
- Kubernetes metrics-server
- Homebrew/LinuxBrew, go install, Docker
- Go
- Docker
- Prometheus
- Grafana
Techniques named here
- Overriding lint code severity
Related in Cloud & Infra


Orca Security: GenAI Risks in Cloud Environments
.png)
Google API keys keep working after you delete them
