Kyverno is a policy engine for Kubernetes. Policies are Kubernetes custom resources, so you write them in YAML and manage them with the same tooling you already use for everything else in the cluster.
That design choice is the whole pitch. The established alternative, OPA Gatekeeper, expresses policy in Rego, a purpose-built query language with its own mental model and its own debugging story. For a team already moving between YAML, Go, Helm and shell, adding Rego is a real cost. Kyverno’s bet was that most Kubernetes policy work does not need a general-purpose policy language.
It was built at Nirmata, initially inside their commercial platform, and donated to the CNCF. Jim Bugwadia, Nirmata’s co-founder, is a co-creator.
The CNCF timeline: accepted as a Sandbox project on 10 November 2020, moved to Incubating on 13 July 2022, and graduated on 16 March 2026, announced at KubeCon EU in Amsterdam. Graduation required an independent security audit, committers across multiple organisations, and demonstrated production adoption. The project’s ADOPTERS.md lists 54 organisations, LinkedIn, Bloomberg and Deutsche Telekom among them.
If you played Lex Imperfecta, you have already debugged it. There is a section on that further down.
Where Kyverno sits
Kyverno runs as an admission webhook. When a request reaches the Kubernetes API server, the server calls out to registered webhooks before persisting anything to etcd. Kyverno evaluates the incoming object against your policies and returns a decision: allow, allow with modifications, or reject.
Kyverno manages its own webhook configuration dynamically, based on the policies you have installed. Install a policy that only matches Pods and the webhook is registered for Pods. This keeps the API server from calling Kyverno for resources no policy cares about. It is also why a policy with a broken match block can go completely silent rather than failing loudly. If nothing matches, nothing is intercepted, and the cluster behaves exactly as though the policy were not there.
Not everything happens at admission. Generation and deletion run as controllers reacting to cluster state, and background scans re-evaluate existing resources against current policies, controlled per policy with spec.evaluation.background.enabled. This is how a policy added today produces findings about workloads deployed last year.
Two generations of policy API
Get this straight first, because the documentation, the sample library and most tutorials do not all sit on the same side of it.
The legacy API is kyverno.io/v1, with ClusterPolicy (cluster-scoped) and Policy (namespaced). One policy holds an ordered list of rules, and each rule carries a validate, mutate, generate or verifyImages block. It grew organically, and validation alone ended up with several overlapping forms: validate.pattern, validate.cel, validate.deny, validate.anyPattern and others. CleanupPolicy (kyverno.io/v2) is the other legacy type.
The current API is policies.kyverno.io/v1, which splits those rule types into dedicated resources built around CEL. Kyverno originally used JMESPath for expressions. Kubernetes added extensive CEL support from 2022 onward, and Kyverno followed, which keeps it aligned with the platform and removes one language from the stack.
This is not a preview. As of v1.18 all five CEL-based types are stable, and both legacy types are formally deprecated on a published schedule:
| Release | Date | Status of ClusterPolicy and CleanupPolicy |
|---|---|---|
| v1.17 | Jan 2026 | Marked for deprecation |
| v1.18 | Apr 2026 | Critical fixes only |
| v1.19 | Jul 2026 | Critical fixes only |
| v1.20 | Oct 2026 | Planned for removal |
So a ClusterPolicy you write today has roughly one release cycle left. When you read any Kyverno example, check the apiVersion before anything else. Both generations are valid YAML, and neither will tell you that you are looking at the wrong one.
There is an official migration guide mapping every legacy field to its CEL equivalent, and a kyverno migrate CLI command.
The policy types
| Kind | Short name | What it does |
|---|---|---|
ValidatingPolicy |
vpol |
Blocks, warns on, or audits resources that fail a rule |
ImageValidatingPolicy |
ivpol |
Verifies image signatures and attestations |
MutatingPolicy |
mpol |
Modifies resources before they are persisted |
GeneratingPolicy |
gpol |
Creates or clones resources in response to others |
DeletingPolicy |
dpol |
Removes matching resources on a schedule |
PolicyException |
A scoped, named exemption from a policy |
The short names work with kubectl explain, which is the fastest way to read a schema without leaving the terminal: kubectl explain vpol.spec.
Validating and mutating types also have Namespaced variants, such as NamespacedValidatingPolicy. Functionality and fields are identical and only the scope of resource selection differs, which lets a namespace owner manage their own policies without cluster-admin.
ValidatingPolicy takes one or more validationActions: Deny rejects the request, Warn returns a warning to the client, and Audit records the result in a report and lets the request through. Audit is how you roll out a rule against an existing cluster without breaking deployments on the first afternoon.
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: require-census
spec:
validationActions: [Deny]
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: [CREATE, UPDATE]
resources: [pods]
validations:
- expression: >
['republic.rome/gens', 'republic.rome/province'].all(label,
object.metadata.?labels[label].orValue('') != ''
)
message: "All workloads must declare a valid gens and province label."
Note object, not {{ request.object }}. The legacy JMESPath variables map onto CEL equivalents: object, oldObject, request.operation, request.userInfo.username.
The shape will look familiar if you have seen Kubernetes’ own ValidatingAdmissionPolicy. That is deliberate, and it goes further than resemblance: Kyverno can generate a native ValidatingAdmissionPolicy from a ValidatingPolicy. Where a policy is simple enough to be expressed natively, the API server evaluates it directly with no webhook call, which is faster and more resilient because it removes Kyverno from the request path for that policy.
MutatingPolicy modifies objects before persistence: injecting a sidecar, adding a default label, setting a missing resource limit. Mutating webhooks run before validating ones, so a mutating policy can supply the field that a validating policy then requires. Patches are expressed as ApplyConfiguration or JSONPatch.
ImageValidatingPolicy is the supply chain piece and the type most often missed. It verifies signatures and attestations against declared attestors, including Cosign public keys, keyless signing, transparency logs and certificates, as well as Notary. spec.matchImageReferences selects which images the policy applies to, using glob patterns or CEL.
GeneratingPolicy creates or clones resources in response to others. The canonical case is a default NetworkPolicy or an image pull secret appearing in every new namespace. spec.evaluation.generateExisting extends that to namespaces that already exist, and spec.evaluation.synchronize keeps generated resources in step with their source.
PolicyException carves out a named exemption instead of loosening the policy itself. The difference matters: an exception is a reviewable object with a scope, so an exemption for one legacy service is visible in code rather than hidden as a weakened rule affecting everything.
Reports and visibility
Kyverno writes results as Kubernetes resources rather than only logging them. The default API group is wgpolicyk8s.io/v1alpha2, producing PolicyReport and its cluster-scoped variant ClusterPolicyReport, each holding a set of results and a summary.
That API came out of the Kubernetes Policy Working Group, which has since completed its mission and closed. The API was spun out into OpenReports, a standalone project standardising reporting across policy engines and scanners so that a cluster running several tools produces one comparable stream of results. Kyverno supports it under openreports.io/v1alpha1 as an opt-in, enabled with --openreportsEnabled on the reports controller or openreports.enabled=true via Helm. The stated direction is to eventually deprecate wgpolicyk8s.io in favour of it.
Policy Reporter, an official Kyverno sub-project, is the dashboard that reads these reports and shows which namespaces have violations, which policies are producing results, and which are silent. That last column is the useful one, because a policy generating no results is either working perfectly or matching nothing at all.
Testing policies before they reach a cluster
The CLI evaluates policies against local manifests, with no cluster required:
kyverno apply ./policies/ --resource ./manifests/pods/
kyverno test runs a directory of test cases with expected results, which is what makes policies viable in CI. Existing CLI tests carry over to the new policy types without changes. kyverno json scan applies policies to arbitrary JSON payloads rather than Kubernetes manifests, so a policy can be checked against something like a rendered Dockerfile before an image is ever built.
There is also a browser playground for trying a policy against an example resource without installing anything.
Beyond the engine
Kyverno is a family of projects rather than a single binary. Alongside the CLI, the sub-projects are Kyverno JSON for applying policies to arbitrary payloads, the Kyverno Authz Server for request authorisation, Chainsaw for end-to-end Kubernetes testing, Policy Reporter, and a Backstage plugin that surfaces policy results in the developer portal from last week’s post.
How it compares
Kyverno is not automatically the right choice. Two alternatives are worth weighing first.
Kubernetes’ native ValidatingAdmissionPolicy went GA in Kubernetes 1.30 in April 2024. It is CEL, it is built into the API server, and it needs no webhook, so it is faster and has no external dependency to fail. For straightforward validation it may be everything you need. MutatingAdmissionPolicy followed in 1.32 and is still stabilising. Kyverno’s response is to generate native policies from its own where it can, so these are not strictly competing.
OPA Gatekeeper also graduated from the CNCF and remains actively developed. Rego is a genuine learning cost, but it buys generality: Gatekeeper’s constraint framework is multi-language and multi-target, and it also integrates with ValidatingAdmissionPolicy. Kyverno answers the non-Kubernetes case differently, through Kyverno JSON and the Authz Server. Kyverno publishes both an evaluation guide and a Gatekeeper migration guide, and both are worth reading before committing either way.
The short version: native admission policy does validation, in CEL, with nothing to install. Gatekeeper does validation and mutation, in Rego, across more than Kubernetes. Kyverno covers validate, mutate, generate, delete and image verification in one consistent model, all as Kubernetes resources, and testable in CI.
In practice: the adventures on OffOn
Lex Imperfecta ran on Kyverno for all three levels, and the same theme ran through every one of them: nothing failed loudly.
The beginner level gave you three broken policies: two validating and one mutating, each misconfigured differently. The point of the level was that a broken policy does not announce itself. It fails silently, and the only signal is behaviour, meaning workloads that should be blocked running freely and workloads that should be allowed being turned away. This is the webhook registration behaviour described earlier. A policy that matches nothing is indistinguishable from a policy that is not installed.
The intermediate level introduced namespace scoping. Five namespaces, two cluster-wide policies that should have covered all province namespaces, one policy scoped to a single namespace, and a PolicyException written too broadly. Scoping depends on namespace labels and how matchConstraints are written against them. kubectl get ns --show-labels was the first command that showed what was actually happening.
The expert level added Falco alongside the admission layer. Admission control and runtime security are complementary rather than alternative. Kyverno stops the wrong thing getting in. Falco catches the wrong thing behaving badly once it is already running.
How to start contributing
Read the contributing guidelines first. It is short, and it points outward: general guidelines and the code of conduct live in kyverno/community, the codebase and developer processes are in DEVELOPMENT.md, and the docs are their own repository.
Four things are asked of a pull request, and three are about making the change reviewable rather than about the code:
- Proof manifests. Include manifests that let a maintainer verify your change without having to understand all of it first.
- Documentation. New or changed functionality usually needs docs. Raise an issue, or better a separate PR, on kyverno/website.
- Tests. Test the change with the Kyverno CLI and include a test manifest in the expected format. If your change is testable as an end user, conformance and e2e tests are required, written with Chainsaw. If the feature does not work with the CLI at all, that needs its own issue asking for CLI support.
- Release triage. Indicate which release the PR is aimed at.
The pull request template warns that a change which neither resolves an existing issue nor has sign-off from a maintainer risks substantial rework or rejection. Open an issue and get agreement first.
The policy library
kyverno/policies is a separate repository from the engine and the lowest-barrier place to start. It holds ready-to-use policies for common scenarios: Pod Security Standards, general best practices, integrations with other tools. Adding a policy, improving an existing one, or adding test cases to an untested one is a well-scoped contribution that needs no knowledge of Kyverno’s internals.
With ClusterPolicy scheduled for removal in v1.20, converting sample policies to the CEL-based types is work the project needs and a good first task: mechanical enough to be approachable, useful enough to be wanted.
Documentation
Docs are a separate repository, kyverno/website. The two-generation API split described earlier is exactly the kind of gap where this work pays off, because a reader landing on an old example has no way to tell that it is old.
The CLI and tests
The CLI has room for new test cases and examples. Test coverage is also the contribution type that is easiest to review and hardest to argue with.
Core
The good first issue label in kyverno/kyverno is actively maintained by the project.
Where to go next
- kyverno.io for the docs, samples and quick start
- Policy types overview for the current API and the deprecation schedule
- Migrating to CEL Policies, a field-by-field mapping from
ClusterPolicy - CEL Libraries for the functions Kyverno adds on top of standard CEL
- Evaluating Policy Engines and the Gatekeeper Migration Guide
- Kubernetes 1.30: ValidatingAdmissionPolicy is GA for the native alternative
- OpenReports and the Kyverno integration docs
- Kyverno Playground to try a policy in the browser
- kyverno/policies for the policy library and good first contributions
- Kyverno on the CNCF project page for graduation status and project metrics
Next week: OpenTelemetry, how two competing standards became one, and why it turns up in three separate adventures.