Kubernetes Labels and Selectors with Examples

Learn how Kubernetes labels and selectors work, add and remove labels with kubectl on Pods, nodes, and Deployments, and use matchLabels and matchExpressions in YAML.

Published

Updated

Read time 22 min read

Reviewed byDeepak Prasad

Kubernetes labels and selectors on Pods, Deployments, Services, and nodes
Tested on Rocky Linux 10.2 (Red Quartz) workstation
Package kubectl 1.36.3
Applies to Any host with kubectl configured; any Kubernetes cluster
Cert prep CKAD · CKA · CKS
Lab environment Multi-node kubeadm cluster with containerd — install Kubernetes with kubeadm
Privilege Normal user (no sudo required on the workstation)
Scope Labels, selectors, kubectl label and get -l, matchLabels and matchExpressions, and how Deployments and Services use selectors. Does not cover annotations, nodeSelector or affinity, taints, full Service or NetworkPolicy tutorials, or admission-controller annotations.
Related guides Kubernetes labels vs annotations
Kubernetes annotations
Kubernetes namespaces
Kubernetes pods
Kubernetes API resources

Labels identify and group Kubernetes objects. Selectors find objects by those labels. The sections below start with a quick kubectl reference, explain where labels belong on each object type, then split labels in a YAML manifest (at creation time or kubectl apply) from updating labels on existing objects with kubectl label. For how labels differ from annotations, use the comparison guide in Related guides above. For annotation procedures, see the Kubernetes annotations guide there.


Quick reference: kubectl label commands

Task Command
Add a label to a Pod kubectl label pod nginx environment=production
Add a label to a Node kubectl label node worker1 disktype=ssd
Add a label to a Deployment kubectl label deployment web tier=frontend
Add a label to a Namespace kubectl label namespace development environment=dev
Overwrite an existing label kubectl label pod nginx environment=staging --overwrite
Remove a label kubectl label pod nginx environment-
Display all labels kubectl get pods --show-labels
Filter resources by label kubectl get pods -l app=nginx

The table uses short names such as nginx and web. Add labels in a YAML manifest shows placement when you create or apply a resource; Update labels on existing objects with kubectl walks through kubectl label in a labels-demo lab.


What are Kubernetes labels?

Kubernetes labels are key/value metadata attached to objects such as Pods, Deployments, nodes, and namespaces. You use them to:

  • filter objects with kubectl get -l and dashboards
  • let controllers find and own Pods through spec.selector
  • let Services, NetworkPolicies, and other resources match related Pods

Labels are not a substitute for object names. Many objects can share the same label value so you can treat them as one group.

Label key and value syntax

Labels follow an optional prefix and a name:

text
optional-prefix/name=value

Examples:

text
environment=production
tier=frontend
example.com/owner=platform
app.kubernetes.io/name=web

Label keys use an optional DNS-style prefix and a name separated by /:

  • Name portion: at most 63 characters
  • Optional DNS prefix: at most 253 characters
  • Values: at most 63 characters, or empty
  • Character rules: non-empty name and value components must start and end with an alphanumeric character; dashes, underscores, and dots are allowed internally
  • Reserved prefixes: kubernetes.io/ and k8s.io/ (for Kubernetes components)

Each label key must be unique on one object. Keep values short enough to filter and display in kubectl tables.


Where labels live on Kubernetes objects

kubectl label always changes metadata.labels on the object you target. That field exists on every resource kind — Ingress, NetworkPolicy, Namespace, and node included. Labeling a Deployment or StatefulSet does not change its Pod template; update spec.template.metadata.labels separately when new Pods must inherit the label. Controllers copy labels from a Pod template to new Pods; they do not copy labels from a parent Deployment or StatefulSet object. The rule for every resource kind lists where spec selectors fit in.

Controller ownership flow

A Deployment does not create Pods directly. It creates ReplicaSets, and each ReplicaSet creates Pods:

text
Deployment
  └── ReplicaSet (one per revision; adds pod-template-hash)
        └── Pod (labels from spec.template.metadata.labels)

A StatefulSet creates Pods directly with stable names. There is no ReplicaSet in between:

