Labels, Selectors & Annotations
Labels and annotations are both key-value pairs attached to Kubernetes objects, but they serve different purposes. Labels are identifying metadata used by Kubernetes itself to select and group resources. Annotations hold non-identifying metadata for tooling, operators, and humans. Understanding the difference is fundamental to how Deployments, Services, and other controllers manage pods.
Labels
Labels are key-value pairs attached to the
metadata section of any Kubernetes object. Each label
consists of a key and a value:
-
Key — optionally prefixed (e.g.
app.kubernetes.io/name). The prefix must be a valid DNS subdomain ≤ 253 characters. The name part is ≤ 63 characters, alphanumeric, dashes, underscores, and dots. - Value — ≤ 63 characters, alphanumeric, dashes, underscores, and dots. May be empty.
apiVersion: v1
kind: Pod
metadata:
name: payments-api
labels:
app.kubernetes.io/name: payments
app.kubernetes.io/version: "2.4.1"
app.kubernetes.io/component: api
environment: production
tier: backend
spec:
containers:
- name: api
image: payments-api:2.4.1
Labels are inert on their own — they become powerful when selectors reference them.
Selectors
A selector is a query expression that matches objects by their labels.
Kubernetes uses selectors internally (e.g. a Service routes traffic to
pods whose labels match spec.selector) and exposes them
via kubectl for ad-hoc queries.
Equality-based selectors
Match resources where a label equals (or does not equal) a value:
# pods where environment=production AND tier=backend
kubectl get pods -l environment=production,tier=backend
# pods where environment is NOT production
kubectl get pods -l environment!=production
Set-based selectors
More expressive — match by membership in a set of values:
# pods where environment is one of staging or production
kubectl get pods -l 'environment in (staging, production)'
# pods where tier is NOT frontend
kubectl get pods -l 'tier notin (frontend)'
# pods that have the label "canary" (any value)
kubectl get pods -l canary
# pods that do NOT have the label "canary"
kubectl get pods -l '!canary'
Selectors in manifests
Controllers use selector fields in their spec. A
Deployment's pod template must carry matching labels:
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
spec:
selector:
matchLabels:
app: payments-api # selector — must match pod template
template:
metadata:
labels:
app: payments-api # pod labels — must match selector
spec:
containers:
- name: api
image: payments-api:2.4.1
Once a Deployment (or ReplicaSet, StatefulSet, DaemonSet) is
created, its spec.selector cannot be changed. To
change the selector you must delete and recreate the resource.
A Service also uses a selector to determine which pods receive traffic:
apiVersion: v1
kind: Service
metadata:
name: payments-api
spec:
selector:
app: payments-api # routes to pods with this label
ports:
- port: 80
targetPort: 8080
Recommended Label Schema
Kubernetes defines a set of well-known labels under the
app.kubernetes.io/ prefix. Using them consistently makes
your resources interoperable with dashboards, operators, and tooling:
| Label | Example | Meaning |
|---|---|---|
app.kubernetes.io/name |
payments |
Name of the application |
app.kubernetes.io/version |
2.4.1 |
Current version of the app |
app.kubernetes.io/component |
api |
Component within an application |
app.kubernetes.io/part-of |
ecommerce |
Larger application this is part of |
app.kubernetes.io/managed-by |
helm |
Tool used to manage the resource |
app.kubernetes.io/instance |
payments-prod |
Unique instance name (e.g. release name) |
Annotations
app: payments, tier: backendprometheus.io/scrape: "true"Annotations are key-value pairs intended for non-identifying metadata. Unlike labels:
- Annotation values can be arbitrary strings with no length limit.
- They are not used by Kubernetes selectors — controllers never use annotations to find or group resources.
- They are used by external tooling, admission controllers, operators, and CI/CD systems.
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
annotations:
# Deployment tracking
deployment.kubernetes.io/revision: "4"
# Prometheus scraping hints
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/metrics"
# Cert-manager configuration
cert-manager.io/cluster-issuer: letsencrypt-prod
# Human-readable notes
description: "Payment processing API — see runbook at wiki/payments"
git-commit: "a3f4c92"
Common real-world uses of annotations include:
| Tool | Annotation example |
|---|---|
| Ingress controllers |
nginx.ingress.kubernetes.io/rewrite-target: /
|
| cert-manager |
cert-manager.io/cluster-issuer: letsencrypt-prod
|
| Prometheus | prometheus.io/scrape: "true" |
| Argo CD | argocd.argoproj.io/sync-wave: "1" |
| Kubernetes itself |
kubectl.kubernetes.io/last-applied-configuration
|
Field Selectors
Field selectors filter resources by object fields (not labels) — useful for finding pods in a specific phase or bound to a specific node:
# pods that are currently Running
kubectl get pods --field-selector status.phase=Running
# pods scheduled on a specific node
kubectl get pods --field-selector spec.nodeName=worker-1
# services that are not ClusterIP
kubectl get services --field-selector spec.type!=ClusterIP
Field selectors support only equality operators (=,
==, !=) and the available fields vary by
resource type. They are less commonly used than label selectors but
invaluable for debugging.
Label Management with kubectl
While manifests are the right place to define labels at create time,
you can add, update, and remove labels imperatively with
kubectl label:
# Add or update a label
kubectl label pod nginx-pod environment=staging
# Remove a label (trailing -)
kubectl label pod nginx-pod environment-
# Label multiple objects at once
kubectl label pods --all tier=frontend
# Overwrite an existing label value
kubectl label pod nginx-pod version=2.0 --overwrite
Label selectors combine naturally with other kubectl commands. Use
-l to target groups of objects:
# List all pods in the staging environment
kubectl get pods -l environment=staging
# Delete all pods tagged as canary
kubectl delete pods -l track=canary
# Show all services belonging to team-a
kubectl describe services -l team=team-a
For bulk or permanent changes, prefer editing the manifest and running
kubectl apply — imperative label changes are not tracked
in Git and can cause drift between your repo and the live cluster. Use
kubectl label for debugging, incident response, or
temporary groupings.