Cloud Native
Kyverno Explained: Why Modern Platforms Need Policy-as-Code
A practitioner's guide to what Kyverno actually does, what it takes off your platform team's plate, and how its CEL-native CRDs and admission architecture deliver it.
Ivan Porta
Founder & Principal Engineer

Kyverno is a CNCF-graduated policy engine that validates, mutates, generates, and cleans up Kubernetes resources with policies written as Kubernetes YAML and CEL, with no new policy language required. Use it to enforce security and operational standards at admission time and in background scans; for a handful of simple validations, Kubernetes’ built-in ValidatingAdmissionPolicy may be enough.
Running pods as root. Deploying images with the latest tag everywhere, unpinned, unsigned, and not traced to a digest. Relying on a Bash script to bootstrap new namespaces with default NetworkPolicies, ResourceQuotas, and pull secrets, all but certain to fail on the next update. Spinning up costly EKS LoadBalancers for demos that linger, forgotten, draining budgets.
Platform teams used to handle this operational overhead manually, and each new team or cluster just added more technical debt. Kyverno helps by bringing all of this under one system.
Kyverno is an open-source, Kubernetes-native policy engine. It lets you write policies in YAML, and now also in CEL, which are checked when resources are created and regularly audited against running workloads. Companies like Coinbase, LinkedIn, and Spotify, as well as the U.S. Department of Defense, use Kyverno in production. Kyverno reached CNCF Graduation at KubeCon Amsterdam in March 2026.
What is Kyverno, actually?
Kyverno is an open-source policy engine for Kubernetes that lets you write policies as Kubernetes resources. Unlike OPA or Gatekeeper, which require you to learn Rego before writing rules, Kyverno uses YAML and supports CEL, so you can work with the same API objects you already know.
Developers or SREs can write a ValidatingPolicy just like they write a Deployment, apply it with kubectl, and see violations as standard Kubernetes events and PolicyReport resources. There’s no need to learn a new language, query model, or way of thinking.
How it works
Kyverno is built on Kubernetes’ dynamic admission controller architecture, with a background scanner layered on top. Policies are evaluated at three points in a resource’s lifecycle:
-
Admission: When a developer, GitOps tool, or CI/CD pipeline submits a resource, the API server triggers admission webhooks. Kyverno’s webhook checks the request, applies matching policies, and returns allow, deny, or a mutated resource in the request path.
-
Reconciliation: Background controllers watch for resources that should exist (cleanup policies, generation rules) and reconcile them in a loop, separate from admission.
-
The new reality: Kyverno’s reporting system regularly scans existing resources against current policies on a schedule, surfacing violations in workloads that were created before a policy was in place or that changed later.
Your application code stays the same. Every resource that goes through the API server is checked, mutated, and recorded.
Policy types
Kyverno’s CEL-native policy types were introduced incrementally, beginning with v1.14 and expanding through v1.17 with the addition of Namespaced versions. This brought the policies.kyverno.io group to a total of eleven CRDs (five cluster-scoped, five namespaced, plus PolicyException). Each type instructs the API server on how to handle the policy outcome:
ValidatingPolicy: Validates resources against a CEL expression.
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: require-team-label
spec:
validationActions: [Deny]
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
- expression: "has(object.metadata.labels) && 'team' in object.metadata.labels"
message: "Pods must carry a 'team' label."MutatingPolicy: Mutates resources during admission using CEL-basedapplyConfigurationorjsonPatchexpressions, similar to KubernetesMutatingAdmissionPolicy.
apiVersion: policies.kyverno.io/v1
kind: MutatingPolicy
metadata:
name: default-run-as-non-root
spec:
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
mutations:
- patchType: ApplyConfiguration
applyConfiguration:
expression: >
Object{
spec: Object.spec{
securityContext: Object.spec.securityContext{
runAsNonRoot: true
}
}
}GeneratingPolicy: Creates downstream resources when a trigger resource is created, namespace defaults, network policies, or RBAC bindings.
apiVersion: policies.kyverno.io/v1
kind: GeneratingPolicy
metadata:
name: default-deny-on-namespace
spec:
evaluation:
synchronize:
enabled: true
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["namespaces"]
variables:
- name: targetNs
expression: "object.metadata.name"
- name: downstream
expression: >-
[
{
"kind": dyn("NetworkPolicy"),
"apiVersion": dyn("networking.k8s.io/v1"),
"metadata": dyn({
"name": "default-deny"
}),
"spec": dyn({
"podSelector": dyn({}),
"policyTypes": dyn(["Ingress", "Egress"])
})
}
]
generate:
- expression: generator.Apply(variables.targetNs, variables.downstream)ImageValidatingPolicy: Checks container images against trusted attestors like Cosign, Notary, or sigstore TUF, and supports in-toto attestations. SBOMs are also supported as one type of attestation payload.
apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
name: require-signed-images
spec:
validationActions: [Deny]
webhookConfiguration:
timeoutSeconds: 30
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
matchImageReferences:
- glob: "ghcr.io/kyverno/test-verify-image*"
attestors:
- name: kyvernoCosign
cosign:
key:
data: |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM
5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==
-----END PUBLIC KEY-----
validations:
- expression: >-
images.containers.map(image, verifyImageSignatures(image, [attestors.kyvernoCosign])).all(e, e > 0)
message: "Images from ghcr.io/kyverno/test-verify-image must be signed by the Kyverno project key."DeletingPolicy: Removes resources that match a selector on a set schedule. This is helpful for temporary environments and cleanup tasks.
apiVersion: policies.kyverno.io/v1
kind: DeletingPolicy
metadata:
name: cleanup-stale-ephemeral-pods
spec:
schedule: "0 2 * * *"
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["*"]
resources: ["pods"]
scope: "Namespaced"
namespaceSelector:
matchLabels:
environment: test
conditions:
- name: olderThan48h
expression: "time.now() - timestamp(object.metadata.creationTimestamp) > duration('48h')"
- name: ephemeralOrFinished
expression: >-
(has(object.metadata.labels) && 'ephemeral' in object.metadata.labels && object.metadata.labels.ephemeral == 'true') || (has(object.status) && has(object.status.phase) && (object.status.phase == 'Succeeded' || object.status.phase == 'Failed'))The Namespaced versions (NamespacedValidatingPolicy, NamespacedMutatingPolicy, NamespacedGeneratingPolicy, NamespacedImageValidatingPolicy, NamespacedDeletingPolicy) are important for multi-tenancy. Teams can create and manage policies in their own namespaces without needing cluster-admin rights, while platform owners use the cluster-scoped types for policies that must apply everywhere.
These CEL-based policies are about as mature as the ValidatingAdmissionPolicy in Kubernetes, with the API server handling more of the work. Kyverno adds features like a policy engine, reporting, exceptions, generation, and image validation, which VAP does not include.
The older ClusterPolicy and CleanupPolicy types still work for now, but they are being phased out. Kyverno 1.17 marked them as deprecated, and they will be removed later in 2026. Plan to migrate to the CEL-native CRDs soon, and write new policies using these types unless you have a specific reason not to.
When should you use ValidatingAdmissionPolicy instead?
Kubernetes 1.30 and later includes ValidatingAdmissionPolicy as a built-in CEL validator that doesn’t need a separate controller. For some teams, this is enough, but for most, it isn’t.
| Capability / Feature | Built-in ValidatingAdmissionPolicy | Kyverno ValidatingPolicy (CEL) |
|---|---|---|
| Primary function | Synchronous validation at admission | Validation, mutation, generation, deletion |
| Enforcement points | Admission only | Admission, background scans, pipelines, CLI |
| Payloads | Kubernetes objects | Kubernetes objects, plus any JSON/YAML |
| CEL library | Basic | Extended (HTTP calls, image verification, Kubernetes lookups) |
| External data | ✕ | Kubernetes resources or HTTP calls |
| Policy bindings | Manual | Automatic |
| Background scans | ✕ | Periodic, plus on policy change |
| Reporting & audit | ✕ | PolicyReport + EphemeralReport CRDs |
| Exceptions | ✕ | Fine-grained PolicyException |
| Image signature verification | ✕ | ✓ |
| Auto-generation | ✕ | Pod controllers, ValidatingAdmissionPolicy |
| Testing | ✕ | Kyverno CLI (unit), Chainsaw (e2e) |
| Architecture | Built into the API server (no controller) | Requires Kyverno controller |
If you only need to validate resources at admission time, VAP is a lighter option and is the right choice. If you need more features, you’ll want a policy engine, and Kyverno offers the closest match between writing policies in YAML and treating them as code.
Operational reality
-
Rollout, not install. Starting Kyverno in audit mode is recommended. Use
validationActions: [Audit]for CEL-native types orfailureAction: Auditfor legacyClusterPolicy(legacy Enforce corresponds to Deny). Avoid deploying deny policies immediately, as this can disrupt controllers if their ServiceAccounts do not meet label requirements. Run in audit mode for at least a week, reviewPolicyReportresources, address violations, and then switch to deny. -
Mutation is not a security control.
MutatingPolicyworks well for setting defaults, image pull secrets, resource requests, and standard labels, but it’s not a replacement for validation. A user who can submit a pod spec might submit one that your mutation never processes, due to admission order or exemptions. Anything that must always be true should use aValidatingPolicy.
A practical recommendation
If you are evaluating a policy engine for a Kubernetes platform and you do not already have a strong reason to pick OPA/Gatekeeper, pilot Kyverno first. The install fits in an afternoon. If it does what you need, you just saved a quarter of operational work. If it doesn't, you have a clear, documented reason to go somewhere else.
Frequently asked
Three questions we keep getting about Kyverno.
What is Kyverno, and where does it sit in Kubernetes admission flow?
Kyverno is a Kubernetes-native policy engine that runs as an admission controller, intercepting every API request via webhooks to validate, mutate, or generate resources before they are persisted.
Should I start with legacy ClusterPolicy or the newer CEL-based policy types?
Use the CEL-based types (ValidatingPolicy, MutatingPolicy, GeneratingPolicy, ImageValidatingPolicy) for new work. ClusterPolicy still works but is deprecated as of Kyverno 1.17.
How do Audit and Enforce differ, and what do background scans do?
Deny (called Enforce in legacy ClusterPolicy) rejects failing requests at admission; Audit admits them but records the failure. Background scans re-evaluate existing resources on a schedule and write results to EphemeralReport intermediaries, which Kyverno's reports controller aggregates into the PolicyReport resources operators consume.
If you'd rather not run the rollout yourself, Todea installs and operates Kyverno end to end as part of the platforms we manage.