Kustomize’s patchesStrategicMerge can inject annotations into your Ingress resources, but getting them to apply only to specific environments requires a bit more finesse than a simple merge.

Let’s see this in action. Imagine you have a base Ingress definition in base/ingress.yaml:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
spec:
  rules:
  - host: my-app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-app-service
            port:
              number: 80

And you want to add environment-specific annotations for production, like a cert-manager issuer.

Here’s how you can structure your Kustomize directories:

.
├── base
│   └── ingress.yaml
├── overlays
│   ├── production
│   │   ├── kustomization.yaml
│   │   └── ingress-patch.yaml
│   └── staging
│       ├── kustomization.yaml
│       └── ingress-patch.yaml
└── kustomization.yaml

In overlays/production/ingress-patch.yaml, you’d define the annotation specific to production:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"

And overlays/production/kustomization.yaml would reference this patch:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base

patches:
- path: ingress-patch.yaml
  target:
    kind: Ingress
    name: my-app-ingress

For staging, you might have a different annotation or no annotations at all. If you want to add a different annotation for staging:

In overlays/staging/ingress-patch.yaml:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  annotations:
    nginx.ingress.kubernetes.io/force-ssl-redirect: "false" # Or some other staging-specific annotation

And overlays/staging/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base

patches:
- path: ingress-patch.yaml
  target:
    kind: Ingress
    name: my-app-ingress

To build the production configuration, you’d run:

kustomize build overlays/production

This command will output the merged Ingress resource, including the production-specific annotations. The patches directive in Kustomize is key here. It allows you to specify which existing resources (target) should have modifications applied (path). Kustomize uses a strategic merge patch by default when the apiVersion and kind match. For Ingress, which is a Kubernetes API object, this works seamlessly for adding or overwriting fields like metadata.annotations. The resources field in the overlay’s kustomization.yaml points back to the base configuration, ensuring that the Ingress resource from the base is loaded first, and then your patch is applied on top of it.

Kustomize’s patchesStrategicMerge is powerful because it understands the structure of Kubernetes objects. When you provide a patch that targets an existing resource by name and kind, it doesn’t just do a simple text replacement. For lists like annotations, it intelligently merges them. If an annotation key already exists in the base, the patched version will overwrite it. If it doesn’t exist, it will be added. This prevents accidental deletion of base annotations.

The core of this pattern is the separation of concerns. Your base directory holds the common, environment-agnostic Ingress definition. Your overlays directories then contain specific modifications for each environment. This makes your Kustomize project highly modular and maintainable. You can easily add new environments or change annotations for existing ones without altering the base configuration. The target field in the kustomization.yaml is crucial for ensuring the patch is applied to the correct resource. Without it, Kustomize wouldn’t know which Ingress object your patch is intended for, especially if you had multiple Ingress resources in your base.

What many people miss is that patchesStrategicMerge is the default behavior when you use patches and the target resource is a Kubernetes object. You don’t need to explicitly state patchesStrategicMerge:. Kustomize infers this and applies the strategic merge logic, which is essential for complex objects like Ingress where fields can be lists or maps that need careful merging rather than a simple overwrite. This is why annotations are added and not replaced entirely if the base also had annotations.

The next step is to manage secrets for these annotations, potentially using Kustomize plugins or external secret management tools.

Want structured learning?

Take the full Kustomize course →