text
StatefulSet
  └── Pod (labels from spec.template.metadata.labels)

The Deployment controller adds pod-template-hash to each ReplicaSet’s selector, ReplicaSet Pod template, and the Pods that ReplicaSet creates. It does not add that key to the Deployment’s own spec.selector. See Kubernetes ReplicaSet for how ReplicaSets maintain Pod counts.

Where to place labels

Goal Label here Reaches Pods? Survives Pod recreate?
Tag the Deployment or StatefulSet resource metadata.labels on the controller No N/A
Label every Pod the controller manages spec.template.metadata.labels Yes Yes, after rollout
Define which Pods the controller owns spec.selector.matchLabels (and matchExpressions when used) N/A (immutable after creation on Deployments and StatefulSets) Yes
One-off label on a running Pod kubectl label pod That Pod only No

Selector requirements vs other Pod labels

On a controlled Pod, some labels matter for ownership and some only for grouping or routing:

  • Selector requirements — conditions under spec.selector.matchLabels and/or spec.selector.matchExpressions. The Pod-template labels must satisfy every requirement.
  • Template labels — all keys under spec.template.metadata.labels. Every new Pod gets these. Every matchLabels key/value pair must appear in the Pod-template labels. The complete template label set must also satisfy every matchExpressions requirement. matchLabels and matchExpressions are evaluated together using logical AND.
  • Extra Pod labels — keys on the template or added with kubectl label pod that are not in the selector. Services can match these for traffic (for example tier=frontend) as long as the Pod carries the key.

Why selector requirements matter:

  • The Deployment or StatefulSet controller lists Pods by matching spec.selector against Pod labels. A Pod without those keys is not part of that controller’s managed Pod set.
  • On Deployments, the controller adds pod-template-hash to each ReplicaSet’s selector, ReplicaSet Pod template, and Pods — not to the Deployment’s own spec.selector. Do not set or edit that key yourself.
  • A Service uses its own spec.selector to pick Pods for traffic. That is separate from the controller selector, but both read labels on the Pod — often the same app key satisfies each.
  • Keep selector keys stable and minimal (usually app or app.kubernetes.io/name). Put environment, tier, or release tags in template labels unless the controller must match them too.
  • Avoid removing or changing selector keys on a running Pod with kubectl label. The controller may stop managing that Pod. Patch the template and roll out when you need a permanent change.

Labeling a Deployment with kubectl label deployment updates metadata.labels on the Deployment object only. It does not add that key to Pods unless you also patch spec.template.metadata.labels and roll out.

If the selector and template disagree, Kubernetes rejects the object. Both Deployments and StatefulSets require the selector to match the Pod template:

output
The Deployment "bad-web" is invalid: spec.template.metadata.labels: Invalid value: {"app":"frontend"}: `selector` does not match template `labels`

Add labels in a YAML manifest

When you create a resource with kubectl apply -f, Helm, or a CI pipeline, place labels in the manifest before the object reaches the API. Kubernetes attaches labels at creation time; each later apply can update metadata.labels or spec.template.metadata.labels when those fields change in the file.

This section covers manifest placement only. To add or change labels on objects that already exist in the cluster, use Update labels on existing objects with kubectl below.

When How Typical use
In a YAML manifest metadata.labels or spec.template.metadata.labels in the file you apply Version-controlled defaults every new Pod should carry
On existing objects kubectl label, kubectl patch Quick lab edits, one-off tags, or operational changes without editing the manifest

The rule for every resource kind

Every API object has one top-level metadata block. Put labels there to tag that object (the Deployment, Ingress, Pod, and so on):

yaml
metadata:
  name: web
  labels:
    key: value

That top-level block works on Pods, Deployments, StatefulSets, Services, Ingresses, NetworkPolicies, Namespaces, nodes, and any other resource you can kubectl label. kubectl label always writes this metadata.labels map on the object you name.

Workload controllers that create Pods add a second metadata block nested under the Pod template. It is not a duplicate of the controller’s metadata — it becomes each Pod’s metadata.labels when the controller creates them:

yaml
spec:
  template:
    metadata:          # Pod metadata (not the Deployment’s)
      labels:
        app: web
    spec:
      containers: ...

