🚀 Executive Summary
TL;DR: The `kubernetes.io/ingress.class` annotation is being deprecated by Kubernetes, with ingress-nginx retiring its support by March 2026, necessitating migration to the `spec.ingressClassName` field. This article provides a practical playbook with options ranging from manual updates to GitOps with policy enforcement or controller-level overrides to ensure a smooth transition and avoid production outages.
🎯 Key Takeaways
- The `kubernetes.io/ingress.class` annotation is deprecated in favor of the structured `IngressClass` resource and `spec.ingressClassName` field for improved stability and standardization.
- Migration strategies include manual search and replace for small teams, GitOps integration with policy enforcement (e.g., Kyverno) for larger platforms, or controller-level overrides (`–ingress-class` argument) for federated organizations.
- Policy-as-code tools like Kyverno can be used to enforce the new `spec.ingressClassName` standard, preventing developers from deploying new Ingress resources with the deprecated annotation.
Kubernetes is deprecating the kubernetes.io/ingress.class annotation, and ingress-nginx follows suit in March 2026. Here’s a senior engineer’s practical playbook for migrating to the ingressClassName field without breaking production.
The ingress.class Annotation is Dying. Here’s Your Migration Playbook.
It was 2 AM. The pager buzzed, a high-priority alert screaming that our `checkout-api-prod` was down. A frantic kubectl get ingress showed everything looked fine. The YAML was there, the host was right. It took me and a junior engineer 30 minutes of panicked digging to realize the EKS cluster upgrade we’d just rolled out had finally, officially, stopped honoring the old kubernetes.io/ingress.class annotation. Our NGINX Ingress Controller was just… ignoring it, sitting there silently as requests to our most critical service timed out. A recent Reddit thread, “ingress-nginx retiring March 2026 – what’s your migration plan?”, reminded me of that night, and I realized a lot of you are probably staring down this same barrel. So let’s talk about it.
First, Why Is This Even Happening?
Before we dive into fixes, let’s understand the “why”. This isn’t just ingress-nginx being difficult. For years, the kubernetes.io/ingress.class annotation was the de-facto standard, but it was always a bit of a hack—a placeholder until a better, more official solution came along. That solution is now here and has been for a while: the IngressClass resource and the spec.ingressClassName field on the Ingress object itself.
This moves the configuration from a free-form annotation (prone to typos) to a structured, first-class field in the Kubernetes API. It’s a good thing. It’s a move toward stability and standardization. The problem is, technical debt waits for no one, and that old annotation is lurking in hundreds of our manifests.
Your Migration Options: From Band-Aid to Surgery
Alright, enough theory. You’re here because you have dozens, maybe hundreds, of services to migrate. Depending on the size of your operation and how your teams are structured, you have a few ways to tackle this.
Option 1: The Quick and Dirty (Manual Search & Replace)
This is for the small teams, the startups, or anyone with a manageable number of Ingress objects. It’s straightforward, effective, and requires no fancy tooling. You’re basically doing a glorified find-and-replace across your codebase or live objects.
First, find everything that’s still using the old annotation. You can run a quick command to smoke them out:
kubectl get ingress --all-namespaces -o jsonpath='{range .items[?(@.metadata.annotations."kubernetes\.io/ingress\.class")]}{.metadata.namespace}{"\t"}{.metadata.name}{"\n"}{end}'
Once you have your list, you’ll need to update each manifest. You’re changing this:
# --- BEFORE ---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-legacy-app-ingress
annotations:
kubernetes.ioio/ingress.class: "nginx"
# ... other annotations
spec:
rules:
# ... rules
To this:
# --- AFTER ---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-legacy-app-ingress
annotations:
# kubernetes.io/ingress.class IS GONE!
# ... other annotations
spec:
ingressClassName: "nginx" # <-- THIS IS THE NEW FIELD
rules:
# ... rules
Warning: Be careful! You remove the annotation from
metadata.annotationsand add the new field underspec. It’s a simple but easy-to-miss detail when you’re tired and staring at YAML for hours.
Option 2: The “Right” Way (GitOps & Policy Enforcement)
For those of us running larger platforms with GitOps, a manual approach is a recipe for disaster. The “right” way is to fix this at the source and prevent it from happening again.
- Update Your Source of Truth: Go into your central repositories. This means updating your Helm chart templates, your Kustomize base manifests, or your Terraform modules. Expose
ingressClassNameas a value and remove the old annotation logic. - Enforce the New Standard: This is the key step. You need to prevent developers from merging or deploying new manifests with the old annotation. This is a perfect use case for a policy-as-code engine like OPA/Gatekeeper or Kyverno.
Here’s a dead-simple Kyverno ClusterPolicy that would block any new Ingress resources using the old annotation:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-old-ingress-class
spec:
validationFailureAction: Enforce
rules:
- name: validate-ingress-class-annotation
match:
any:
- resources:
kinds:
- Ingress
validate:
message: "The 'kubernetes.io/ingress.class' annotation is deprecated. Please use the 'spec.ingressClassName' field instead."
pattern:
metadata:
annotations:
"kubernetes.io/ingress.class": "!*" # Block if this key exists
This approach turns a manual cleanup task into a permanent, automated guardrail. It’s more work up front, but it’s how you build a reliable, scalable platform.
Option 3: The “I Can’t Touch That” Override (Controller-Level Fix)
Okay, let’s get real. Sometimes you’re the platform engineer for a huge organization. You have 50 different application teams, and you can’t force them all to update their thousands of manifests. You need a way to keep the lights on while you slowly herd the cats.
In this scenario, you can configure the ingress-nginx controller itself to watch for a specific class name, regardless of what’s in the annotation. You do this by modifying the arguments in your ingress-nginx controller’s Deployment manifest.
# In your ingress-nginx-controller Deployment YAML...
...
- args:
- /nginx-ingress-controller
# Tell the controller its name. It will now only process
# Ingresses with spec.ingressClassName = "nginx-internal"
- --ingress-class=nginx-internal
# Optional: if you have a default class and want this controller
# to pick up Ingresses with NO class defined. Use with caution.
- --watch-ingress-without-class=true
- --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
...
This is a powerful, “hacky-but-effective” tool. It lets you control routing behavior at the infrastructure level, giving you breathing room to push teams to adopt the new ingressClassName field over time. You are effectively saying, “I don’t care what your old manifest says; if you want to be served by *this* controller, you must use `ingressClassName: nginx-internal`.”
Choosing Your Path
So, which one is for you? Here’s a quick cheat sheet.
| Option | Effort | Scalability | Best For… |
|---|---|---|---|
| 1. Manual Fix | Low | Low | Small teams, < 50 services, quick cleanup jobs. |
| 2. GitOps & Policy | Medium | High | Mature platform teams, GitOps workflows, preventing future debt. |
| 3. Controller Override | Low | High | Large, federated organizations where you can’t control all manifests. |
Final Thoughts
This isn’t a five-alarm fire… yet. You have until March 2026 before the old annotation is fully removed from ingress-nginx. But this is exactly the kind of technical debt that bites you during a late-night cluster upgrade. My advice? Start the conversation now. Run that `kubectl` command to see how big your problem is. Pick the path that fits your organization’s culture and workflow.
Fix it now, on your own terms, and save your future self from a 2 AM pager storm. You’ll thank me later.
🤖 Frequently Asked Questions
âť“ Why is the `kubernetes.io/ingress.class` annotation being deprecated?
The `kubernetes.io/ingress.class` annotation is being deprecated because it was a temporary solution. Kubernetes now provides a more structured and official way to define Ingress classes using the `IngressClass` resource and the `spec.ingressClassName` field, enhancing stability and standardization.
âť“ What are the main migration strategies for the deprecated `ingress.class` annotation?
The main strategies are: 1) Manual search and replace for small setups; 2) GitOps integration with policy enforcement (e.g., Kyverno) for scalable, preventative measures; and 3) Ingress-nginx controller-level overrides (`–ingress-class` argument) for large organizations needing a transitional fix.
âť“ What is a common implementation pitfall when migrating Ingress objects?
A common pitfall is incorrectly removing the `kubernetes.io/ingress.class` annotation from `metadata.annotations` without adding the `ingressClassName` field under `spec`, or vice-versa, leading to Ingress objects being ignored by the controller.
Leave a Reply