Stop Yelling at Developers, Let Kyverno Enforce Your Kubernetes Policies

Picture this: it’s 2 AM, your phone is buzzing, and your on-call engineer is staring at a Kubernetes cluster where someone just deployed a container running as root , with no resource limits and pulling from latest. The app is hammering the node, other workloads are starving, and the post-mortem is going to be a fun read.
You could yell at the developer. You could write a passive-aggressive Confluence page titled “Kubernetes Best Practices (Please Read This Time)”. Or, and hear me out, you could just make it impossible to deploy bad configurations in the first place.
That’s exactly what Kyverno does. And it does it without making you learn a new programming language.

The Wild West of Kubernetes

Kubernetes is incredibly powerful. It’s also incredibly permissive by default.
Out of the box, nothing stops a developer from:

  • Running containers as root
  • Skipping resource requests and limits
  • Using latest as an image tag
  • Exposing a service without the right labels


Pulling images from an untrusted registry
Kubernetes trusts you. Maybe too much.
The classic answer to this problem has been OPA (Open Policy Agent) with Gatekeeper. And while OPA is powerful, it comes with a steep price: you have to write policies in Rego, a purpose-built query language that has humbled many senior engineers. It’s expressive, yes. Approachable? Not particularly.
Here’s a taste of Rego:

package kubernetes.admission

deny[msg] {
  input.request.kind.kind == "Pod"
  input.request.object.spec.containers[_].securityContext.runAsRoot == true
  msg := "Containers must not run as root"
}

It works. But if your team isn’t already fluent in Rego, you’re adding a new language to your stack just to enforce some guardrails. That’s a hard sell.
Enter Kyverno.

What is Kyverno?

Kyverno (from the Greek κυβερνώ, “to govern”) is a policy engine built specifically for Kubernetes. The key difference from OPA/Gatekeeper: policies are Kubernetes resources. No new language. No Rego. Just YAML , which your team is already writing.
Kyverno was donated to the CNCF in 2020 and is now a CNCF incubating project, meaning it’s production-grade and here to stay.
It works as an admission controller, a webhook that intercepts requests to the Kubernetes API before they’re persisted. Every time someone runs kubectl apply, Kyverno gets a look at the request and decides what to do with it.
Kyverno can do three things:

ModeWhat it does
ValidateBlock resources that don’t meet your rules
MutateAutomatically modify resources to make them compliant
GenerateCreate additional resources automatically when a new resource appears

Let’s look at each one.

Installing Kyverno

First things first. You can install Kyverno with Helm:

helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
helm install kyverno kyverno/kyverno -n kyverno --create-namespace

That’s it. Kyverno is now watching your cluster.

Validate: Stop the Bad Stuff

Validation policies block requests that don’t meet your rules. If a resource violates a policy, the API server rejects it and returns an error to the userk, instantly, before anything gets deployed.

Example 1: No Latest Tags
Using latest as an image tag is an anti-pattern. It makes deployments non-deterministic, you never know exactly what’s running. Let’s enforce that:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-latest-tag
spec:
  validationFailureAction: Enforce
  rules:
    - name: require-image-tag
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "Using 'latest' as an image tag is not allowed. Pin your image to a specific version."
        pattern:
          spec:
            containers:
              - image: "!*:latest"

Now if a developer tries to deploy with image: nginx:latest, they get a clear error message back. No more mystery failures from a silently updated image.

Example 2: Require Resource Limits
Resource limits prevent a single pod from consuming an entire node. Without them, one misbehaving app becomes everyone’s problem:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-resource-limits
spec:
  validationFailureAction: Enforce
  rules:
    - name: check-container-resources
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "CPU and memory limits are required for all containers."
        pattern:
          spec:
            containers:
              - resources:
                  limits:
                    memory: "?*"
                    cpu: "?*"

Note validationFailureAction: Enforce, this is what makes the policy actually block. If you set it to Audit instead, violations are logged but not blocked. Audit mode is great for rolling out a new policy without immediately breaking things.

Mutate: Fix It Automatically

Sometimes you don’t want to block, you want to silently correct. Mutation policies automatically patch resources before they’re stored, so developers get compliant deployments without lifting a finger.

Example 3: Add a Default Label
Your team forgot to add the team label again. Instead of blocking the deploy and sending them back to fix it, just add it:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-default-labels
spec:
  rules:
    - name: add-team-label
      match:
        any:
          - resources:
              kinds:
                - Deployment
      mutate:
        patchStrategicMerge:
          metadata:
            labels:
              +(team): "platform"

The +(team) syntax means “add this label only if it doesn’t already exist”. If the developer set it themselves, Kyverno leaves it alone.

Example 4: Set a Default Security Context
Want all containers to run as non-root unless explicitly overridden? Mutate it in:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-default-securitycontext
spec:
  rules:
    - name: set-run-as-non-root
      match:
        any:
          - resources:
              kinds:
                - Pod
      mutate:
        patchStrategicMerge:
          spec:
            containers:
              - (name): "*"
                securityContext:
                  +(runAsNonRoot): true

Security posture improved, zero developer friction.

Generate: Create Resources on Demand

Generate policies create new resources automatically when a triggering resource appears. This is perfect for enforcing conventions at namespace creation time.

Example 5: Auto-create a NetworkPolicy for Every New Namespace
Every new namespace should have a default NetworkPolicy that denies all ingress traffic unless explicitly allowed. Doing this manually is error-prone. Let Kyverno handle it:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: default-network-policy
spec:
  rules:
    - name: generate-network-policy
      match:
        any:
          - resources:
              kinds:
                - Namespace
      generate:
        apiVersion: networking.k8s.io/v1
        kind: NetworkPolicy
        name: default-deny-ingress
        namespace: "{{request.object.metadata.name}}"
        data:
          spec:
            podSelector: {}
            policyTypes:
              - Ingress

Every time someone creates a namespace, they automatically get a deny-all NetworkPolicy. Security defaults baked in from day one.

Wrapping Up

Kyverno doesn’t replace good engineering judgment, but it does make good judgment the default. Instead of writing runbooks that nobody reads and sending passive-aggressive Slack messages about image tags, you bake your standards directly into the cluster.
The developer experience is better too. Clear, immediate error messages at deploy time are far more useful than a 2 AM incident where someone has to reverse-engineer what went wrong.

  • Validate to block bad configurations
  • Mutate to silently enforce defaults
  • Generate to provision resources automatically
  • Audit mode to roll out safely

And since policies are just Kubernetes resources, your GitOps pipeline manages them like everything else. One less thing to context-switch on.
Stop yelling at developers. Kyverno will do it for you, politely, consistently, and at 1 AM instead of 2 AM.

Want to go deeper? The Kyverno documentation is genuinely good, and the Kyverno policies library has ready-to-use policies for common scenarios.