Introduction
If you run workloads on Kubernetes, you already know how easy it is for small mistakes to slip into production. A missing resource limit here, a container running as root there, and suddenly your cluster has problems nobody planned for. This is exactly the kind of thing policy as code was built to fix, and Kyverno is one of the easiest tools to start with.
Kyverno is a policy engine built specifically for Kubernetes. Instead of learning a new language to write rules, you write policies in plain YAML, the same format you already use for your deployments and services. That alone makes it a great starting point if you are new to Kubernetes security. Kyverno is also a CNCF Graduated project, which means it has passed a high bar for stability and community trust, and it is already used in production by companies like Spotify, LinkedIn, and Adidas.
In this post, we will walk through five Kyverno policies. We will start simple and end with something a bit more advanced, verifying container image signatures. Each one comes with a small demo so you can see exactly what happens when a policy blocks something. By the end, you will have a solid feel for how Kyverno works and a few policies you can actually use.

Before we get into the policies, here is a quick picture of how Kyverno fits into the request flow. Every time you or your CI pipeline sends something to Kubernetes, Kyverno gets a chance to check it first.
A quick note before we start. Kyverno recently introduced a newer, CEL based way of writing policies, and the classic format used below (ClusterPolicy) is now marked as deprecated in the official docs, though it is still fully supported and works today. It is still the easiest format to learn as a beginner, and this guide points you to the migration path at the end, once you are ready for it.
1. Require Resource Limits
One of the most common problems in a shared cluster is a single pod using up all the CPU or memory on a node. This is often called the "noisy neighbor" problem, and it can slow down or crash other workloads running nearby.
This policy makes sure every pod defines both requests and limits for CPU and memory before it is allowed to run. You can see the full syntax for this kind of rule in the Kyverno validate rules docs.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-requests-limits
spec:
background: true
rules:
- name: validate-resources
match:
any:
- resources:
kinds:
- Pod
validate:
failureAction: Enforce
message: "CPU and memory resource requests and limits are required."
pattern:
spec:
containers:
- resources:
requests:
memory: "?*"
cpu: "?*"
limits:
memory: "?*"
cpu: "?*"
Once this is applied, any pod that does not set these values will simply be rejected. It is a small rule, but it prevents a lot of headaches later.
Demo: what happens if you skip resource limits
Say you try to apply this pod, which has no requests or limits set.
apiVersion: v1
kind: Pod
metadata:
name: no-limits-pod
spec:
containers:
- name: app
image: nginx:1.27
kubectl apply -f no-limits-pod.yaml
Error from server: error when creating "no-limits-pod.yaml": admission webhook "validate.kyverno.svc-fail" denied the request:
resource Pod/default/no-limits-pod was blocked due to the following policies
require-requests-limits:
validate-resources: 'CPU and memory resource requests and limits are required.
Validation rule validate-resources failed at path /spec/containers/0/resources/'
Now add the missing fields and try again.
apiVersion: v1
kind: Pod
metadata:
name: with-limits-pod
spec:
containers:
- name: app
image: nginx:1.27
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "250m"
kubectl apply -f with-limits-pod.yaml
# pod/with-limits-pod created
That's it. The pod goes through as soon as it meets the policy.
2. Block the "latest" Image Tag
It is tempting to just use image: myapp:latest when you are getting started. The problem is that "latest" can point to a different image every time it is rebuilt. This makes deployments unpredictable and makes it very hard to know exactly what version of your app is actually running.
This policy blocks any pod that uses the "latest" tag, or no tag at all.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-latest-tag
spec:
background: true
rules:
- name: require-image-tag
match:
any:
- resources:
kinds:
- Pod
validate:
failureAction: Enforce
message: "An image tag is required."
pattern:
spec:
containers:
- image: "*:*"
- name: validate-image-tag
match:
any:
- resources:
kinds:
- Pod
validate:
failureAction: Enforce
message: "Using a mutable image tag like 'latest' is not allowed."
pattern:
spec:
containers:
- image: "!*:latest"
This forces every team to use a proper version tag, which makes rollbacks and debugging much easier down the road. This exact example is adapted from the sample policy library that ships with the Kyverno project.
Demo: what happens if you use "latest"
apiVersion: v1
kind: Pod
metadata:
name: latest-tag-pod
spec:
containers:
- name: app
image: myapp:latest
kubectl apply -f latest-tag-pod.yaml
Error from server: error when creating "latest-tag-pod.yaml": admission webhook "validate.kyverno.svc-fail" denied the request:
resource Pod/default/latest-tag-pod was blocked due to the following policies
disallow-latest-tag:
validate-image-tag: 'Using a mutable image tag like ''latest'' is not allowed.
Validation rule validate-image-tag failed at path /spec/containers/0/image/'
Swap in a real version and the pod goes through without any trouble.
apiVersion: v1
kind: Pod
metadata:
name: versioned-pod
spec:
containers:
- name: app
image: myapp:1.4.2
kubectl apply -f versioned-pod.yaml
# pod/versioned-pod created
3. Require Labels
Labels are how Kubernetes keeps things organized. They help you filter resources, connect them to monitoring tools, and figure out who owns what. Without labels, a growing cluster turns into a pile of unlabeled boxes nobody can identify.
This policy requires every pod to have a specific label before it can be created. If you want a refresher on what labels are for, the Kubernetes labels documentation is a good place to start.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
spec:
rules:
- name: check-for-labels
match:
any:
- resources:
kinds:
- Pod
validate:
failureAction: Enforce
message: "The label 'app.kubernetes.io/name' is required."
pattern:
metadata:
labels:
app.kubernetes.io/name: "?*"
You can add more required labels over time, like team name or environment, once this basic rule feels comfortable.
Demo: what happens if a label is missing
apiVersion: v1
kind: Pod
metadata:
name: unlabeled-pod
spec:
containers:
- name: app
image: myapp:1.4.2
kubectl apply -f unlabeled-pod.yaml
Error from server: error when creating "unlabeled-pod.yaml": admission webhook "validate.kyverno.svc-fail" denied the request:
resource Pod/default/unlabeled-pod was blocked due to the following policies
require-labels:
check-for-labels: 'The label ''app.kubernetes.io/name'' is required.
Validation rule check-for-labels failed at path /metadata/labels/'
Add the label and the same pod is accepted right away.
apiVersion: v1
kind: Pod
metadata:
name: labeled-pod
labels:
app.kubernetes.io/name: myapp
spec:
containers:
- name: app
image: myapp:1.4.2
kubectl apply -f labeled-pod.yaml
# pod/labeled-pod created
4. Restrict Privileged Containers
A privileged container can do almost anything the host machine can do, including accessing other containers and system resources it should never touch. This is one of the biggest security risks in Kubernetes, and it is usually not something you want to allow unless there is a very specific reason for it.
This policy blocks any pod from running in privileged mode. It is also one of the checks covered by the Kubernetes Pod Security Standards, which Kyverno has its own dedicated guide for if you want to enforce the whole standard at once instead of writing individual rules.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-privileged-containers
spec:
background: true
rules:
- name: privileged-containers
match:
any:
- resources:
kinds:
- Pod
validate:
failureAction: Enforce
message: "Privileged mode is not allowed."
pattern:
spec:
containers:
- =(securityContext):
=(privileged): "false"
This is one of the simplest changes you can make that meaningfully reduces your attack surface.
Demo: what happens if a pod asks for privileged mode
apiVersion: v1
kind: Pod
metadata:
name: privileged-pod
spec:
containers:
- name: app
image: myapp:1.4.2
securityContext:
privileged: true
kubectl apply -f privileged-pod.yaml
Error from server: error when creating "privileged-pod.yaml": admission webhook "validate.kyverno.svc-fail" denied the request:
resource Pod/default/privileged-pod was blocked due to the following policies
disallow-privileged-containers:
privileged-containers: 'Privileged mode is not allowed.
Validation rule privileged-containers failed at path /spec/containers/0/securityContext/privileged/'
Remove the privileged flag, and the pod is created normally.
apiVersion: v1
kind: Pod
metadata:
name: safe-pod
spec:
containers:
- name: app
image: myapp:1.4.2
kubectl apply -f safe-pod.yaml
# pod/safe-pod created
5. Verify Container Image Signatures
This last one is a step up in complexity, but it is worth learning because it solves a real problem, how do you know the image running in your cluster is actually the one your team built, and not something tampered with along the way?
This is where image signing comes in. Tools like Cosign, part of the Sigstore project, let you cryptographically sign container images when you build them. Kyverno can then check that signature before allowing the image to run, and reject anything that is not signed or does not match. The full details of this feature are in the Kyverno Sigstore verification docs.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-image-signature
spec:
background: false
rules:
- name: check-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/yourorg/*"
failureAction: Enforce
attestors:
- entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
<your public key here>
-----END PUBLIC KEY-----
This is the same idea behind supply chain security, which has become a major topic in the industry after several high profile attacks where malicious code got smuggled into trusted software. Signing and verifying images is one of the clearest ways to close that gap, and it is a great skill to have even if you are still early in your Kubernetes journey.
Demo: what happens with an unsigned image
First, you would sign your image once, when you build it.
cosign sign --key cosign.key ghcr.io/yourorg/myapp:1.4.2
Now if someone tries to deploy an image that was never signed, Kyverno stops it. Note that image verification is handled through Kyverno's mutating webhook, so the error will mention "mutate" rather than "validate" even though the outcome is a rejection.
apiVersion: v1
kind: Pod
metadata:
name: unsigned-image-pod
spec:
containers:
- name: app
image: ghcr.io/yourorg/myapp:1.0.0
kubectl apply -f unsigned-image-pod.yaml
Error from server: admission webhook "mutate.kyverno.svc" denied the request:
resource Pod/default/unsigned-image-pod was blocked due to the following policies
check-image-signature:
check-signature: 'image verification failed for ghcr.io/yourorg/myapp:1.0.0:
signature not found'
Deploy the signed version instead, and it goes through.
apiVersion: v1
kind: Pod
metadata:
name: signed-image-pod
spec:
containers:
- name: app
image: ghcr.io/yourorg/myapp:1.4.2
kubectl apply -f signed-image-pod.yaml
# pod/signed-image-pod created
Conclusion
None of these policies are complicated on their own, but together they cover a lot of ground: resource limits, image tagging, labeling, privilege restrictions, and image signing. If you are new to Kyverno, a good approach is to start with the first two policies, get comfortable applying them with kubectl apply -f policy.yaml, watch how Kyverno reports violations, and then work your way down the list. You can try all of this without a real cluster using the Kyverno Playground.
Kyverno is actively evolving too. As of version 1.18, the ClusterPolicy style shown in this guide is marked deprecated in the docs in favor of newer CEL based policy types like ValidatingPolicy. It still works fully today and remains the easiest format for a beginner to learn, but once you are comfortable, it is worth reading the guide to migrating to CEL policies to see where the project is headed.
If you want to go further, the official Kyverno documentation has a large library of sample policies you can copy and adapt for your own cluster, and the Kyverno GitHub repository is where the project itself lives if you want to see what is being worked on next.
Thank you for reading! If you found this blog post helpful, please consider sharing it with others who might benefit. Feel free to check out my other blog posts and visit my socials!

