Kyverno vs Gatekeeper vs ValidatingAdmissionPolicy in 2026

Oct 20, 2026 · 13 min · Ajay Kumar

Every Kubernetes cluster I have inherited had a policy engine, and about half of them had two, installed by different teams in different years, both blocking the same privileged pods with slightly different error messages. In 2026 the choice got simpler and more confusing at the same time. Simpler, because the Kubernetes API server now evaluates CEL policies natively, so a lot of what used to need Kyverno or OPA Gatekeeper is a built-in object. More confusing, because Kyverno rewrote its own API around the same CEL, Gatekeeper learned to compile its templates into native policies, and the old advice ("Kyverno if you like YAML, Gatekeeper if you like Rego") no longer describes either project. This post compares the Kubernetes policy engines as they stand in October 2026: what each one is, the versions and deprecations that matter, what the published latency numbers say, and how I would choose.

What a Kubernetes policy engine actually does

A policy engine sits in the admission path. When someone runs kubectl apply, the API server authenticates and authorises the request, then hands the object to admission controllers that can change it (mutating admission) and then accept or reject it (validating admission) before it is written to etcd. Policy engines plug into that chain in one of two ways: as an external webhook the API server calls over HTTPS, or, since Kubernetes 1.30, as policy objects the API server evaluates in-process.

The useful jobs break down into five verbs: validate (reject a pod that runs privileged), mutate (add a default seccomp profile), generate (create a NetworkPolicy whenever a namespace appears), verify images (check a signature before the pod runs), and clean up (delete stale resources on a schedule). Most teams start with validate and want the other four six months later, which matters because the built-in options only do the first two.

If you came here from the agent world, the same design applies there. I wrote about policy as code for AI agents earlier this month, and the admission controller is the original version of that idea: a declarative rule, evaluated at a boundary the workload cannot route around, versioned in git.

The built-in options: Pod Security Admission, VAP and MAP

Kubernetes ships three policy mechanisms of its own, and each has a narrow, well-defined job.

Pod Security Admission has been stable since 1.25. You label a namespace with pod-security.kubernetes.io/enforce: restricted (or baseline, or privileged) and the API server applies the matching Pod Security Standard. There are three modes, enforce, audit and warn, and one detail people miss: audit and warn are applied to workload resources such as Deployments, but enforce is applied only to the resulting pods. It is free, fast and has no configuration language at all, which is both its strength and its ceiling. You cannot add a rule.

ValidatingAdmissionPolicy (VAP) went GA in Kubernetes 1.30, released in April 2024. You write CEL expressions in a policy object, then attach it with a binding that chooses the validation actions (Deny, Warn, Audit) and the namespaces it covers. Bindings can take parameters from a ConfigMap or a CRD, so one policy can serve many teams with different settings.

MutatingAdmissionPolicy (MAP) is the newest. The docs mark it "Stable since Kubernetes v1.36; enabled by default", and v1.36.0 shipped on 22 April 2026. Mutations are written as CEL that returns an apply configuration (merged like server-side apply) or a JSON patch. Combined with VAP, this means a cluster on 1.36 or later can validate and mutate without any webhook at all.

What none of the three can do: generate resources, verify image signatures, scan existing resources and produce reports, or clean anything up. VAP also matches the exact resources you name. If you write a rule against pods, a Deployment that violates it is accepted, and the failure shows up later as an event on the ReplicaSet that cannot create its pods. That is correct behaviour and a reliably confusing experience for application teams.

Kyverno in 2026: graduated, and halfway through a rewrite

Kyverno had a big year. The CNCF announced its graduation on 24 March 2026 at KubeCon Europe, noting growth from 574 to more than 9,000 GitHub stars since it was contributed by Nirmata in 2020. The release also quotes LinkedIn running it across 230+ clusters; that is a press-release anecdote, not a benchmark.

The more important change is the API. Kyverno 1.17 promoted a family of CEL-based policy types to v1: ValidatingPolicy, MutatingPolicy, GeneratingPolicy, ImageValidatingPolicy, DeletingPolicy and a new PolicyException, each with a namespaced variant. It also marked the classic ClusterPolicy and CleanupPolicy as deprecated. Then Kyverno 1.19, released on 20 August 2026, declared full feature parity between the CEL types and the legacy ones, called itself the final release with full support for the legacy types, and scheduled removal of ClusterPolicy, Policy, CleanupPolicy and the old kyverno.io PolicyException for 1.20, estimated November 2026. The current patch is v1.19.1 from 10 September, with 1.19.2 release candidates out as I write.

So if your mental model of Kyverno is validate.pattern with =(...) anchors and JMESPath {{ request.object }} variables, that model has about a month left. The new ValidatingPolicy deliberately looks like a Kubernetes VAP with extras bolted on: the same matchConstraints and validations fields, the same CEL, plus Kyverno-only features like automatic rules for pod controllers, background scanning into PolicyReports, CEL libraries for HTTP calls and resource lookups, and the option to compile a policy down to a native VAP with spec.autogen.validatingAdmissionPolicy.enabled.