kubectl label deployment changes only the top-level metadata.labels. Patch spec.template.metadata.labels when every new Pod should inherit a key.

Some kinds also use labels in spec — but only to match other objects, not to label themselves:

Kind of field YAML path (examples) What it does
Object labels metadata.labels Tags this object; kubectl get -l reads it
Pod template labels spec.template.metadata.labels Copied onto every Pod a controller creates
Controller selector spec.selector.matchLabels / matchExpressions Which Pod labels this controller owns
Service selector spec.selector Which Pod labels receive traffic
NetworkPolicy selectors spec.podSelector, spec.namespaceSelector, nested from / to entries Which Pod or Namespace labels the rule applies to
Scheduling spec.nodeSelector, node affinity terms Which node labels a Pod may run on

You do not need a separate manifest pattern per resource for object labels. Add metadata.labels on an Ingress or NetworkPolicy the same way you do on a Pod. The extra rows apply only when that resource creates Pods or selects other objects by label.

Deployment manifest

A Deployment has three label areas. Align object labels, selector requirements, and template labels when you write the file:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  labels:
    app: web
spec:
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
        environment: development
        tier: frontend
    spec:
      containers:
        - name: nginx
          image: nginx:1.28.3-alpine
  • Object labelsmetadata.labels on the Deployment object itself
  • Selector requirementsspec.selector.matchLabels and/or matchExpressions; conditions managed Pods must satisfy (immutable after creation)
  • Template labelsspec.template.metadata.labels; labels placed on every Pod the controller creates

The selector must match the Pod template. Extra template labels such as tier can exist for Services and filters without appearing in the selector. Labeling the Deployment with kubectl label updates only metadata.labels on that object; patch the template when managed Pods need the label.

StatefulSets use the same object-label, selector, and Pod-template-label placement as Deployments. Unlike Deployments, they create Pods directly rather than through ReplicaSets.

DaemonSets and ReplicaSets use spec.selector with spec.template.metadata.labels. Jobs normally use an automatically generated selector. CronJobs place Job metadata under spec.jobTemplate.metadata and Pod labels under spec.jobTemplate.spec.template.metadata.labels. For any other kind, start with metadata.labels; use kubectl explain <kind>.spec to see whether that type exposes label selectors in spec.


Update labels on existing objects with kubectl

The commands in this section update labels on existing objects — the workflow Kubernetes documents as updating labels. They do not replace Pod-template labels in a manifest when you need every replacement Pod to inherit a key; patch spec.template.metadata.labels in YAML or with kubectl patch as shown below.

The examples use namespace labels-demo and a three-replica web Deployment. Create them once, then run the label commands in order.

Create an isolated namespace for the lab:

bash
kubectl create namespace labels-demo

The namespace gives you a sandbox so label experiments do not touch other workloads.

Create a Deployment with three nginx Pods. kubectl create deployment sets app=web on the Pod template automatically:

bash
kubectl create deployment web --image=nginx:1.28.3-alpine --replicas=3 -n labels-demo

Wait until all three Pods are ready before you label them:

bash
kubectl rollout status deployment/web -n labels-demo --timeout=120s

The command exits with successfully rolled out when every replica is Running.

Label Pods with kubectl

Pick one Pod from the Deployment and store its name in $POD:

bash
POD=$(kubectl get pods -l app=web -n labels-demo -o jsonpath='{.items[0].metadata.name}')

The variable holds one Pod name such as web-574f549468-bb96q so later commands do not hard-code a generated suffix.

Add owner=team-a to that Pod:

bash
kubectl label pod "$POD" owner=team-a -n labels-demo

Verify the label on that Pod:

bash
kubectl get pod "$POD" -n labels-demo --show-labels

Sample output:

output
NAME                   READY   STATUS    RESTARTS   AGE   LABELS
web-574f549468-bb96q   1/1     Running   0          5s    app=web,owner=team-a,pod-template-hash=574f549468

The new owner key appears only on the Pod you labeled. Replacement Pods from the Deployment will not inherit it unless you add it to the Pod template.

Pass several key=value pairs in one command:

bash
kubectl label pod "$POD" release=2026.07 cost-center=eng -n labels-demo

