Skip to content

Kubernetes YAML Validator

Validate Kubernetes manifests locally and catch what the API server only hints at — bad selectors, memory in millibytes, a removed apiVersion.

Manifest

3 documents

Deployment, Ingress, Service

3 resources
Deploymentapiapps/v1
Serviceapiv1
Ingressapinetworking.k8s.io/v1

Issues

3 shown
  • errorSelector does not match the pod template labels: app=api-server (template has app=api).

    Every matchLabels entry must be present, with the same value, in spec.template.metadata.labels.

    /0/spec/selector/matchLabels

  • error`pathType` is required in networking.k8s.io/v1.

    Use one of Exact, Prefix, ImplementationSpecific — Prefix is what the old v1beta1 behaviour was closest to.

    /2/spec/rules/0/http/paths/0/pathType

  • warningMemory "512m" uses the milli suffix, so it means 0.512 bytes.

    You almost certainly meant 512Mi. The m suffix belongs on CPU, not memory.

    /0/spec/template/spec/containers/0/resources/requests/memory

Not checked

the limits of a local validator
  • The full OpenAPI schema of your cluster — this is a hand-written subset covering the common kinds.
  • Custom resources. A CRD's own fields are unknown here, so only apiVersion, kind and metadata are checked.
  • Admission controllers, policies and mutating webhooks, which can reject a manifest this tool calls valid.
  • Whether an image, storage class, service account or namespace exists in the cluster.
  • Whether RBAC permits the apply, or whether the resources requested are available on any node.

Parsed and validated in your browser. No cluster is contacted and nothing is uploaded, so a manifest holding a Secret stays on your machine.

About the Kubernetes YAML Validator

This Kubernetes YAML validator checks a manifest for the mistakes that survive kubectl apply --dry-run=client, or that apply cleanly and then never become ready. The headline one is a Deployment whose spec.selector does not match its own pod template labels: the API server rejects it with "selector does not match template labels" and does not say which key was wrong. This tool names the key and shows both values.

Multiple documents in one file are the normal case, so they are handled as such — paste your whole kustomize output and every document is validated, with each finding located by document index and JSON Pointer.

It runs entirely in your browser. Manifests routinely contain Secrets, internal hostnames and image registry paths, so nothing is uploaded and no cluster is contacted.

The scope is a hand-written subset, not your cluster's OpenAPI schema, and the tool says so. Any kind it does not know is reported as checked only for apiVersion, kind and metadata — never as valid. The Not checked panel lists the five categories of thing no local validator can know.

  • Selector-to-template-label matching for Deployments, StatefulSets, DaemonSets and ReplicaSets, with the mismatched key named
  • Removed API versions flagged with the release that removed them — extensions/v1beta1, batch/v1beta1, policy/v1beta1 and nine more
  • Name validation against the right rule per kind, including the RFC 1035 exception that stops a Service name beginning with a digit
  • Resource quantities parsed, so a request above its own limit is caught and memory: 512m is flagged as half a byte
  • Values YAML silently retyped: a numeric ConfigMap value, an unquoted label, a numeric env value
  • Secret data checked as base64, with stringData suggested when it is plaintext
  • Ingress paths checked for the pathType that networking.k8s.io/v1 made mandatory
  • CronJob schedules parsed with the same cron engine as the cron parser tool, and rejected if they carry a seconds field

How to use it

  1. Paste your manifest, or use Open a file. Multi-document YAML separated by --- is expected, not a special case.
  2. Read the errors first: those are what the API server will reject, or what will apply and then sit unready.
  3. Use the resource list to confirm the file declares what you think it declares — a duplicated name across two documents is easy to miss and kubectl apply keeps only the last.
  4. Check the warnings for the quieter problems: missing resource limits, an image with no tag, a Service selector matching no workload in the file.
  5. Read the Not checked panel before treating a clean result as a guarantee. Admission controllers and policies can still reject this manifest.

Real-world use cases

DevOps & platform engineers

Catch a Deployment selector that doesn't match its own pod template labels before applying — the field that is immutable after creation, so getting it wrong means delete-and-recreate rather than a quick patch.

Backend developers

Check a manifest copied from an older tutorial for a removed apiVersion like extensions/v1beta1 or a missing pathType, both of which apply cleanly on some clusters and get rejected outright on newer ones.

SREs debugging a stuck rollout

Work out why a pod is OOM-killed immediately after a resources.requests.memory value like 512m turns out to mean half a byte, not 512 megabytes — a units mistake the API server accepts without comment.

Platform teams reviewing manifest PRs

Validate rendered kustomize or helm template output before merging, including checking that a Secret's data values are actually base64 and that ConfigMap values are quoted strings rather than bare YAML numbers.

Engineers new to a cluster's manifests

Get an at-a-glance resource list — every kind, name, namespace and apiVersion across a multi-document file — without cross-referencing fifty lines of --- separated YAML by hand.

Examples

A selector that does not match its template

Input
spec:
  selector:
    matchLabels:
      app: api-server
  template:
    metadata:
      labels:
        app: api
Output
error  /0/spec/selector/matchLabels
Selector does not match the pod template labels:
app=api-server (template has app=api).

The cluster's own message for this names no key at all. On a manifest with five labels, that is the difference between a ten-second fix and a twenty-minute one.

Memory in millibytes

Input
resources:
  requests:
    memory: 512m
Output
warning  /0/spec/template/spec/containers/0/resources/requests/memory
Memory "512m" uses the milli suffix, so it means 0.512 bytes.
You almost certainly meant 512Mi.

