Kubernetes Resource Name Validator

Paste manifests, or names one per line, and find out which of the naming rules applies to each one and exactly which character breaks it. A dot is fine on a ConfigMap and refused on a Service, a Service name has to start with a letter, and a CronJob runs out of room at 52 characters for a reason nothing in the manifest mentions.

Paste below, or drop a file anywhere on this panel

Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.

The answer appears here

Paste on the left and press Validate. Nothing leaves this tab.

Examples

Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.

A capital letter

Resource names are DNS labels, so My-Service is rejected at apply time

My-Service

Over 63 characters

The segment limit, which bites when a generated name adds a suffix

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Common mistakes

These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

  1. Using a capital letter or an underscore

    Most resource names are DNS labels: lowercase alphanumerics and hyphens only. An underscore is the most common rejection.

    Instead:Lowercase with hyphens. Convert underscores when translating from Docker Compose service names.

  2. Assuming one rule covers every kind

    Some kinds use a DNS subdomain, which allows dots, and some use a DNS label, which does not. A name valid for one is rejected for another.

    Instead:Check against the kind you are actually creating.

  3. Naming close to the 63 character limit

    Controllers append suffixes. A Deployment name becomes a ReplicaSet name plus a hash, which becomes a Pod name plus another, and the limit applies at each step.

    Instead:Leave roughly 15 characters of headroom.

What it checks

Every name field in what you paste, against the rule for that field rather than one rule for all of them, plus the lower limits a controller imposes on some kinds without saying so.

Three rules, and the kind picks one

Most names are an RFC 1123 subdomain: 253 characters of lowercase letters, digits, hyphens and dots. A Namespace is an RFC 1123 label: 63 characters and no dots anywhere in it. A Service is an RFC 1035 label, which adds one restriction on top of that, a leading letter, so 1st-api names a Deployment and is rejected as a Service. Paste manifests and the kind is read from each document. Paste a bare list and write a line as service/api to get the exact rule.

The other name fields, held to other rules again

Container names, init container names and volume names are all DNS labels, as is metadata.namespace, so my_app is a legal ConfigMap name and an illegal container name in the same file. A port name is a rule of its own, an IANA service name: 15 characters of lowercase letters, digits and hyphens, at least one letter, no two hyphens in a row and none at either end, which makes http-metrics-endpoint too long and 8080 wrong twice over, since YAML reads it as a number where the field is a string and there is no letter in it. All of them are read out of a Deployment, StatefulSet, DaemonSet or Job template, out of a CronJob's jobTemplate, out of a bare Pod, and off a Service's ports.

The limit a controller imposes, under the field's own

A CronJob stops at 52 because each run's Job is named after it plus the minutes since the epoch. A Job name is accepted at 253 and then copied into the job-name label on every pod, where 63 is the ceiling, so the Job sits at zero completions with the reason only in its events. A Deployment past 46 characters still runs, and its pod names stop containing the whole name because the generator truncates the base to 58. A DaemonSet is 57, a StatefulSet is 63 less the width of its highest ordinal, read from spec.replicas when the manifest states it, and a Pod past 63 keeps its name in kubectl and loses it in its own hostname.

generateName is checked as the name it becomes

The server truncates the base to 58 characters and appends 5 random ones, so base plus 5 characters is what has to be valid, and that is what gets checked, against the rule for the kind. A base ending in a hyphen passes, because 5 characters follow it, while the same string in metadata.name is rejected. A base of 1st- is fine on a Pod and a failure on a Service. An object carrying only generateName has no name to measure and the verdict says so, rather than putting a green tick over an empty table, and the generated sample is kept out of the table because it is not a name you wrote. The limits above are applied to the generated name as well, so a CronJob base of 55 characters is reported here exactly as the same string in metadata.name would be, and the fix names the budget the base has left rather than the budget the finished name has.

Every fault at once, and a correction only when there is one

The usual paste has two problems together, a capital and an underscore, so all of them are reported with a caret under the character rather than one per round trip. A corrected form is offered only when it is mechanical and only after it has been validated: My_API becomes my-api. A Service name starting with a digit gets no suggestion, because prefixing or reordering it changes what the name means.

What it says instead of guessing

A name with no kind attached is checked against the subdomain rule and then told which kinds it would fail: a dot rules out a Service and a Namespace, a leading digit rules out a Service, 64 characters rules out a Service, a Namespace and a Job. If the YAML does not parse, every name: line is still checked and the report says the per-kind rules were skipped. What it cannot see either way: whether the name is already taken in that namespace, and whether anything else still points at the old one.

Why one Kubernetes name is legal and the same name elsewhere is not