Both keys are written in a single API update on the same Pod.

Select existing Pods with -l and apply another label to the whole set:

bash
kubectl label pods -l app=web tier=frontend -n labels-demo

Confirm every replica carries the label:

bash
kubectl get pods -l app=web -n labels-demo --show-labels

Sample output:

output
NAME                   READY   STATUS    RESTARTS   AGE   LABELS
web-574f549468-bb96q   1/1     Running   0          6s    app=web,cost-center=eng,owner=team-a,pod-template-hash=574f549468,release=2026.07,tier=frontend
web-574f549468-gl9b5   1/1     Running   0          6s    app=web,pod-template-hash=574f549468,tier=frontend
web-574f549468-x4p8w   1/1     Running   0          6s    app=web,pod-template-hash=574f549468,tier=frontend

Every Pod selected by app=web now includes tier=frontend, including replicas you did not target individually.

Label a Deployment object with kubectl

Label the Deployment resource itself — not its Pods:

bash
kubectl label deployment web environment=development -n labels-demo

Confirm the label landed on the Deployment object, not on its Pods:

bash
kubectl get deployment web -n labels-demo --show-labels

Sample output:

output
NAME   READY   UP-TO-DATE   AVAILABLE   AGE   LABELS
web    3/3     3            3           11s   app=web,environment=development

That command updates metadata.labels on the Deployment. It does not add environment to Pods the Deployment already manages.

Patch Pod template labels on a Deployment

To persist labels on new Pods, patch spec.template.metadata.labels with every key replacement Pods should carry:

bash
kubectl patch deployment web -n labels-demo --type=merge -p '{"spec":{"template":{"metadata":{"labels":{"environment":"development","tier":"frontend"}}}}}'

Wait until the new ReplicaSet finishes replacing Pods:

bash
kubectl rollout status deployment/web -n labels-demo --timeout=120s

Check that new Pods carry the template labels:

bash
kubectl get pods -l app=web -n labels-demo -L app,tier,environment

Sample output:

output
NAME                   READY   STATUS    RESTARTS   AGE   APP   TIER       ENVIRONMENT
web-7dd89dc696-f9rrb   1/1     Running   0          6s    web   frontend   development
web-7dd89dc696-jcbw8   1/1     Running   0          9s    web   frontend   development
web-7dd89dc696-wcfpb   1/1     Running   0          4s    web   frontend   development

Changing the Pod template triggers a new ReplicaSet and replaces Pods. The Pod stored in $POD from earlier steps is terminated during that rollout. Pick a Running Pod again before any later single-Pod command:

bash
POD=$(kubectl get pods -l app=web -n labels-demo --field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}')

Manual labels such as owner, release, and cost-center stay only on the old Pod unless you add them to the template.

Inspect the live Deployment to see all three label areas together:

bash
kubectl get deployment web -n labels-demo -o yaml

The metadata.labels, spec.selector, and spec.template.metadata.labels stanzas should match the placement table above.

Label nodes and namespaces with kubectl

Nodes accept labels the same way as namespaced objects. On the lab cluster the worker node is worker01:

bash
kubectl label node worker01 disktype=ssd

Verify the node label:

bash
kubectl get node worker01 --show-labels

Sample output (trimmed):

output
NAME       STATUS   ROLES    AGE   VERSION   LABELS
worker01   Ready    <none>   21h   v1.36.3   ...,disktype=ssd,...

Overwrite the value when you need to change hardware class:

bash
kubectl label node worker01 disktype=hdd --overwrite

The node now advertises disktype=hdd to the scheduler.

Remove the label with the trailing-hyphen syntax:

bash
kubectl label node worker01 disktype-

The disktype key disappears from the node’s metadata.labels.

Node labels feed scheduling features such as nodeSelector and node affinity. This article stops at labeling nodes; scheduling configuration belongs in workload guides.

Label the namespace that holds the lab objects:

bash
kubectl label namespace labels-demo environment=dev

List namespace labels to confirm the key is stored on the Namespace object:

bash
kubectl get namespace labels-demo --show-labels

Sample output:

