Foundations

Labels, Selectors & Annotations

● Beginner ⏱ 10 min read

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:

pod-with-labels.yaml
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:

deployment-selector.yaml
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
⚠️
Selector is immutable

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.

Deployment
spec.selector.matchLabels:
app: payments-api
payments-api-7d4f8b-xk2l
app: payments-api
payments-api-7d4f8b-p9qr
app: payments-api
redis-abc123
app: redis
The Deployment selector matches pods by label — unmatched pods are ignored. Service uses the same mechanism to route traffic.

A Service also uses a selector to determine which pods receive traffic:

service-selector.yaml
apiVersion: v1
kind: Service
metadata:
  name: payments-api
spec:
  selector:
    app: payments-api   # routes to pods with this label
  ports:
  - port: 80
    targetPort: 8080

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

Labels
Key-value pairs, ≤ 63 chars each
Used by Kubernetes selectors to group resources
Deployments, Services, and ReplicaSets use them for routing
Selector is immutable once set
Example: app: payments, tier: backend
Annotations
Values can be any length (no limit)
Never used by K8s selectors — invisible to controllers
Used by external tools: Prometheus, cert-manager, Argo CD
Good for build metadata, runbook links, tool config
Example: prometheus.io/scrape: "true"
Labels identify and group — Annotations describe and configure tooling. Don't confuse them.

Annotations are key-value pairs intended for non-identifying metadata. Unlike labels:

deployment-annotations.yaml
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.