How to balance developer velocity with security using Policy-as-Code and Common Expression Language.
Platform Engineering has never been a buzzword: it provides the backbone of scalable, secure, and developer-friendly [Kubernetes] operations. But as platforms grow, so does the chaos. At the heart of effective platform engineering lies a single challenge: How do you enforce consistent policies across clusters, workloads, and teams without becoming a bottleneck?
This is where Kyverno shines. By making use of the recent addition of CEL (Common Expression Language) support, it has evolved from a policy engine into a comprehensive governance platform.
What is Kyverno?
Kyverno is a policy engine explicitly designed for Kubernetes. Unlike general-purpose policy tools that require learning new languages (like OPA and Rego), Kyverno speaks Kubernetes natively. It uses standard Kubernetes resources to define and enforce policies, meaning no new complex syntax—just the YAML manifests that Kubernetes administrators already know and LOVEEEE...
Kyverno enables you to:
- Validate resources against policies before they're admitted to the cluster.
- Mutate resources automatically to inject defaults, labels, or sidecars.
- Generate resources dynamically based on triggers (like creating NetworkPolicies for new namespaces).
- Verify image signatures and attestations for supply chain security.
Why should platform engineering teams use Kyverno?
Platform Engineering is about enabling developer velocity while maintaining security and compliance. Kyverno acts as the guardrails that make this possible.
Self-service with guardrails
You can create an environment where developers can self-serve without requiring manual reviews for every deployment. Kyverno automates the “boring” stuff.
For example, you can automatically add resource limits to containers that don't specify them, inject sidecar containers for logging or monitoring, apply consistent labels and annotations across all workloads, or enforce naming conventions for resources.
Security by Default
Security shouldn't be an afterthought. Using Kyverno, platform teams can enforce security policies cluster-wide by blocking privileged containers, requiring container images from approved registries, enforcing pod security standards, verifying image signatures with Sigstore/Cosign integration, preventing hostPath volume mounts, and many other best practices.
Audit-ready compliance
Meeting standards like SOC 2, PCI-DSS, or HIPAA requires proof. Kyverno provides Policy Reports that show compliance status across clusters, and an Audit Mode to test policies safely before enabling enforcement.
Pro Tip: Always start new policies in Audit mode. This generates reports on what would have been blocked, allowing you to fix existing violations without disrupting production traffic.
The game-changer: CEL support
Common Expression Language (CEL) is a non-Turing-complete expression language that's fast becoming the standard for policy evaluation in the Kubernetes ecosystem. It is fast, portable, extensible, and safe.
Kubernetes supports CEL as Generally Available (GA) with CRD validation rules since v1.25 and on ValidatingAdmissionPolicy since v1.30. Kyverno extends this support in its latest releases.
While the ClusterPolicy (and namespaced Policy) supports CEL expressions, new policy types have been created for CEL. Figure 1 shows the additions.

