# kube-score: static analysis for Kubernetes manifests

> kube-score reads Kubernetes YAML and prints recommendations for reliability and security. It is a linter for manifests, not a cluster scanner, and its checks are opinionated by design.

**zegl/kube-score** — Kubernetes object analysis with recommendations for improved reliability and security. kube-score actively prevents downtime and bugs in your Kubernetes YAML and Charts. Static code analysis for Kubernetes.

- Repository: https://github.com/zegl/kube-score
- Website: https://kube-score.com
- Stars: 3,104 · Forks: 204
- Language: Go
- License: MIT
- Published: 2026-09-24 · Updated: 2026-09-24 · Language: en
- Canonical page: https://hysenlabs.com/projects/zegl-kube-score

## What kube-score checks that kubectl apply does not

Kubernetes will accept a Deployment that has no resource limits, no readiness probe, and a container running as root. The API server validates schema and admission policy, not operational judgement. kube-score fills that gap: it parses your object definitions and returns a list of recommendations for making the application more secure and resilient, as the README puts it.

The audience is narrow and identifiable. Platform teams that template manifests with Helm or Kustomize, and reviewers who currently catch missing probes by eye, get the most from it. Anyone deploying a single hand-written Deployment to a hobby cluster will find the output noisy relative to the risk.

The checks listed in the README cover container limits, whether a Pod is targeted by a NetworkPolicy with both egress and ingress rules, PodDisruptionBudget presence on Deployments and StatefulSets, host PodAntiAffinity, readiness probes that are not identical to the liveness probe, securityContext settings such as running as a high-numbered user rather than root or with a privileged root filesystem, and use of stable APIs for Deployments, StatefulSets and DaemonSets. Several of these are availability concerns rather than security ones, which is why the project describes itself as preventing downtime and bugs, not just hardening workloads.

The important framing is that each check encodes someone's opinion about how workloads should be configured. A PodDisruptionBudget recommendation is only meaningful if you run multiple replicas and drain nodes. If you do not, the finding is still printed, and you have to decide whether to act or silence it.

## How the scoring pipeline reads manifests

The repository layout shows the split: cmd/ holds the CLI entry point, parser/ turns YAML into Kubernetes objects, score/ contains the checks, renderer/ formats results, and sarif/ handles SARIF output. The Go module depends on k8s.io/api and k8s.io/apimachinery, so objects are decoded into the same types the Kubernetes libraries use rather than a bespoke model. That matters because a check can reason about real field semantics, such as whether a probe's handler is identical to another probe's.

Input arrives as files or stdin. The README states that the input should be all applications you deploy to the same namespace for the best result, which hints at cross-object checks: a NetworkPolicy check needs to see both the policy and the Pod it selects, and those can live in different files. Scoring a single Deployment in isolation can therefore produce a false positive about missing network policy coverage.

The --kubernetes-version flag changes which checks run against the manifests, and the default is v1.18. That default is worth pausing on. If you run a newer cluster, the tool's notion of which APIs are stable may lag your reality, and the README's own advice is to set it to the version you use in production.

Exit behaviour is the CI contract. kube-score exits with code 1 when a critical error is found, and --exit-one-on-warning lowers that threshold to warnings. Output formats are human, json, ci and sarif, with json at version v2 by default and v1 marked deprecated in the help text. The ci format is described as easier to parse by other programs, and sarif exists for CI platform integration.

## Installing kube-score and scoring your first manifest

The README lists four distribution channels: pre-built binaries on GitHub releases for macOS, Linux and Windows, a Docker image at zegl/kube-score, Homebrew, and Krew under the plugin name score. Homebrew is the shortest path on macOS or Linux.

```bash
brew install kube-score
```

After that, kube-score version should print the release you installed. To see what the tool can report before pointing it at anything, list the checks.

```bash
kube-score list
```

The README describes this action as printing a CSV list of all available score checks. Those identifiers are what you pass to --ignore-test and to the per-object annotation, so this is the command to run when you want to know whether a specific concern is covered.

Now score a manifest. The README gives the static YAML form directly.

```bash
kube-score score my-app/*.yaml
```

You should see each object listed with its checks and a severity. If a check fails at critical level, the process exits 1, which is what makes the command usable as a CI step without a wrapper script.

Helm users pipe rendered output rather than chart files, because kube-score analyzes manifests, not templates.

```bash
helm template my-app | kube-score score -
```

The trailing dash tells kube-score to read stdin. The Kustomize equivalent follows the same shape.

```bash
kustomize build . | kube-score score -
```

If you prefer not to install anything, the Docker image works with a bind mount of the current directory.

```bash
docker run -v $(pwd):/project zegl/kube-score:latest score my-app/*.yaml
```

One caveat on that image: the Dockerfile builds Helm v3.10.2 and Kustomize v4.5.7 into the container, so the versions you get inside Docker are pinned by the image, not by your local toolchain.

## Ignoring tests without turning the gate off

Every linter eventually meets a finding the team has consciously accepted. kube-score handles this at two levels. The --ignore-test flag disables a test for the whole run and can be set multiple times. The kube-score/ignore annotation disables it for one object, with a comma-separated list of test IDs as the value.

The README's example annotates a Service to suppress the service-type test, which warns against NodePort services.

```yaml
apiVersion: v1
kind: Service
metadata:
  name: node-port-service-with-ignore
  namespace: foospace
  annotations:
    kube-score/ignore: service-type
spec:
  selector:
    app: my-app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  type: NodePort
```

The per-object form is the better default, because the exemption travels with the manifest and shows up in code review. A --ignore-test flag buried in a CI configuration is invisible to the person reading the YAML.

