kubectl annotate: Set and Remove Annotations
kubectl annotate adds, updates, or removes annotations, the non-identifying metadata controllers and tools read.
Annotations carry config for ingress controllers, GitOps tools, and your own automation. annotate edits them without a full apply.
What it does
kubectl annotate sets key=value annotations on resources. As with labels, changing an existing annotation needs --overwrite, and a trailing - on a key removes it. Annotation values can be long (no 63-char limit like labels) and are not used by selectors.
Common usage
kubectl annotate ingress web \
nginx.ingress.kubernetes.io/proxy-body-size=50m
# overwrite an existing annotation
kubectl annotate deploy api kubernetes.io/change-cause="deploy v2" --overwrite
# remove one
kubectl annotate deploy api kubernetes.io/change-cause-Options
| Flag / Syntax | What it does |
|---|---|
| <key>=<value> | Set an annotation |
| <key>- | Remove the annotation with that key |
| --overwrite | Allow changing an existing annotation |
| -l, --selector | Apply to all resources matching a selector |
| --all | Apply to all resources of the type |
| --dry-run=client | Preview without writing |
In CI
Use the kubernetes.io/change-cause annotation with --overwrite to record a human-readable cause that shows up in kubectl rollout history. Keep keys properly prefixed (for example example.com/owner); an unprefixed key in a reserved namespace can be rejected.
Common errors in CI
"error: \'kubernetes.io/change-cause\' already has a value (...), and --overwrite is false" means add --overwrite. "error: at least one annotation update is required" means no key=value or key- was given. A value with shell-special characters can be truncated or split; quote it.
Using this in CI
A runner has no kubeconfig, no cached context, and no interactive auth. Every kubectl invocation in CI needs the context supplied explicitly, and most confusing CI failures here are the command running against the wrong cluster or no cluster at all.
# never rely on the ambient context on a runner
kubectl --context "$KUBE_CONTEXT" -n "$NAMESPACE" get pods
# confirm what you are actually connected to before mutating anything
kubectl config current-context
kubectl cluster-info
# fail fast instead of hanging on an unreachable API server
kubectl --request-timeout=30s get nodes