output
NAME          STATUS   AGE   LABELS
labels-demo   Active   21s   environment=dev,kubernetes.io/metadata.name=labels-demo

Kubernetes automatically adds the immutable kubernetes.io/metadata.name label on every namespace. That key mirrors the namespace name and cannot be removed.

Overwrite, remove, and preview label changes

kubectl label refuses to change an existing key unless you opt in:

bash
kubectl label deployment web environment=production -n labels-demo

Sample output:

output
error: 'environment' already has a value (development), and --overwrite is false

Apply the new value with --overwrite:

bash
kubectl label deployment web environment=production --overwrite -n labels-demo

The Deployment’s metadata.labels now shows environment=production.

Append - to the key name to delete a label:

bash
kubectl label deployment web environment- -n labels-demo

The environment key is removed from the Deployment object only. Pod-template labels and running Pods are unchanged.

Client-side dry-run with -o yaml prints the Pod manifest Kubernetes would send, including the new label:

bash
kubectl label pod "$POD" testkey=testval -n labels-demo --dry-run=client -o yaml

Sample output (labels stanza):

output
labels:
    app: web
    environment: development
    pod-template-hash: 7dd89dc696
    testkey: testval
    tier: frontend

The testkey entry appears in the printed YAML, but the live Pod is unchanged until you run the command without --dry-run. For more dry-run modes and server-side validation, see kubectl commands and YAML examples.


View labels on objects

Labels set in a manifest and labels added with kubectl label both end up in metadata.labels on the object. These read commands work the same whether the key was set at creation time or added later.

If you are continuing from the lab above, $POD should still be set. Otherwise, store one Pod name first:

bash
POD=$(kubectl get pods -l app=web -n labels-demo -o jsonpath='{.items[0].metadata.name}')

List every label on that Pod:

bash
kubectl get pod "$POD" -n labels-demo --show-labels

Sample output:

output
NAME                   READY   STATUS    RESTARTS   AGE   LABELS
web-7dd89dc696-f9rrb   1/1     Running   0          14s   app=web,environment=development,pod-template-hash=7dd89dc696,tier=frontend

Show selected keys as table columns with -L:

bash
kubectl get pods -n labels-demo -L app,tier,environment

Sample output:

output
NAME                   READY   STATUS    RESTARTS   AGE   APP   TIER       ENVIRONMENT
web-7dd89dc696-f9rrb   1/1     Running   0          14s   web   frontend   development
web-7dd89dc696-jcbw8   1/1     Running   0          6s    web   frontend   development
web-7dd89dc696-wcfpb   1/1     Running   0          11s   web   frontend   development

Inspect the full label map in YAML:

bash
kubectl get pod "$POD" -n labels-demo -o yaml

The metadata.labels stanza in that output is the authoritative list of keys on the object.


Select Kubernetes resources by label

A label selector lists objects whose metadata.labels satisfy the expression. Both kubectl and many API fields use the same grammar.

Select with -l or --selector

These flags are equivalent:

bash
kubectl get pods -l app=web -n labels-demo
bash
kubectl get pods --selector=app=web -n labels-demo

Both commands list Pods in labels-demo where app=web.

Comma-separated requirements use logical AND — every listed key must match:

bash
kubectl get pods -l 'app=web,tier=frontend' -n labels-demo

Only Pods that carry both labels are returned.

An existence selector lists objects that define the key, regardless of value:

bash
kubectl get pods -l tier -n labels-demo

Pods without a tier label are omitted from the list.

Use !key for non-existence. After the template rollout above, none of the replacement Pods carry owner:

bash
kubectl get pods -l '!owner' -n labels-demo

Sample output:

output
NAME                   READY   STATUS    RESTARTS   AGE
web-7dd89dc696-f9rrb   1/1     Running   0          8s
web-7dd89dc696-jcbw8   1/1     Running   0          10s
web-7dd89dc696-wcfpb   1/1     Running   0          5s

Show specific keys as columns without printing the full LABELS string:

bash
kubectl get pods -l app=web -n labels-demo -L app,tier,environment

Each named key appears in its own column for easier scanning.

Equality-based selectors

Equality-based selectors compare one key to one value. Supported operators: =, ==, and !=.

