| 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 versus annotations: purpose, comparison table, YAML placement, selector behavior, and when to use each. Does not teach kubectl label, kubectl annotate, matchLabels, or selector troubleshooting — those live in the linked tutorials. |
| Related guides | Kubernetes labels and selectors Kubernetes annotations Kubernetes pods kubectl commands and YAML examples |
Kubernetes stores two kinds of user-defined metadata on most objects: labels in metadata.labels and annotations in metadata.annotations. They sit beside each other in YAML, but Kubernetes treats them differently. Labels identify and group resources for selectors; annotations describe resources without participating in selection.
This page is a comparison only. For hands-on work, use the labels and annotations guides listed in Related guides above.
Quick answer
| Labels | Annotations | |
|---|---|---|
| YAML field | metadata.labels |
metadata.annotations |
| Primary purpose | Identify and group objects | Describe objects for people or tools |
| Used by selectors? | Yes | No |
| Typical value size | Short (≤ 63 characters) | Long strings, URLs, JSON (≤ 256 KiB total per object) |
| Example use | app=web, environment=production |
example.com/git-commit=a1b2c3d4 |
If Kubernetes or kubectl get -l must match the value, use a label. If the value is informational and should not drive routing, ownership, or filtering, use an annotation.
Basic difference
Labels answer: Which group does this object belong to? Services, Deployments, ReplicaSets, and kubectl get -l read labels to find related Pods and controllers. Labels used by controller or Service selectors should remain stable; other labels, such as app.kubernetes.io/version, can change between releases.
Annotations answer: What extra context describes this object? Runbook URLs, owner contacts, tool-specific JSON, and release metadata that should not drive selection live here. Git commits and build IDs can also appear as labels — for example app.kubernetes.io/version — when users or tools need to filter by them. Selectors ignore annotations. Some controllers read specific annotation keys they document (for example Ingress controllers), but that is not the general label-selector mechanism.
Both use optional DNS-style key prefixes (example.com/owner). Neither replaces object names, and neither should store secrets.
Labels and annotations compared
| Aspect | Labels | Annotations |
|---|---|---|
| Map location | metadata.labels |
metadata.annotations |
| Selector participation | Matched by kubectl -l, Service spec.selector, controller spec.selector, NetworkPolicy label selectors |
Not matched by standard label selectors |
| Value length | Name and value portions follow label character and length rules; values at most 63 characters or empty | Arbitrary strings; combined keys and values at most 256 KiB per object |
| Typical consumers | Scheduler (via node labels), Services, controllers, kubectl filters |
Humans, CI/CD, GitOps, optional controller hooks |
| Changes over time | Labels used by controller or Service selectors should remain stable. Other labels, such as app.kubernetes.io/version, can change between releases. Changing selector keys on Pods can break ownership |
Annotation changes do not affect standard label selectors. However, a controller may react when one of its documented annotation keys changes |
| Pod template on controllers | spec.template.metadata.labels stamps every new Pod |
spec.template.metadata.annotations stamps every new Pod when needed |
Labels and annotations are independent maps. The same object can carry both without conflict.
Side-by-side on one Deployment
The same Deployment can carry identifying labels and descriptive annotations at once. They serve different roles:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
labels:
app: web
environment: production
tier: frontend
annotations:
example.com/owner: platform-team
example.com/git-commit: a1b2c3d4
example.com/documentation: https://docs.example.com/web
spec:
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
environment: production
tier: frontend
spec:
containers:
- name: nginx
image: nginx:1.27-alpine- Labels on the Deployment and Pod template —
app,environment,tier— are what selectors andkubectl get -l environment=productioncan match. - Annotations on the Deployment — owner, Git commit, documentation URL — describe the release. They do not appear in
spec.selectorand are not queried with-l.
To propagate annotations onto every Pod, add them under spec.template.metadata.annotations in the same manifest. Pod-template placement is covered in the Kubernetes annotations guide linked in Related guides.
Wrong vs right choices
Git commit and build ID metadata can be a label or an annotation depending on whether selection matters. Kubernetes documents app.kubernetes.io/version for a revision hash when tools or users need to filter by it.
| Metadata | Use a label when | Use an annotation when |
|---|---|---|
| Git commit or build ID | You need to filter, select, or group resources by that value and it satisfies label syntax | It is only traceability metadata and should not participate in selection |
Other metadata types have a clearer split:
| Metadata | Wrong (label) | Right (annotation) | Why |
|---|---|---|---|
| Documentation URL | metadata.labels.docs: https://... |
metadata.annotations.example.com/documentation: https://... |
URLs are long and not meant for kubectl get -l |
| Application name for Service routing | metadata.annotations.app: web only |
metadata.labels.app: web on the Pod template |
Services match labels on Pods, not annotations |
| Environment for filtering | metadata.annotations.environment: production only |
metadata.labels.environment: production |
kubectl get pods -l environment=production reads labels |
Putting selectable data only in annotations is a common mistake: the object looks documented in kubectl describe, but Services and kubectl get -l behave as if that key does not exist. Changing ordinary informational labels is valid; the risk applies mainly when that label participates in workload ownership, routing, policy, or another selector.
When to use labels
Use labels when the value must identify or group objects:
- Application identity —
app,app.kubernetes.io/name - Version or revision when selectable —
app.kubernetes.io/versionfor a Git or build identifier tools must filter on - Environment or tier for filters —
environment=production,tier=frontend - Controller ownership — keys in
spec.selector.matchLabelsand matching Pod-template labels - Service endpoints — keys in Service
spec.selectorthat Pods must carry - Node selection — node labels read by
nodeSelectoror affinity (label the node first)
For syntax, kubectl label, selector grammar, and troubleshooting, continue to the Kubernetes labels and selectors guide in Related guides.
When to use annotations
Use annotations when the value describes the object and should not drive selection:
- Release traceability when selectors do not need the value — or use a label such as
app.kubernetes.io/versionwhen users or tools must filter by revision - Owner, on-call, or team contact strings
- Links to runbooks, diagrams, or external docs
- Tool-specific configuration read by one controller (check that controller’s docs)
- Structured JSON for operators or GitOps — as a single string value
For kubectl annotate, overwrite/remove syntax, and the 256 KiB limit, continue to the Kubernetes annotations guide in Related guides.
Selectors ignore annotations
kubectl get -l, Services, Deployments, and NetworkPolicies evaluate metadata.labels; they never search metadata.annotations. Annotations are ignored by label selectors, but annotations used by Ingress controllers, operators, or other extensions can alter object behavior when those tools watch documented keys.
Decision checklist
| Question | If yes → | If no → |
|---|---|---|
| Must users or tools select/filter by this value? | Label, provided it satisfies label syntax | Annotation |
| Is it only release traceability or descriptive context? | Annotation | Label if selectors need it |
Must kubectl get -l or a Service find objects by this value? |
Label | Annotation |
| Must a Deployment selector match Pods by this key? | Label on the Pod template | Annotation |
| Is the value a URL, long string, or JSON blob? | Annotation | Label (if short enough) |
| Is it only for humans or an optional tool? | Annotation | Label if selectors need it |
What's Next
- Kubernetes Api Resources
- Kubernetes CNI, CSI and CRI Interfaces Explained
- Add, Remove, Reset and Rejoin Kubernetes Nodes
References
Summary
Labels in metadata.labels identify and group objects; selectors and kubectl get -l match them. Annotations in metadata.annotations store descriptive metadata selectors ignore. Use labels for app identity, environment, tier, and anything Services or controllers must match — including a version or build ID when you need to select by it. Use annotations for traceability-only release metadata, URLs, owner contacts, and tool-specific data that should not drive selection.

