Skip to main content
Kyverno vs OPA Gatekeeper in 2026: CEL, Rego, or Native Admission Policy

Kyverno vs OPA Gatekeeper in 2026: CEL, Rego, or Native Admission Policy

KU
• 7 min read•0 views

Kyverno 1.19 deprecated ClusterPolicy and went all in on CEL, Gatekeeper still speaks Rego, and Kubernetes now ships validating and mutating policies natively. How to pick, with the same policy written three ways.

Key takeaways

  • Pick Kyverno if you want one Kubernetes-native tool for validation, mutation, resource generation and image signature checks, written in CEL.
  • Pick Gatekeeper if your organization already writes Rego or shares policy with OPA elsewhere.
  • For simple validation rules on Kubernetes 1.30 or later, native ValidatingAdmissionPolicy needs no webhook at all.

Choose Kyverno when you want validation, mutation, resource generation and image signature verification in one Kubernetes-native tool. Choose Gatekeeper when your team already writes Rego or shares OPA policy beyond Kubernetes. And check first whether you need either: as of Kubernetes 1.36, both ValidatingAdmissionPolicy and MutatingAdmissionPolicy are stable, in-process, and webhook-free.

Verified against

Kyverno 1.19.2, Gatekeeper 3.23.1 (bundling OPA 1.17.1), Kubernetes ValidatingAdmissionPolicy (stable since 1.30) and MutatingAdmissionPolicy (stable since 1.36), against kyverno.io, the Gatekeeper docs and the kubernetes.io admission policy reference. Checked October 2026.

What changed in 2026

The comparison most teams remember is "Kyverno is YAML, Gatekeeper is Rego". That is out of date. Kyverno's policy types page now lists ClusterPolicy and CleanupPolicy as deprecated in 1.19 and removed in 1.20. Their replacements, ValidatingPolicy, MutatingPolicy, GeneratingPolicy, DeletingPolicy and ImageValidatingPolicy, are all stable since 1.18 and use CEL, the same expression language as the Kubernetes built-ins. A ValidatingPolicy is described as a superset of a ValidatingAdmissionPolicy.

Gatekeeper moved toward CEL from the other side. Its K8sNativeValidation engine lets a ConstraintTemplate carry CEL next to (or instead of) Rego, stable since Gatekeeper 3.18, and it can generate native ValidatingAdmissionPolicy objects so the API server enforces simple rules itself.

So the real question in 2026 is less "YAML or Rego" and more "how much do I need beyond what the API server already does?"

The comparison table

Kyverno 1.19Gatekeeper 3.23Native VAP / MAP
Policy languageCEL (legacy JMESPath types removed in 1.20)Rego, plus CEL via K8sNativeValidationCEL
MutationYes, MutatingPolicyYes, Assign, AssignMetadata, ModifySet, AssignImageYes, MAP (stable since 1.36)
Generate resourcesYes, GeneratingPolicyNoNo
Image signature checksBuilt in, ImageValidatingPolicy (cosign, Notary)Through external data providers (beta)No
Audit of existing resourcesBackground scans and policy reportsAudit controllerNo, admission only
Runs whereWebhook, plus CLI and JSON payloadsWebhook, audit, gator CLIInside the API server

The same policy three ways: disallow :latest

Kyverno, as a CEL ValidatingPolicy. The autogen block makes Kyverno write the matching rules for Deployments, StatefulSets, Jobs and CronJobs, so users get the error at kubectl apply time rather than when a ReplicaSet fails to create pods:

YAML
# kyverno/disallow-latest.yaml
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
  name: disallow-latest-tag
spec:
  validationActions: [Deny]
  autogen:
    podControllers:
      controllers: [deployments, statefulsets, jobs, cronjobs]
  matchConstraints:
    resourceRules:
      - apiGroups: [""]
        apiVersions: [v1]
        operations: [CREATE, UPDATE]
        resources: [pods]
  validations:
    - expression: "object.spec.containers.all(c, !c.image.endsWith(':latest'))"
      message: "Pin an image tag or digest; :latest is not allowed."

Gatekeeper, using the k8sdisallowedtags template from the official gatekeeper-library. You install the template once, then each rule is a small Constraint:

YAML
# gatekeeper/no-latest.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sDisallowedTags
metadata:
  name: container-image-must-not-have-latest-tag
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
  parameters:
    tags: ["latest"]

The Rego inside that template is the part your team would own if you wrote your own:

Rego
violation[{"msg": msg}] {
  container := input_containers[_]
  not is_exempt(container)
  tags := [tag_with_prefix | tag := input.parameters.tags[_]; tag_with_prefix := concat(":", ["", tag])]
  strings.any_suffix_match(container.image, tags)
  msg := sprintf("container <%v> uses a disallowed tag <%v>; disallowed tags are %v", [container.name, container.image, input.parameters.tags])
}