Why does CEL matter for platform engineers?
- Performance: CEL expressions are compiled and executed significantly faster than traditional webhook validations, reducing latency on resource creation/modification.
- Native Integration: Since Kubernetes 1.26+, CEL has been built into the ValidatingAdmissionPolicy (started as “alpha”). Kyverno’s support means you can use one language across your entire stack.
- Expressiveness: CEL handles complex logic with elegant, concise expressions.
Some Examples
- The following example creates a ClusterPolicy using CEL that requires each pod to have a label that contains only lowercase letters, numbers, and hyphens (underscores, uppercase letters, and other characters are not allowed).
apiVersion: kyverno.io/v1 # policy-require-labels.yaml
kind: ClusterPolicy
metadata:
name: require-labels-cel
spec:
validationFailureAction: Enforce
rules:
- name: check-team-label
match:
any:
- resources:
kinds:
- Pod
validate:
cel:
expressions:
- expression: "has(object.metadata.labels.team)"
message: "All pods must have a 'team' label"
- expression: "object.metadata.labels.team.matches('^[-a-z0-9]+$')"
message: "Team label must contain only lowercase letters, numbers, and hyphens"The above example uses the old ClusterPolicy type with the CEL extension, but we also have other policy types for the same purpose. The following examples will use those.
You can apply it to your cluster and see the result:
$> kubectl apply -f policy-require-labels.yaml
clusterpolicy.kyverno.io/require-labels-cel created
$> kubectl run test-pod --image=busybox
Error from server: admission webhook "validate.kyverno.svc-fail" denied the request:
resource Pod/default/test-pod was blocked due to the following policies
require-labels-cel:
check-team-label: All pods must have a 'team' label
$> kubectl run test-pod --image=busybox -l team=my_team
Error from server: admission webhook "validate.kyverno.svc-fail" denied the request:
resource Pod/default/test-pod was blocked due to the following policies
require-labels-cel:
check-team-label: Team label must contain only lowercase letters, numbers, and hyphens- Dynamic resource requests for memory based on the team size:
## v1beta1 will work with Kyverno 1.16
## for v1.15, use v1alpha1
apiVersion: policies.kyverno.io/v1beta1
kind: ValidatingPolicy
metadata:
name: memory-requests-per-team-size
spec:
validationActions:
- Deny
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: [v1]
operations: [CREATE, UPDATE]
resources: [pods]
validations:
- message: "Memory request exceeds team quota"
expression: |
object.metadata.labels.team == 'small-team' ?
int(object.spec.containers[0].resources.requests.memory.replace('Mi', '')) <= 512 :
int(object.spec.containers[0].resources.requests.memory.replace('Mi', '')) <= 2048This policy will check the “team” label for the incoming pod requests. If the value is “small-team”, the memory request must be less than or equal to 512Mi; for other values, it must be less than or equal to 2048Mi.
Pro Tip: This policy assumes that the request value will be made with Mi and only checks the first container. For other possible values, the expression must be extended.
- Create a default deny NetworkPolicy for newly created namespaces. The policy below assigns the requested namespace name to the nsName variable and uses it in the expression:
## v1beta1 will work with Kyverno v1.16
## for v1.15, use v1alpha1
apiVersion: policies.kyverno.io/v1beta1
kind: GeneratingPolicy
metadata:
name: generate-network-policy
spec:
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["namespaces"]
variables:
- name: nsName
expression: "object.metadata.name"
generate:
- expression: >
generator.Apply(variables.nsName, [
{
"kind": dyn("NetworkPolicy"),
"apiVersion": dyn("networking.k8s.io/v1"),
"metadata": dyn({
"name": "default-deny",
}),
"spec": dyn({
"podSelector": dyn({}),
"policyTypes": dyn(["Ingress", "Egress"])
})
}]
)- Parametrized Policies: Kyverno now supports ClusterPolicy with parameter resources. This allows you to write a policy once (as a template) and configure it differently for every namespace or environment.
Why is this huge for GitOps?
- Reusability: The policy logic stays static; only the config changes.
- Team Autonomy: Platform teams define the rule (e.g., "Replica Limit"), but individual teams can be assigned different limits via a simple ConfigMap, Secret, or CRD.
## Define the maxReplicas per environment in a ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: replica-limit
namespace: default
data:
maxReplicas: "3"
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-deployment-replicas
spec:
validationFailureAction: Enforce
background: false
rules:
- name: deployment-replicas
match:
any:
- resources:
kinds:
- Deployment
- Deployment/scale
operations:
- CREATE
- UPDATE
validate:
cel:
paramKind:
apiVersion: v1
kind: ConfigMap
paramRef:
name: "replica-limit"
namespace: default
parameterNotFoundAction: "Deny"
expressions:
- expression: "object.spec.replicas <= int(params.data.maxReplicas)"
messageExpression: "'Deployment spec.replicas must be less than ' + string(params.data.maxReplicas)"
See it in action:
$> kubectl apply -f policy-replica-limit.yaml
configmap/replica-limit created
clusterpolicy.kyverno.io/check-deployment-replicas created
$> kubectl create deploy test-deploy --image=nginx:mainline-alpine-slim --replicas=5
error: failed to create deployment: admission webhook "validate.kyverno.svc-fail" denied the request:
resource Deployment/default/test-deploy was blocked due to the following policies
check-deployment-replicas:
deployment-replicas: Deployment spec.replicas must be less than 3
$> kubectl create deploy test-deploy --image=nginx:mainline-alpine-slim --replicas=2
deployment.apps/test-deploy created
$> kubectl scale deploy test-deploy --replicas=5
Error from server: admission webhook "validate.kyverno.svc-fail" denied the request:
resource Scale/default/test-deploy was blocked due to the following policies
check-deployment-replicas:
deployment-replicas: Deployment spec.replicas must be less than 3Final thoughts
Kyverno has become an essential tool for platform engineering teams building self-service Kubernetes platforms. Its Kubernetes-native approach, combined with CEL's raw performance and expressiveness, makes it the ideal choice for balancing velocity with governance.
Whether you are just starting your platform engineering journey or looking to level up your existing stack, Kyverno deserves a place in your toolkit.
Ready to get started? Check out the Kyverno documentation and join the growing community of platform engineers using policy-as-code to build better Kubernetes platforms. You can find more example policies here and try them out on the playground.