There is a counterpart for optional tests. --enable-optional-test turns one on for the run, and the README describes an annotation for enabling them per object as well. Two flags exist to neutralize the annotations themselves: --disable-ignore-checks-annotations and --disable-optional-checks-annotations. Setting the first means no annotation can suppress a finding, which is a reasonable posture for a security-focused pipeline where exemptions must be argued centrally rather than in the manifest. The trade-off is that you lose the audit trail that annotations provide.

## Where kube-score gives you the wrong answer

The most consequential limitation is that kube-score never talks to your cluster. It reads YAML, so it cannot see what is actually running, which controller mutated a field, or whether a NetworkPolicy exists in the namespace but was never committed to the repository you are scoring. A manifest set that passes cleanly can still describe a cluster with no network policy at all.

The namespace-scoping advice cuts both ways. Because cross-object checks need the full picture, scoring a fragment gives unreliable results. A CI job that lints only the file changed in a pull request will report NetworkPolicy and anti-affinity failures that the complete namespace would not have. The fix is to render the whole chart or kustomization and pipe that, which costs build time on every run.

The --kubernetes-version default of v1.18 is the other sharp edge. Checks that depend on API stability are evaluated against that version unless you override it, and the README is explicit that the flag affects which checks run. A team on a much newer cluster that leaves the default in place is grading manifests against an old target.

Finally, the checks are heuristics, not guarantees. A PodDisruptionBudget does nothing if your workload has one replica. PodAntiAffinity configured as a soft preference does not prevent co-scheduling. kube-score reports the presence of the configuration, and interpreting whether that configuration achieves its goal remains your job.

## kube-score against kube-linter and kube-bench

Two tools come up in the same searches, and they occupy different positions.

kube-linter is the closest alternative: it is also a static analyzer for Kubernetes YAML. The practical difference is scope and configuration surface. kube-score ships a fixed set of checks with a small flag vocabulary, and its per-object escape hatch is an annotation on the manifest itself. kube-linter exposes a check registry with its own configuration file, so teams that need to tune thresholds or write custom checks get more room. kube-score has an answer for the second need: the examples/ directory contains custom_check.go and its test, which shows checks being written in Go and compiled in, rather than configured at runtime. If your organization wants to enforce a rule that is specific to its own platform conventions, that is a code contribution to your fork, not a YAML file.

kube-bench is a different category entirely. It checks a cluster's control plane and nodes against the CIS Kubernetes Benchmark by running on the hosts. kube-score never inspects a running cluster. The two are complementary, and confusing them leads to the mistaken belief that a clean kube-score run says anything about node configuration or API server flags.

A third option worth naming is admission control, such as policy engines that reject objects at apply time. That approach catches everything reaching the cluster, including objects from sources you do not lint. kube-score's advantage is timing: it fails in the pull request, before a merge, with output a developer can read in the terminal.

## Maintenance, licence and the cost of upgrading

The repository is not archived, and the last push was on 2026-05-20. Releases are infrequent and deliberate: v1.18.0 in February 2024, v1.19.0 in October 2024, v1.20.0 in April 2025. That cadence suits a linter whose rules track Kubernetes API stability, which changes slowly. It also means a newly stable API may not be recognized for a while after it lands, and the --kubernetes-version flag is the lever you have in the meantime.

Upgrade cost is low by design. There is no server component, no CRD to install, and no state. You replace a binary or pull a new image tag. The two things that can break a pipeline are changes to check severity, which may flip a previously passing run to exit code 1, and changes to output format. The help text already marks the json v1 format as deprecated and slated for removal, so any consumer parsing that output should move to v2 or to sarif. Pin the version in CI if you want to review a release before it can fail your builds.

The licence is MIT. For most users that means the usual permissions to use, modify and redistribute with the licence text retained. If you fork it to add custom checks, the MIT terms travel with your fork. Nothing here is legal advice; read the LICENSE file in the repository for the actual terms.

One detail worth knowing before you depend on the Docker image: it bundles Helm and Kustomize at fixed versions, so the image tag, not your local tooling, determines what those subcommands produce.

## Conclusion

Adopt kube-score if your team writes Kubernetes YAML or Helm charts and wants a fast, deterministic gate before apply. Skip it if you need runtime cluster auditing or admission control, since it only reads manifests. Before rolling it out, run kube-score list to see which checks exist, decide whether --exit-one-on-warning matches your tolerance, and confirm the default --kubernetes-version v1.18 against the version you actually run.

## FAQ

### What is kube-score?

kube-score is a static analysis tool for Kubernetes object definitions. It reads your YAML and returns a list of recommendations for making the application more secure and resilient, and it exits with code 1 when a critical error is found.

### How do I install kube-score?

The README lists pre-built binaries for macOS, Linux and Windows on GitHub releases, a Docker image at zegl/kube-score, Homebrew via brew install kube-score, and Krew via kubectl krew install score.

### How do I ignore a kube-score test?

Use the --ignore-test flag to disable a test for the whole run, or add the kube-score/ignore annotation to a single object with a comma-separated list of test IDs. The annotation can be neutralized for the whole run with --disable-ignore-checks-annotations.

### Can kube-score check a running cluster?

Not directly. It analyzes manifests, but the README shows a pipeline that dumps cluster resources with kubectl and pipes the result into kube-score score -.

## Sources

- [License: MIT](https://github.com/zegl/kube-score/blob/master/LICENSE)
- [Project website](https://kube-score.com)
- [README](https://github.com/zegl/kube-score/blob/master/README.md)
- [Releases](https://github.com/zegl/kube-score/releases)
- [zegl/kube-score on GitHub](https://github.com/zegl/kube-score)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/zegl-kube-score