Two caveats. First, the official compatibility table lists Kyverno 1.19 against Kubernetes 1.33 to 1.35, so if you are on 1.36 or 1.37 you are outside the tested range until 1.20. Second, community patch support is roughly three months per release, so you will be upgrading Kyverno about as often as you upgrade the cluster.

OPA Gatekeeper in 2026: Rego, plus a CEL escape hatch

Gatekeeper is the Kubernetes admission project under Open Policy Agent, which remains a CNCF graduated project. Its current release is v3.23.1 from 27 August 2026, with 3.24 in beta. The model has not changed: a ConstraintTemplate defines a rule and the CRD for its parameters, and Constraints instantiate it against resources. The audit controller scans existing objects and writes violations to Constraint status. Mutation exists through dedicated CRDs (Assign, AssignMetadata, ModifySet, AssignImage), but there is no equivalent of Kyverno's generate.

What changed is CEL. Per the Gatekeeper VAP docs, a ConstraintTemplate can carry a K8sNativeValidation engine with CEL expressions, stable since Gatekeeper 3.18. Since 3.20, Gatekeeper can also generate a native ValidatingAdmissionPolicy and binding from those templates, and the docs now say both generation flags default to true. When a template has both CEL and Rego, CEL wins and there is no fallback, so you cannot use Rego as a safety net for a CEL rule that errors.

The pitch is clear: write a rule once, have the API server enforce it in-process, and keep Gatekeeper for audit, shift-left testing with gator, and the Rego rules that CEL cannot express. If you already have a large Rego library, that is a reasonable migration path. If you do not, learning Rego in 2026 just to write admission rules is hard to justify.

Kyverno vs Gatekeeper vs VAP: the comparison table

Engine Current state (Oct 2026) Policy language Where it runs Validate / mutate / generate / verify images Source
Pod Security Admission Stable since 1.25 None (3 fixed profiles) API server Validate only Kubernetes docs
ValidatingAdmissionPolicy GA in 1.30 (April 2024) CEL API server Validate only Kubernetes blog
MutatingAdmissionPolicy Stable in 1.36 (22 April 2026) CEL API server Mutate only Kubernetes docs
Kyverno 1.19.1; CNCF graduated March 2026; ClusterPolicy removal planned for 1.20 CEL (legacy YAML/JMESPath deprecated) Webhook plus controllers All four, plus cleanup and reports Kyverno 1.19
OPA Gatekeeper 3.23.1; CEL engine stable since 3.18; VAP generation beta since 3.20 Rego, or CEL via K8sNativeValidation Webhook plus audit; can emit native VAPs Validate, mutate (Assign CRDs); no generate Gatekeeper docs
Kubewarden v1.38.2 (21 September 2026) WebAssembly modules (Rego, Go, Rust and others) Webhook Validate, mutate Release

The convergence is the story. Three of the six rows now speak CEL with near-identical matchConstraints and validations fields. Here is the same rule, "images must come from our registry", as a native VAP and as a Kyverno ValidatingPolicy:

# Native: Kubernetes 1.30+ (admissionregistration.k8s.io/v1). No webhook involved.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: allowed-registries
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
    - apiGroups: [""]
      apiVersions: ["v1"]
      operations: ["CREATE", "UPDATE"]
      resources: ["pods"]
  validations:
  - expression: >-
      object.spec.containers.all(c, c.image.startsWith('registry.example.com/')) &&
      (!has(object.spec.initContainers) ||
       object.spec.initContainers.all(c, c.image.startsWith('registry.example.com/')))
    message: "images must come from registry.example.com"
---
# A VAP does nothing until a binding selects where it applies and how.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: allowed-registries
spec:
  policyName: allowed-registries
  validationActions: ["Deny"]          # or ["Warn", "Audit"] while you roll out
  matchResources:
    namespaceSelector:
      matchLabels:
        policy.example.com/registries: enforced
---
# Kyverno 1.17+ (policies.kyverno.io/v1). Same CEL, but Kyverno also writes
# rules for Deployments, StatefulSets, Jobs and CronJobs automatically, and
# scans existing pods into PolicyReports in the background.
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
  name: allowed-registries
spec:
  validationActions: [Deny]
  matchConstraints:
    resourceRules:
    - apiGroups: [""]
      apiVersions: [v1]
      operations: [CREATE, UPDATE]
      resources: [pods]
  validations:
  - expression: >-
      object.spec.containers.all(c, c.image.startsWith('registry.example.com/')) &&
      (!has(object.spec.initContainers) ||
       object.spec.initContainers.all(c, c.image.startsWith('registry.example.com/')))
    message: "images must come from registry.example.com"

The CEL is identical. What you pay Kyverno for is everything around it: the autogenerated controller rules, reports on resources that existed before the policy, exceptions as first-class objects, and the generate and image verification jobs VAP will never do.

How much latency does a policy engine add?

This is the question people ask before installing a webhook, and until recently the honest answer was "it depends, and nobody publishes numbers". Kyverno now does. Its scaling page and the raw benchmark summary report end-to-end pod creation latency for Kyverno 1.18.1 on a 32 vCPU machine running KinD with KWOK fake nodes, driven by k6.

