Kyverno: Kubernetes Policies Without Rego
The Policy Problem in Kubernetes
Anyone running Kubernetes clusters knows the problem: the more teams and services work on a cluster, the more important binding rules become. No container should land in production with the latest tag. Every deployment needs resource limits. New namespaces should automatically get a NetworkPolicy. Enforcing these requirements manually -- via documentation and code review -- does not scale.
At encircle360, we have touched on this topic repeatedly over the past years. In our article on Kubernetes in production, we described how important resource limits and clean configurations are. But we had not yet answered how to enforce these standards automatically. This is exactly where policy engines come into play.
OPA Gatekeeper: The Established Approach
The most well-known candidate in this space is OPA Gatekeeper -- a combination of the Open Policy Agent and a Kubernetes-specific integration. OPA is a powerful, universal policy framework. It uses Rego, a declarative query language inspired by Datalog.
We evaluated OPA Gatekeeper in early 2021 and were impressed by its flexibility. Rego can essentially express any conceivable policy. The problem: the learning curve is steep. Rego is not a language that a Kubernetes administrator picks up in an afternoon. The syntax is unfamiliar, debugging is cumbersome, and for simple policies like "disallow the latest tag" you end up writing a surprising amount of code.
An example -- this Rego policy disallows the latest tag:
package k8scontainerlimits
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
endswith(container.image, ":latest")
msg := sprintf("Container '%v' uses the latest tag", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not contains(container.image, ":")
msg := sprintf("Container '%v' has no tag specified", [container.name])
}
On top of that, you need the ConstraintTemplate and the Constraint as Kubernetes resources. For a team that primarily works with YAML and Helm, this is a high barrier to entry.
Kyverno: The Kubernetes-Native Approach
Then we came across Kyverno. Kyverno was released in version 1.0 at the end of 2020 and has been a CNCF Sandbox project since mid-2021. At the time of this article, version 1.5 is available, and the project is visibly gaining traction in the community.
The fundamental difference from OPA Gatekeeper: Kyverno policies are written in pure YAML. No new language, no separate tooling. If you can write Kubernetes manifests, you can write Kyverno policies. For us, this was the decisive factor.
Kyverno integrates as an admission webhook into the Kubernetes API server. Every request to the API -- whether creating a pod, updating a deployment, or creating a namespace -- passes through the configured policies. Kyverno can do three things: validate, mutate, and generate.
Validation: Enforcing Rules
The most obvious function is validation. A policy checks incoming resources against defined criteria and rejects them if they do not match.
The example from above -- disallowing the latest tag -- looks like this in Kyverno:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-latest-tag
annotations:
policies.kyverno.io/title: Disallow Latest Tag
policies.kyverno.io/description: >-
Der latest-Tag ist nicht deterministisch und sollte
in Production nicht verwendet werden.
spec:
validationFailureAction: enforce
rules:
- name: validate-image-tag
match:
resources:
kinds:
- Pod
validate:
message: "Der Tag 'latest' ist nicht erlaubt. Bitte einen spezifischen Tag verwenden."
pattern:
spec:
containers:
- image: "!*:latest"
This is pure YAML. No new language, no compiling, no separate testing framework. The policy describes the desired pattern, and Kyverno rejects anything that does not match. The validationFailureAction: enforce ensures that the policy actually blocks -- as opposed to audit, which only creates a report without rejecting the request.
For getting started, we recommend running new policies in audit mode first. This way you can see which existing resources would violate the policy without disrupting ongoing operations.
Mutation: Automatically Adjusting Resources
Mutation is the second pillar of Kyverno and in practice at least as valuable as validation. A mutation policy automatically modifies incoming resources before they land in the cluster.
A typical use case: setting default labels on all deployments. In our clusters, we want every deployment to carry at least a managed-by label and a team label.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-default-labels
spec:
rules:
- name: add-labels
match:
resources:
kinds:
- Deployment
- StatefulSet
mutate:
patchStrategicMerge:
metadata:
labels:
+(app.kubernetes.io/managed-by): kyverno
+(encircle360.com/policy-version): "1.0"
The + prefix before the label key means: only add this label if it does not already exist. Existing labels are not overwritten. This small syntax convention makes mutation policies safe and predictable.
We also use mutation policies to automatically set resource requests on pods that were deployed without them and to add annotations for our monitoring system. This significantly reduces the amount of boilerplate in our Helm charts.
Generation: Automatically Creating Resources
The third function of Kyverno is generation -- and it surprised us the most. A generate policy automatically creates new Kubernetes resources in response to other events.
Our specific use case: every new namespace should automatically receive a default NetworkPolicy that allows all egress traffic but restricts ingress traffic to explicitly approved ports. Previously, we did this manually or solved it via a shell script in the CI pipeline. With Kyverno, this is done declaratively:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: generate-default-networkpolicy
spec:
rules:
- name: default-deny-ingress
match:
resources:
kinds:
- Namespace
exclude:
resources:
namespaces:
- kube-system
- kube-public
- kyverno
generate:
kind: NetworkPolicy
name: default-deny-ingress
namespace: "{{request.object.metadata.name}}"
data:
spec:
podSelector: {}
policyTypes:
- Ingress
As soon as someone creates a new namespace, Kyverno automatically creates the NetworkPolicy within it. The exclude rule ensures that system namespaces are not affected. This is a powerful concept that extends far beyond NetworkPolicies. We are currently experimenting with automatically generating default ResourceQuotas and LimitRanges as well.
Kyverno vs. OPA Gatekeeper: Our Assessment
After several months with Kyverno in staging and production, we can draw a fairly clear comparison.
OPA Gatekeeper is the more powerful, more flexible tool. Rego can express policies that are difficult or impossible to implement with Kyverno's YAML approach -- such as complex cross-resource validations or policies that incorporate external data sources. For organizations with a dedicated platform engineering team that has the time and motivation to learn and maintain Rego, OPA Gatekeeper is an excellent choice.
For us -- a smaller team that uses Kubernetes as a tool, not as an end in itself -- Kyverno was the better decision. The barrier to entry is minimal. Everyone on the team who can read YAML can read, understand, and modify Kyverno policies. New policies are written in minutes, not hours. And for the vast majority of use cases -- tag validation, label standards, default resources -- Kyverno's feature set is more than sufficient.
A practical advantage that is often overlooked: Kyverno policies are regular Kubernetes resources. You can manage them with kubectl, package them in Helm charts, and deploy them through the same GitOps workflows you use for all other resources. There is no separate toolchain, no additional build step.
Installation and Getting Started
Installation is straightforward -- a single Helm chart:
helm repo add kyverno https://kyverno.github.io/kyverno/
helm install kyverno kyverno/kyverno -n kyverno --create-namespace
Kyverno also comes with a growing library of pre-built policies that cover best practices. On the official website, you can find policies for Pod Security Standards, best practices, and compliance requirements. We were able to adopt many of them directly or use them with minimal adjustments.
Conclusion
Just under a year after its 1.0 release, Kyverno is already a serious player in the Kubernetes policy space. The project solves a real problem in a way that fits the Kubernetes ecosystem: declarative, YAML-based, and without unnecessary overhead.
For teams that want to introduce policy enforcement without learning a new programming language, Kyverno is our clear recommendation. It covers the most common use cases -- validation, mutation, generation -- and integrates seamlessly into existing workflows. The combination of a low barrier to entry and high practical value convinced us.
Our next step is integrating Kyverno into our CI pipeline to validate policies during Helm template rendering -- before the manifests even reach the cluster. But more on that another time.
Written by
Patrick HütterFounder & Software Architect
Software architect, engineer and entrepreneur. Patrick has been building products and platforms for over a decade — from enterprise backends and cloud-native infrastructure to AI-powered applications. As founder of encircle360, he combines deep technical expertise with entrepreneurial vision, driving open source projects that create real impact.
You might also like
CI Runners in a microVM: Docker Builds with Kata Containers on Kubernetes
Aug 31, 2026 · 10 min read
GitOps with Helmfile and Kyverno: Our Deployment Workflow
Mar 10, 2025 · 7 min read
K3s and KubeVirt: Converged Infrastructure on Bare Metal
Jan 20, 2025 · 6 min read