Native, with no engine installed. A policy plus a binding, enforced by the API server:

YAML
# native/disallow-latest.yaml
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: disallow-latest-tag
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups: [""]
        apiVersions: [v1]
        operations: [CREATE, UPDATE]
        resources: [pods]
  validations:
    - expression: "object.spec.containers.all(c, !c.image.endsWith(':latest'))"
      message: "Pin an image tag or digest; :latest is not allowed."
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: disallow-latest-tag
spec:
  policyName: disallow-latest-tag
  validationActions: [Deny]

Notice the CEL expression is identical in the Kyverno and native versions. That is the strongest argument for CEL in 2026: a rule you write today can move between Kyverno and the API server without a rewrite. None of the three CEL rules above catches an untagged image such as nginx, which defaults to latest; the Gatekeeper library template does, with a second violation rule.

The failure mode that causes the outages

Every webhook engine has to answer one question: what happens to API requests when the webhook is down?

Gatekeeper's docs answer it directly: it defaults to failurePolicy: Ignore, so when the webhook is unreachable, constraints are not enforced, and audit is expected to catch what slipped through. That is fail-open by design. You can switch to Fail, and the docs walk through what to consider first.

Kyverno's platform notes document the opposite failure. On EKS with the VPC CNI, if every node is gone (a single node group scaled to zero), a Fail-mode policy matching Pods can block the CNI's own DaemonSet pods, so nodes never become ready, so Kyverno never comes back to answer the webhook. Kyverno has excluded kube-system from its webhooks by default since 1.12 for exactly this reason. Don't remove that exclusion, and give the admission controller several replicas.

Native policies sidestep this class of outage because there is no webhook to be unreachable. A ValidatingAdmissionPolicy defaults to failurePolicy: Fail, but that governs CEL errors and misconfiguration, not network calls.

Where this breaks

CEL is deliberately limited. Rules that need to look up other cluster objects, call external data or loop over large lists are where Rego and Kyverno's extra CEL libraries earn their keep, and where native VAP runs out. Kyverno's migration also has a deadline: if your repo is full of kyverno.io/v1 ClusterPolicy files, those stop working in 1.20, so budget the conversion now. And Gatekeeper's image verification depends on a separate external data provider you operate yourself.

Questions people ask

Is Kyverno better than OPA Gatekeeper?

For Kubernetes-only teams, usually yes, because one tool covers validation, mutation, generation, cleanup and image verification, all in CEL. Gatekeeper is the better fit when you already run OPA for APIs, Terraform or CI and want one Rego codebase across all of them. Both audit existing resources as well as blocking new ones, so the decision is mostly about language and scope.

Is Kyverno ClusterPolicy deprecated?

Yes. As of October 2026, Kyverno's docs list ClusterPolicy (kyverno.io/v1) and CleanupPolicy as deprecated in 1.19 and removed in 1.20. The CEL-based replacements, such as ValidatingPolicy, MutatingPolicy and ImageValidatingPolicy, are stable since 1.18 and, per the docs, at full feature parity in 1.19. Kyverno publishes a migration guide for converting existing policies.

Do I still need Kyverno or Gatekeeper with ValidatingAdmissionPolicy?

Not for simple field checks: required labels, banned tags, replica caps. ValidatingAdmissionPolicy is stable since Kubernetes 1.30 and runs inside the API server. You still want an engine for mutation beyond MAP, generating resources, image signature verification, background audit reports of existing objects, and testing policy in CI before it reaches the cluster.

The call we'd make

New cluster, Kubernetes-only team: Kyverno 1.19 with CEL policy types, and let it generate native VAPs for the simple rules. Existing Rego estate: stay on Gatekeeper and write new templates with K8sNativeValidation CEL. Either way, start with the controls in our Kubernetes security guide, then add signature checks once you sign images with cosign. The caveat: whichever engine you choose, decide its fail mode on purpose and write it down.

Sources

React
Share this post
Cite this article
Plain text

Kiril Urbonas (2026). Kyverno vs OPA Gatekeeper in 2026: CEL, Rego, or Native Admission Policy. DevOpsNess. https://www.devopsness.com/blog/kyverno-vs-opa-gatekeeper

Markdown

[Kyverno vs OPA Gatekeeper in 2026: CEL, Rego, or Native Admission Policy](https://www.devopsness.com/blog/kyverno-vs-opa-gatekeeper) (Kiril Urbonas, DevOpsNess, 2026-10-31)

KU

Kiril Urbonas

AI Engineer

585 articles
View all articles by Kiril Urbonas

You might have missed

Evergreen posts worth revisiting.