There is no single Kubernetes naming rule. The API server validates each field with one of a handful of functions, and which one it reaches for depends on the kind and the field rather than on anything visible in the manifest. When it refuses, it prints the regex at you. When a controller has quietly lowered the limit instead, it says nothing at all: the object is created and the thing it generates is what breaks.

The three rules, stated exactly

These are the patterns the API server applies, quoted as it writes them. The only difference between the first two is the dot, and the only difference between the last two is that an RFC 1035 label has to begin with a letter. That is the whole of it, and it accounts for nearly every naming rejection.

RFC 1123 subdomain   253 characters
  [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*

RFC 1123 label       63 characters
  [a-z0-9]([-a-z0-9]*[a-z0-9])?

RFC 1035 label       63 characters
  [a-z]([-a-z0-9]*[a-z0-9])?

Which field gets which

The subdomain rule is the default and covers the great majority of objects, which is why most names feel unrestricted. The exceptions are the two that end up in DNS on their own: a Namespace name and a Service name both become a single DNS label. Inside a pod spec the label rule applies again, so the fields you write next to each other in one file are not held to the same standard.

RFC 1123 subdomain   Deployment, ConfigMap, Secret,
                     Ingress, PVC, Job, CronJob, Pod, Node

RFC 1123 label       Namespace, metadata.namespace,
                     container names, volume names

RFC 1035 label       Service

IANA service name    port names, 15 characters

The dot is where a Service breaks

A dot is part of the subdomain alphabet and is not part of a label, so the same string is fine on a ConfigMap and refused on a Service. This is the one that catches people copying a hostname into a name field, and the message from the API server names the regex rather than the dot.

configmap/api.internal    valid
secret/api.internal       valid
service/api.internal      rejected, position 4 is a dot
namespace/api.internal    rejected, position 4 is a dot

a Service name is one DNS label, and there
are no dots inside a DNS label

A digit can start any name except a Service

RFC 1035 requires a leading letter, and a Service name is the only place Kubernetes still applies it. So a Deployment called 1st-api applies cleanly and the Service in front of it does not, from the same file, in the same apply.

deployment/1st-api    valid
service/1st-api       rejected at position 1

no correction is suggested for this one: a
prefix or a reordering changes the name

Names that pass and then break something downstream

This is the half nothing warns about. The field accepts the name, the object is created, and a controller then builds another object's name out of it and runs out of room. A CronJob is the only one of these the API server enforces up front. The rest are silent, and two of them fail in a way that never mentions the name.

CronJob      52   run Job is <name>-<minutes since epoch>
                  enforced at apply time

Job          63   pods carry the name in the job-name
                  label, so the pods are rejected and
                  the Job sits at 0 completions

Deployment   46   pods are <name>-<hash>-<5 chars>, the
                  base is truncated to 58, so the pod
                  name stops containing the whole name

DaemonSet    57   pods are <name>-<5 chars>

StatefulSet  63 minus 1 minus the ordinal width, since
             each pod name is a DNS label of its own

Pod          63   the hostname is the name, truncated

generateName is not a name, and is checked as the one it becomes

The server truncates the base to 58 characters and appends 5 random ones, so the result is always 63 characters or fewer and always ends in a letter or a digit. What has to be valid is therefore the base plus 5 characters, not the base, and that is the string checked here against the rule for the kind. Three consequences you can see in the report: a base ending in a hyphen passes where the same string in metadata.name is rejected, a base starting with a digit passes on a Pod and fails on a Service, and the lower limit a controller imposes is measured against the generated name too, so a 55 character CronJob base is reported at the 60 characters it becomes. That last one is the hardest to catch by eye, because no length in the manifest says 60. There is also a trap that has nothing to do with validity, so it is reported on every generateName either way: kubectl apply needs a name to diff against and fails with "resource name may not be empty". kubectl create works, and so does a Job created from a CronJob.

generateName: worker-     checked as worker-abcde
  verdict   No name to check, 1 note on this object

generateName: -bad        checked as -badabcde
  verdict   No name to check, and 1 thing that will
            still cause a failure
  reason    "-" cannot start a piece of the name

name: worker-             rejected, "-" cannot end
                          a piece of the name

CronJob, 55 character base  becomes 60 characters
  verdict   No name to check, and 1 thing that will
            still cause a failure
  reason    past the 52 a CronJob really has
  fix       shorten the base to 47 or fewer

kubectl apply    resource name may not be empty
kubectl create   fine

253 characters the API server allows and DNS does not

Kubernetes bounds a subdomain name at 253 in total and says nothing about the pieces between the dots. DNS bounds each piece at 63 octets. A name can therefore be created and then fail to resolve the moment anything turns it into a hostname, which is reported separately here because the object exists either way.

<64 characters>.example.com

total length 76, under the 253 limit
first piece 64 characters, over the DNS 63
accepted by the API server, unresolvable

Where the underscore habit comes from

No object name allows an underscore, and three things sitting right next to object names do: a label value, a ConfigMap or Secret key, and an environment variable name. So the character is legal in most of a manifest and illegal in the one field people reach for first. Uppercase is the same story: legal in a label value, never in a name.

my_app  as a container name      rejected
my_app  as a ConfigMap key       valid
my_app  as a label value         valid
MY_APP  as an env var name       valid

What this does not check

Only the text. It does not know whether the name is already taken in the namespace, since that needs the cluster, and it does not know what else references the name you are about to change: a Service selector, an Ingress backend, a PVC claimName, a kustomize patch. Those are the reason a rename is not free, and they are not visible in the file being checked. For full schema validation against a specific cluster version, kubeconform is the right tool.

checked      the characters, the length, and the
             rule for the field the name sits in

not checked  uniqueness in the namespace
             every other object naming this one

More kubernetes tools

Kubernetes YAML Generator Answer a few questions, get a manifest Kubernetes YAML Validator Is this YAML valid, and where is it wrong? Kubernetes Rollout & Probe Timing Restart loops, capacity dips and stuck drains Kubernetes Service Checker Will this Service have any endpoints? Kubernetes Network Policy Viewer What can reach what, and what just broke Kubernetes Manifest Diff What actually changed Kubernetes Resource Calculator What these manifests actually reserve Kubernetes RBAC Viewer Who can actually do what Kubernetes Deployment Generator See the capacity dip before you deploy Kubernetes Service Generator port, targetPort, and which one you meant Kubernetes StatefulSet Generator Stable names, and volumes that outlive you Kubernetes QoS Class Calculator And where you sit in the eviction order Kubernetes Node Capacity Planner Allocatable is not capacity Kubernetes Max Pods per Node Calculator The ENI limit, not the 110 default Kubernetes Label Selector Tester Which key failed, not just that one did Kubernetes Taint & Toleration Tester Tolerations permit, they do not attract Kubernetes Label & Annotation Validator Why your image tag is not a legal label Kubernetes CrashLoopBackOff Guide The status is a timer, not a cause Kubernetes TLS Certificate Decoder When it expires, and what it covers Kubernetes ServiceAccount Token Decoder Bound or legacy, and when it dies Kubernetes imagePullSecret Generator The registry URL nobody gets right Kubernetes cert-manager Certificates Issuer scope, and the rate limit that costs a week Kubernetes Flux HelmRelease Generator With the source it cannot work without Kubernetes Helm Chart Generator Chart.yaml, values, and working templates Kubernetes Argo CD Application Generator The defaults that are off, turned on Kubernetes Ingress Generator pathType, and the TLS secret that fails quietly Kubernetes HPA Generator Why it says unknown and never scales Kubernetes SecurityContext Generator Pod level, container level, and which wins Kubernetes Probe Generator How long it gets to start before liveness kills it Kubernetes PVC Generator Access modes your storage can actually do Kubernetes ResourceQuota Generator And the LimitRange that stops it breaking deploys Kubernetes CronJob Generator The fields that decide whether it runs Kubernetes NetworkPolicy Generator With the DNS rule already in it Kubernetes RBAC Generator And what it actually grants Kubernetes PodDisruptionBudget Generator Can this budget ever be satisfied? Kubernetes API Deprecation Checker What breaks on the next upgrade Kubernetes Kubeconfig Viewer Read it without handing it to anyone Kubernetes Quantity Parser What 512Mi, 100m and 1e3 actually mean Kubernetes Multi-Document YAML Splitter One file per object, in apply order Kubernetes .env to ConfigMap & Secret Split the credentials out on the way Kubernetes Secret Decoder See what is actually in there Kubernetes Ingress to Gateway API Which annotations silently stop working Kubernetes Manifest Visualizer What points at what, and what points at nothing Kubernetes Patch Tester Which --type, and what it deletes Kubernetes kubectl JSONPath Tester kubectl's dialect, not generic JSONPath Kubernetes Node Affinity Tester Required excludes, preferred only scores Kubernetes Helm Values Diff A missing key is a third value Kubernetes Pod and Service CIDR Fixed at cluster creation Kubernetes Secret Encoder base64 is not encryption Kubernetes API Version Checker Will this apply on that cluster Kubernetes Go Template Tester Go truthiness is not JavaScript's Kubernetes Cluster Cost Estimator At your rates, not a price table Kubernetes Kustomize Build Transformers run in kustomize's order Kubernetes Helm Chart Validator version is SemVer, appVersion is not

Elsewhere on the site