Kyverno 1.18.1: p99 pod creation latency at 500 virtual users (ms) 1,000 ms alert threshold baseline run 443 simple vpol, 3 replicas 445 vpol + mpol, 3 replicas 567 16 PSS vpols, 3 replicas 649 vpol + mpol, 1 replica 1,113 16 PSS vpols, 1 replica 1,247 10,000 pod creations per run on KinD with KWOK nodes. Source: kyverno/kyverno docs/perf-testing/v1.18.1.
p99 end-to-end pod creation latency at 500 concurrent virtual users, from Kyverno's published v1.18.1 benchmark summary. The run labelled baseline is the project's reference run; the README does not spell out its configuration.

Two readings. At normal load the engine is close to noise: with 50 virtual users and 16 Pod Security Standards policies, p99 was 182 ms on one replica and 106 ms on three. At 500 concurrent creators the replica count is what matters: the same 16 policies hit 1,247 ms p99 on one replica and 649 ms on three, and the project's own KyvernoAdmissionHighLatency alert fires above one second. The lesson is boring and true. Run three admission replicas, set a PodDisruptionBudget, and watch kyverno_admission_review_duration_seconds.

Native VAP and MAP skip the network hop entirely, which is the structural reason to prefer them for simple, high-volume rules. Gatekeeper's docs do not publish a comparable benchmark that I could find, so I will not guess at a number.

When to use which in 2026

My decision rule, in order:

  1. Start with Pod Security Admission everywhere. Label every namespace at least baseline enforce and restricted warn. It costs nothing and catches the worst pods even if your engine is down.
  2. Use VAP (and MAP on 1.36+) for a handful of simple, stable rules that platform teams own: registry allowlists, required labels, banning hostPath. No webhook means no webhook outage.
  3. Add Kyverno when you need any of generate, image verification, reports, exceptions or controller autogen, which in practice is most multi-team clusters. Write new policies only in the policies.kyverno.io types; anything you write as ClusterPolicy today is migration debt due in weeks. The Kyverno and Pod Security Standards guide walks through the whole setup on 1.19.
  4. Keep Gatekeeper if you already run it with a real Rego library, move simple templates to K8sNativeValidation so they compile to VAPs, and stop there. I would not start a new Gatekeeper deployment in 2026 unless the organisation already standardises on OPA elsewhere.
  5. Never run two engines enforcing the same rule. You will get two error messages, two exception processes and two upgrade cycles, and nobody will remember which one is authoritative.

None of this matters if nobody owns it. A policy engine is a production dependency on the critical path of every deploy, and it fails the way any webhook fails: closed, at 3 AM, during the cluster upgrade. The Kubernetes security threats worth blocking at admission are real, but so is the discipline of treating policies as code with tests, staged rollout and an owner.

FAQ

Is Kyverno better than OPA Gatekeeper?

For most teams starting fresh in 2026, yes, because Kyverno covers validate, mutate, generate, image verification and cleanup with CEL policies that look like native Kubernetes objects. Gatekeeper remains a good choice if you already have a Rego library or use OPA outside Kubernetes. Both are maintained and both can now emit native ValidatingAdmissionPolicies.

Is Kyverno ClusterPolicy deprecated?

Yes. Kyverno 1.17 deprecated ClusterPolicy and CleanupPolicy, 1.19 reached feature parity with the CEL-based types and is the last release with full legacy support, and removal is planned for 1.20, estimated November 2026. Use ValidatingPolicy, MutatingPolicy, GeneratingPolicy, ImageValidatingPolicy and DeletingPolicy for anything new.

Does ValidatingAdmissionPolicy replace Kyverno?

Only for simple validation. VAP has been GA since Kubernetes 1.30 and runs inside the API server, but it cannot generate resources, verify image signatures, report on existing resources or write rules for pod controllers automatically. Many teams run VAP for a few platform rules and Kyverno for everything else.

What replaced PodSecurityPolicy?

Pod Security Admission, stable since Kubernetes 1.25, which applies the Pod Security Standards (privileged, baseline, restricted) per namespace through labels. PodSecurityPolicy was removed in the same release. For rules beyond the three fixed profiles, layer Kyverno, Gatekeeper or VAP on top.

This quarter I would do three things on any cluster I own: label every namespace with Pod Security Admission levels, inventory every ClusterPolicy and convert it to the CEL types before Kyverno 1.20 lands, and bump the Kyverno admission controller to three replicas with an alert on p99 admission latency. If the cluster is already on 1.36, I would also move the two or three simplest rules to native VAP and MAP, and enjoy one less thing that can page me.

Related: Policy as Code for AI Agents in 2026: Cedar at the Gateway, Rego in the Hook, and the 11.2% the Model Still Lets Through, Kubernetes as the Agent Control Plane in 2026: Agent Sandbox Hit v1.0, 300 Claims a Second, and DRA in Every Supported Release and Container vs microVM vs gVisor: Choosing Agent Isolation.

I'm Ajay Kumar — I build and operate PandaStack, an open-source Firecracker microVM cloud for AI agents, and write about the infrastructure and tooling around it.

Need this kind of infrastructure work? See what I do or email hello@ajayk.sh.


Related