List Pods where tier equals frontend:

bash
kubectl get pods -l tier=frontend -n labels-demo

= and == mean the same thing:

bash
kubectl get pods -l tier==frontend -n labels-demo

Both forms return the same Pod set.

Exclude a value — negative selectors also match objects where the key is absent:

bash
kubectl get pods -l 'environment!=production' -n labels-demo

Pods labeled environment=development match; Pods with no environment key match as well.

To require that environment exists and is not production:

bash
kubectl get pods -l 'environment,environment!=production' -n labels-demo

The first term requires the key; the second excludes the production value.

Operator Meaning
= Label equals the value
== Label equals the value
!= Value differs from the specified value or the key is absent

Set-based selectors

Set-based selectors match keys against a set of values or test key existence.

Match Pods whose environment is development or staging:

bash
kubectl get pods -l 'environment in (development,staging)' -n labels-demo

Exclude Pods whose tier is backend or database:

bash
kubectl get pods -l 'tier notin (backend,database)' -n labels-demo

Pods with tier=frontend match. Pods with no tier key also match notin.

To require that tier exists while excluding backend roles:

bash
kubectl get pods -l 'tier,tier notin (backend,database)' -n labels-demo

The existence check and the notin filter are ANDed together.

Multiple selector requirements are joined with AND. Kubernetes label selectors do not offer a general OR between separate keys. Use in (...) when one key may have several acceptable values.

Selector Meaning
key in (value1,value2) Value matches one of the listed values
key notin (value1,value2) Value is not listed or the key is absent
key Label key exists
!key Label key does not exist

Quote selectors that contain parentheses so the shell passes them intact.


Use matchLabels and matchExpressions in YAML

Controllers and some API objects express selectors as matchLabels and matchExpressions instead of a single -l string.

Use matchLabels

Each key/value pair in matchLabels must match exactly:

yaml
selector:
  matchLabels:
    app: web

Use matchExpressions

Set-based rules use key, operator, and optional values:

yaml
selector:
  matchExpressions:
    - key: environment
      operator: In
      values:
        - development
        - staging

Operators in YAML mirror the CLI: In, NotIn, Exists, and DoesNotExist.

For In and NotIn, you must supply a values list. For Exists and DoesNotExist, omit values entirely.

yaml
selector:
  matchExpressions:
    - key: environment
      operator: NotIn
      values:
        - production
        - staging
    - key: tier
      operator: Exists
    - key: deprecated
      operator: DoesNotExist
Operator values required? Meaning
In Yes Label value is in the list
NotIn Yes Label value is not in the list, or the key is absent
Exists No Label key is present (any value)
DoesNotExist No Label key is absent

A Pod must satisfy every matchExpressions entry and every matchLabels pair when both are present:

yaml
selector:
  matchLabels:
    app: web
  matchExpressions:
    - key: environment
      operator: In
      values:
        - development
        - staging
Selector Best suited for
matchLabels Exact key/value matching
matchExpressions Set-based or existence conditions

Service spec.selector remains an equality-only map and does not accept matchExpressions.


How Services and policies use selectors

Incorrect selector relationships are a common cause of Services with no endpoints and Deployments that reject Pod templates.

Service selectors and Pod labels

A Service sends traffic to Pods whose labels match spec.selector. Create a ClusterIP Service for the lab Deployment:

yaml
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80

Apply it in labels-demo:

bash
kubectl apply -f web-service.yaml -n labels-demo

Kubernetes creates a ClusterIP Service that selects Pods with app=web.

Describe the Service to read the selector and endpoints:

bash
kubectl describe service web -n labels-demo

Sample output (trimmed):

output
Selector:                 app=web
Type:                     ClusterIP
IP:                       10.110.224.72
Endpoints:                192.168.5.47:80,192.168.5.46:80,192.168.5.48:80

EndpointSlices mirror the same Pod set the Service selected:

bash
kubectl get endpointslices -l kubernetes.io/service-name=web -n labels-demo

Sample output:

output
NAME        ADDRESSTYPE   PORTS   ENDPOINTS                                AGE
web-cs6v7   IPv4          80      192.168.5.47,192.168.5.46,192.168.5.48   3s

