Kubernetes YAML Linter
Paste a manifest and get severity-ranked findings: removed apiVersions, missing requests and probes, unpinned images, and permissive securityContext.
Privacy: Everything runs in your browser. Manifests are parsed locally with js-yaml and never uploaded, which matters because manifests leak image registries, internal hostnames, and env var names.
Paste a manifest to lint it. Nothing is uploaded.
Parsed in your browser with js-yaml — your manifests are never uploaded. Line numbers are best-effort: they point at the first matching key in the document, so on a manifest with several containers, trust the resource and path over the line.
What it does
Paste one or more manifests, separated by ---, and get back a ranked list of
findings with a line number, the resource they belong to, the exact field path, and
a link to the Kubernetes documentation for the rule. Errors are things that will
break or that hand out root; warnings are things that will page you at 3am;
info-level findings are conventions worth adopting but not worth blocking a deploy
over.
The rule set covers what actually goes wrong in review: apiVersion values that no
longer exist, missing resource requests and memory limits, absent liveness and
readiness probes, images pinned to :latest or to no tag at all, privileged: true,
hostNetwork, hostPath volumes, missing runAsNonRoot and
allowPrivilegeEscalation: false, dangerous added capabilities, absent labels, and
the empty-block indentation mistake that silently drops your entire container list.
How it works
Parsing happens in your browser with js-yaml. Documents are split on the raw text
before parsing, which is what lets each finding carry a line number from the file
you actually pasted rather than from a re-serialised copy. YAML anchors and merge
keys are resolved first, so a securityContext inherited through <<: *defaults
counts as set — worth knowing, because linters that skip this step produce a wall of
false positives on any manifest that uses aliases.
Rules read the parsed object graph, not a regex over text. That distinction matters
for the pod template: a Deployment, StatefulSet, DaemonSet and Job all keep
their containers at spec.template.spec.containers, a CronJob buries it under
spec.jobTemplate.spec.template.spec, and a bare Pod has it at spec. Each is
walked at the right path, including initContainers.
The deprecation table is the part worth being precise about. It maps
apiVersion plus kind pairs to the version that removed them and the replacement
group, following the
Kubernetes deprecated API migration guide:
extensions/v1beta1 Deployments went in 1.16, networking.k8s.io/v1beta1 Ingress
and the v1beta1 RBAC types in 1.22, batch/v1beta1 CronJob and
policy/v1beta1 PodSecurityPolicy in 1.25. Anything else with beta or alpha in
the group gets an info-level note rather than a false alarm.
Security rules follow the
Pod Security Standards
rather than an invented house style. hostNetwork, hostPID, hostIPC and
privileged are Baseline violations and reported as errors; the explicit
allowPrivilegeEscalation: false and runAsNonRoot: true that Restricted demands
are warnings, since plenty of legitimate workloads have not got there yet.
Why there is no CPU limit rule
The resource rules ask for CPU and memory requests and a memory limit, and deliberately never ask for a CPU limit. This is not an oversight.
Requests are what the scheduler packs against, and a container with none lands in the BestEffort QoS class, first in line for eviction when a node comes under pressure. A memory limit is a containment boundary: with one, a leak gets OOM-killed inside its own cgroup, and without one it takes the node and every neighbour with it. Both are described in Resource Management for Pods and Containers.
A CPU limit behaves differently. CPU is compressible, so the limit is enforced by CFS quota — the container is throttled rather than killed. On a bursty request path, a limit that looks generous in aggregate still produces tail-latency spikes every time a burst exhausts the quota inside a 100ms period. Requests already guarantee a floor and let the workload use idle capacity. Flagging a missing CPU limit would push people toward a worse configuration, so the linter stays quiet about it.
When to reach for it
Reviewing a manifest somebody else wrote is the obvious one, and the severity filter
makes it quick to answer “is there anything here I must block on”. It is also useful
on your own output from Helm or Kustomize: pipe helm template through your
clipboard and lint the rendered result, which is the thing that actually gets
applied.
The empty-block check earns its place on its own. When spec.template.spec parses to
null because the containers list is indented one level too far left, the YAML is
perfectly legal and the API server error is unhelpful. Seeing it named explicitly
saves the ten minutes you would otherwise spend counting spaces.
Related tools
- YAML ↔ JSON converter for reading a manifest as JSON when the nesting stops making sense.
- JSON formatter for the API server’s response.
- Nginx and Caddy config generator when the same service is fronted by a plain reverse proxy.
- TLS certificate inspector for the certificate your Ingress is serving.
- DNS lookup to confirm the record your Ingress host points at.
Frequently asked questions
Does a clean report mean the manifest is valid?+
continers: or an int where a string belongs will pass here and still be rejected by the API server. kubectl apply --dry-run=server is the only thing that validates against the real schema for your cluster version.Why is the line number sometimes on the wrong container?+
missing-readiness-probe findings point near the first name: match. The spec.template.spec.containers[1] path in each finding is authoritative; the line is a hint.Why does it complain about resource limits when limits caused my last outage?+
It says my pod template is missing but I can see it.+
spec.template.spec parses to null, which is what happens when the block under it is indented one level too far left. The YAML is legal, so the parser does not complain — the key simply has no value and containers became a sibling of template. This is the single most common Kubernetes YAML mistake and the reason the linter checks for empty blocks at all.Are anchors and merge keys supported?+
<<: *anchor is resolved before the rules run, so a securityContext pulled in from an anchor counts as set. Note that YAML anchors are scoped to a single document — referencing an anchor defined above a --- separator is invalid YAML, and you will get a syntax error rather than a lint finding.Related tools
YAML ↔ JSON Converter
Convert YAML to JSON and back in your browser. Resolves anchors and merge keys, handles multi-document files, and lets you pick the output indent.
JSON Formatter and Validator
Paste JSON, get it pretty-printed or minified. Validates syntax, highlights errors, works entirely in your browser.
Nginx and Caddy Config Generator
Fill in a domain and an upstream, get a working nginx server block and an equivalent Caddyfile side by side, with TLS, headers, and rate limiting.