Valid syntax, accepted by the API server, and the pod is OOM-killed immediately. Nothing in the failure looks like a units problem.

A ConfigMap value YAML turned into a number

Input
data:
  replicas: 3
Output
error  /0/data/replicas
"replicas" is a number; every ConfigMap value must be a string.
Quote it: replicas: "3".

Every ConfigMap value is a string. This is rejected on apply with a message about types rather than about quoting.

An Ingress path missing pathType

Input
paths:
  - path: /
    backend:
      service:
        name: api
        port:
          number: 80
Output
error  /2/spec/rules/0/http/paths/0/pathType
`pathType` is required in networking.k8s.io/v1.
Use one of Exact, Prefix, ImplementationSpecific.

It was optional in the removed v1beta1 API, so every manifest copied from an older tutorial has this.

Common errors

MessageCauseFix
The Deployment "api" is invalid: spec.selector: Invalid value: selector does not match template labelsspec.selector.matchLabels contains a key or value that is not present, identically, in spec.template.metadata.labels.Make them agree. Note that the selector is immutable after creation — if this is an existing Deployment you must delete and recreate it, so it is worth catching before the first apply.
no matches for kind "CronJob" in version "batch/v1beta1"The API version was removed. batch/v1beta1 went in 1.25, extensions/v1beta1 in 1.22, policy/v1beta1 in 1.25.Move to the served version — batch/v1 here. This tool lists the removal release for every version it knows about, so you can tell a removal from a typo.
metadata.name: Invalid value: must be no more than 63 characters / a lowercase RFC 1123 labelA capital letter, an underscore, or a leading digit on a Service. Different kinds enforce different name rules.Lowercase it and replace underscores with hyphens. A Service name must additionally start with a letter, which is the rule that rejects names like 1password-sync.
CreateContainerConfigError, or a pod OOM-killed immediatelyFrequently a resource quantity that means something other than intended — memory: 100m is 0.1 bytes — or a volumeMount naming a volume the pod spec never declared.Both are checked here. The m suffix belongs on CPU only; memory takes Mi and Gi.
Secret data is not base64 encodedA plaintext value was put in data, which Kubernetes expects to be base64.Use stringData instead and let Kubernetes encode it, or base64 the value yourself. stringData is almost always what you want in a file a human edits.
The manifest applies but nothing happensOften an Ingress with no ingressClassName that no controller claims, or a Service whose selector matches no pods.Both are warnings here. For the Service, check the selector against the pod template labels rather than the Deployment's own labels — they are frequently not the same thing.

Frequently asked questions

Is my manifest uploaded anywhere?

No. This Kubernetes YAML validator parses and checks everything in your browser, which is why it is safe to paste a manifest containing a Secret. No cluster is contacted and no schema is fetched. Disconnect from the network and it still works.

How is this different from kubectl apply --dry-run?

--dry-run=client checks little more than that the YAML parses. --dry-run=server is a real validation, but it needs a reachable cluster with credentials, and it will not tell you about a Service selector that matches nothing or a memory value in millibytes. This tool is for the pass before either — it catches the mistakes that are cheap to make and expensive to diagnose, without needing a cluster at all.

Does it validate custom resources?

Only their apiVersion, kind and metadata. A CRD's own fields are defined by a schema installed in your cluster, which is not available here, so an unknown kind is reported as partially checked rather than valid. The same rule applies to any kind outside the common set: the tool tells you the scope of the verdict it gave you.

Why does it warn about missing resource limits?

Because a container with no limits can consume a whole node, and one with no requests cannot be scheduled sensibly — the scheduler has no idea how much room it needs. It is a warning rather than an error because there are legitimate reasons to omit them, particularly when a LimitRange sets defaults for the namespace. Which this tool cannot see, and says so.

Which Kubernetes version does it target?

The current stable API surface: apps/v1, batch/v1, networking.k8s.io/v1, policy/v1, autoscaling/v2. Every beta version listed as removed was removed in 1.22 through 1.26, so if your cluster is 1.27 or later none of them are served. It does not model per-version field availability beyond that, which is one of the limits in the Not checked panel.

Can it check kustomize or Helm output?

Yes, once rendered. Pipe kustomize build or helm template into a file and paste the result — multi-document output is exactly what this expects. Un-rendered Helm charts are not YAML at all, they are Go templates, so they cannot be validated by anything that does not run Helm.

What about Docker Compose files?

Use the Docker Compose validator, which shares this one's YAML parser and the same rule about reporting what it did not check. The two formats look similar and behave differently in almost every detail — env is a mapping in Compose and a list of name/value pairs in Kubernetes, which is a mistake this validator calls out by name.

Which resource kinds does it actually understand?

Around 28: Pod, Deployment, StatefulSet, DaemonSet, ReplicaSet, Job, CronJob, Service, Ingress, IngressClass, NetworkPolicy, ConfigMap, Secret, ServiceAccount, Namespace, PersistentVolume, PersistentVolumeClaim, Endpoints, EndpointSlice, LimitRange, ResourceQuota, HorizontalPodAutoscaler, PodDisruptionBudget, Role, RoleBinding, ClusterRole, ClusterRoleBinding, StorageClass and CustomResourceDefinition — each checked against the apiVersion the current stable API actually serves for that kind. Anything else is checked for apiVersion, kind and metadata shape only, and reported as such rather than passed as valid.

Last updated