The Service selector matches Pod labels, regardless of readiness. Readiness determines whether matching endpoints normally receive traffic; an unready Pod can still appear in EndpointSlices with its ready condition set to false.

Full Service types, ports, and headless Services are covered in Kubernetes Services.

NetworkPolicy rules consume Pod and Namespace labels you set elsewhere; this article does not walk through policy authoring. Schedulers read node labels through nodeSelector and node affinity when you label nodes with kubectl label node.


The app.kubernetes.io/ prefix is a recommended convention, not a requirement:

yaml
metadata:
  labels:
    app.kubernetes.io/name: web
    app.kubernetes.io/instance: web-development
    app.kubernetes.io/version: "1.0"
    app.kubernetes.io/component: frontend
    app.kubernetes.io/part-of: storefront
    app.kubernetes.io/managed-by: Helm
Label Purpose
app.kubernetes.io/name Name of the application
app.kubernetes.io/instance Unique application instance
app.kubernetes.io/version Application version
app.kubernetes.io/component Component within the application
app.kubernetes.io/part-of Larger application or system
app.kubernetes.io/managed-by Tool managing the resource

Pick a consistent subset you will query in dashboards and kubectl, and keep custom keys such as environment when they help operations.


How labels differ from annotations

Labels are for selection and grouping; annotations are for description selectors ignore. The comparison guide in Related guides covers side-by-side YAML and when to use each. This article covers labels and selectors only.


Label selectors vs field selectors

Selector type Filters by Example
Label selector metadata.labels -l app=nginx
Field selector Supported resource fields --field-selector status.phase=Running

Label selectors support equality and set-based conditions on labels. Field selectors depend on fields exposed by each resource type and do not replace labels for grouping application workloads.

List running Pods with a field selector:

bash
kubectl get pods --field-selector status.phase=Running -n labels-demo

Sample output:

output
NAME                   READY   STATUS    RESTARTS   AGE
web-7dd89dc696-f9rrb   1/1     Running   0          8s
web-7dd89dc696-jcbw8   1/1     Running   0          14s
web-7dd89dc696-wcfpb   1/1     Running   0          11s

Field selectors answer questions about object state. Labels answer questions about ownership and application identity.


Troubleshoot Kubernetes labels and selectors

Symptom Likely cause Fix
kubectl label says the label already has a value Key exists without --overwrite Pass --overwrite or remove the key first
kubectl get -l returns no resources Wrong namespace, spelling, or syntax kubectl get pods --show-labels -n <ns>
Labelling a Deployment did not label its Pods Label on Deployment metadata only Patch spec.template.metadata.labels and roll out
`selector` does not match template `labels` on apply spec.selector disagrees with spec.template.metadata.labels Every matchLabels pair must appear on the template; the template must satisfy every matchExpressions rule
spec.selector: ... field is immutable on patch Deployment or StatefulSet selector changed after creation Recreate the workload with the new selector; spec.selector cannot change on an existing controller
Service has no endpoints after label change Service selector no longer matches Pods Compare Service selector and Pod labels; check EndpointSlices
Invalid label key or value Illegal characters or length Follow label syntax rules; avoid reserved prefixes for custom keys
Set-based selector fails in the shell Unquoted parentheses Quote the selector: -l 'environment in (dev,prod)'
Controller stops counting a relabelled Pod Selector label changed or removed on a controlled Pod Update the Pod template instead; the old Pod may keep running outside the managed set while a new Pod is created

selector does not match template labels

Kubernetes rejects a Deployment or StatefulSet when spec.selector does not match the Pod-template labels. The API returns a message like this:

output
The Deployment "bad-web" is invalid: spec.template.metadata.labels: Invalid value: {"app":"frontend"}: `selector` does not match template `labels`

A common trigger is app set differently in the selector and the template:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bad-web
spec:
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
        - name: nginx
          image: nginx:1.28.3-alpine

The selector requires app=web, but the template advertises app=frontend. Change the template label to app: web, or fix the selector before the first apply.

The same error appears when matchExpressions fail. A selector that requires environment In (production) rejects a template labeled environment: development even when matchLabels already agree:

yaml
selector:
    matchLabels:
      app: web
    matchExpressions:
      - key: environment
        operator: In
        values:
          - production
  template:
    metadata:
      labels:
        app: web
        environment: development

Sample output:

output
The Deployment "bad-expr" is invalid: spec.template.metadata.labels: Invalid value: {"app":"web","environment":"development"}: `selector` does not match template `labels`

Align the template with the selector. Either set environment: production on the Pod template:

yaml
template:
    metadata:
      labels:
        app: web
        environment: production

Or widen the expression if development Pods should be included:

yaml
matchExpressions:
      - key: environment
        operator: In
        values:
          - production
          - development

Both matchLabels and matchExpressions must pass on the same template label set.

Immutable spec.selector after creation

Once a Deployment or StatefulSet exists, you cannot change spec.selector. A patch that tries to change it fails:

output
The Deployment "web" is invalid: spec.selector: Invalid value: {"matchLabels":{"app":"changed"}}: field is immutable

Plan selector keys before the first apply. To adopt a different selector on a live workload, create a new Deployment or StatefulSet with the desired spec.selector and migrate, or delete and recreate the object.

Service has no endpoints

Read the Service selector Kubernetes is using:

bash
kubectl get service web -n labels-demo -o jsonpath='{.spec.selector}{"\n"}'

Sample output:

output
{"app":"web"}

Compare that map with labels on every Pod in the namespace:

bash
kubectl get pods -n labels-demo --show-labels

Confirm the same Pods match when you filter with -l:

bash
kubectl get pods -l app=web -n labels-demo

All three commands should agree on which Pods carry app=web.

Break the selector deliberately so no Pod matches:

bash
kubectl patch service web -n labels-demo -p '{"spec":{"selector":{"app":"frontend"}}}'

List EndpointSlices for the Service after the broken selector:

bash
kubectl get endpointslices -l kubernetes.io/service-name=web -n labels-demo

Sample output:

output
NAME        ADDRESSTYPE   PORTS     ENDPOINTS   AGE
web-tq9xd   IPv4          <unset>   <unset>     6s

No Pod carries app=frontend, so the EndpointSlice has no endpoints to list. Restore the working selector:

bash
kubectl patch service web -n labels-demo -p '{"spec":{"selector":{"app":"web"}}}'

EndpointSlices repopulate once the selector matches the Pod labels again.

Labels added manually to Pods disappear

A label on one running Pod does not update the Deployment template. When the Pod is recreated, only template labels return. Add persistent keys under spec.template.metadata.labels.


References


Summary

Labels identify and group Kubernetes objects. Place them on the right field in a YAML manifest or with kubectl label on existing objects — metadata.labels on a controller, spec.template.metadata.labels for every Pod it creates, and spec.selector.matchLabels for ownership. Selectors match those labels in kubectl, Deployments, ReplicaSets, and Services. Keep Deployment selectors aligned with Pod-template labels, point Service selectors at labels Pods actually carry, and verify EndpointSlices when Service traffic stops flowing.


Frequently Asked Questions

1. Can I use annotations in kubectl label selectors?

No. kubectl -l and Service or Deployment selectors match labels only. See Kubernetes labels vs annotations for the difference; for kubectl annotate, see Kubernetes annotations.

2. Why does my Service exist but have no endpoints?

A Service selector matches Pod labels, regardless of readiness. Readiness determines whether matching endpoints normally receive traffic. EndpointSlices can still contain an unready Pod address with its ready condition set to false. If the selector matches no Pods, EndpointSlices list no addresses even though the Service keeps its ClusterIP.

3. Can I change a Deployment selector after creation?

No. spec.selector on a Deployment or StatefulSet is immutable after creation. To change which labels a controller uses to find its Pods, recreate the workload with a new selector and migrate.

4. Does labeling a Deployment add the same label to its Pods?

No. kubectl label deployment only changes labels on the Deployment object. To label every Pod the controller manages, add the key under spec.template.metadata.labels and roll out the updated template.

5. Why does kubectl label refuse to change an existing value?

kubectl label protects existing keys unless you pass --overwrite. Use --overwrite when you intend to replace a label value on a live object.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)