Rules that exist only in documentation don’t get followed. Rules enforced by computers do.

Kubernetes gives you incredible flexibility. Every team can deploy whatever they want, configured however they like. This freedom becomes chaos without guardrails.

Kyverno is a policy engine for Kubernetes. It validates, mutates, and generates resources based on policies you define — as Kubernetes-native YAML.

Why Kyverno?

There are multiple policy engines: Open Policy Agent (OPA) with Gatekeeper, Kyverno, Kubewarden. I chose Kyverno because:

  1. Native YAML — No Rego, no new language to learn
  2. Validation + Mutation + Generation — One tool for all policy types
  3. Declarative — Policies are Kubernetes resources
  4. GitOps-friendly — Manage policies like any other manifest

OPA/Gatekeeper is powerful but requires learning Rego. Kyverno lets you start writing policies immediately if you know YAML.

Core Concepts

Kyverno policies can do three things:

Validate — Block resources that don’t meet criteria

"You can't deploy without resource limits"

Mutate — Automatically modify resources

"All pods get this label automatically"

Generate — Create new resources when others are created

"Every namespace gets a default NetworkPolicy"

Installing Kyverno

helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update

helm install kyverno kyverno/kyverno \
  --namespace kyverno \
  --create-namespace

For GitOps with ArgoCD:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: kyverno
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://kyverno.github.io/kyverno/
    chart: kyverno
    targetRevision: 3.1.4
    helm:
      values: |
        admissionController:
          replicas: 3
        backgroundController:
          replicas: 2
        cleanupController:
          replicas: 2
        reportsController:
          replicas: 2
  destination:
    server: https://kubernetes.default.svc
    namespace: kyverno
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Your First Policy: Require Labels

A classic starting policy — require all pods to have specific labels:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-labels
spec:
  validationFailureAction: Enforce
  rules:
    - name: require-team-label
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "The label 'team' is required."
        pattern:
          metadata:
            labels:
              team: "?*"

Now try creating a pod without the label:

kubectl run test --image=nginx
# Error: The label 'team' is required.

Validation Failure Actions

Two modes:

Enforce — Block non-compliant resources (production)

validationFailureAction: Enforce

Audit — Allow but report non-compliance (testing)

validationFailureAction: Audit

Start with Audit to see what would fail, then switch to Enforce.

Common Validation Policies

Require Resource Limits

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-resource-limits
spec:
  validationFailureAction: Enforce
  rules:
    - name: require-limits
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "CPU and memory limits are required."
        pattern:
          spec:
            containers:
              - resources:
                  limits:
                    memory: "?*"
                    cpu: "?*"

Disallow Privileged Containers

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-privileged
spec:
  validationFailureAction: Enforce
  rules:
    - name: deny-privileged
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "Privileged containers are not allowed."
        pattern:
          spec:
            containers:
              - securityContext:
                  privileged: false

Disallow Latest Tag

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-latest-tag
spec:
  validationFailureAction: Enforce
  rules:
    - name: require-image-tag
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "Using ':latest' tag is not allowed. Specify a version tag."
        pattern:
          spec:
            containers:
              - image: "!*:latest"

Restrict Image Registries

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: restrict-registries
spec:
  validationFailureAction: Enforce
  rules:
    - name: allowed-registries
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "Images must come from approved registries."
        pattern:
          spec:
            containers:
              - image: "registry.example.com/* | gcr.io/my-project/*"

Mutation Policies

Automatically modify resources to enforce standards:

Add Default Labels

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-default-labels
spec:
  rules:
    - name: add-managed-by
      match:
        any:
          - resources:
              kinds:
                - Pod
                - Deployment
                - Service
      mutate:
        patchStrategicMerge:
          metadata:
            labels:
              managed-by: kyverno
              environment: "{{request.namespace}}"

Add Default Security Context

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-security-context
spec:
  rules:
    - name: add-run-as-nonroot
      match:
        any:
          - resources:
              kinds:
                - Pod
      mutate:
        patchStrategicMerge:
          spec:
            securityContext:
              runAsNonRoot: true
              seccompProfile:
                type: RuntimeDefault

Add Image Pull Secrets

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-imagepullsecret
spec:
  rules:
    - name: add-registry-secret
      match:
        any:
          - resources:
              kinds:
                - Pod
      mutate:
        patchStrategicMerge:
          spec:
            imagePullSecrets:
              - name: registry-credentials

Generation Policies

Create resources automatically when others are created:

Default NetworkPolicy per Namespace

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: generate-default-networkpolicy
spec:
  rules:
    - name: generate-deny-all
      match:
        any:
          - resources:
              kinds:
                - Namespace
      exclude:
        any:
          - resources:
              namespaces:
                - kube-system
                - kyverno
      generate:
        apiVersion: networking.k8s.io/v1
        kind: NetworkPolicy
        name: default-deny
        namespace: "{{request.object.metadata.name}}"
        data:
          spec:
            podSelector: {}
            policyTypes:
              - Ingress
              - Egress

Every new namespace gets a default-deny NetworkPolicy. Teams must explicitly allow traffic.

Default ResourceQuota per Namespace

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: generate-resourcequota
spec:
  rules:
    - name: generate-quota
      match:
        any:
          - resources:
              kinds:
                - Namespace
      exclude:
        any:
          - resources:
              namespaces:
                - kube-system
                - kyverno
      generate:
        apiVersion: v1
        kind: ResourceQuota
        name: default-quota
        namespace: "{{request.object.metadata.name}}"
        data:
          spec:
            hard:
              requests.cpu: "4"
              requests.memory: 8Gi
              limits.cpu: "8"
              limits.memory: 16Gi
              persistentvolumeclaims: "10"

Using Variables

Kyverno supports JMESPath expressions for dynamic values:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-namespace-label
spec:
  rules:
    - name: add-ns-to-pods
      match:
        any:
          - resources:
              kinds:
                - Pod
      mutate:
        patchStrategicMerge:
          metadata:
            labels:
              namespace: "{{request.namespace}}"
              created-at: "{{time_now_utc()}}"

Useful variables:

  • {{request.namespace}} — Namespace of the resource
  • {{request.object.metadata.name}} — Name of the resource
  • {{request.userInfo.username}} — User who made the request
  • {{time_now_utc()}} — Current timestamp

Excluding Resources

Don’t apply policies to system components:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-labels
spec:
  rules:
    - name: require-team-label
      match:
        any:
          - resources:
              kinds:
                - Pod
      exclude:
        any:
          - resources:
              namespaces:
                - kube-system
                - kube-public
                - kyverno
          - subjects:
              - kind: ServiceAccount
                name: system:*

Policy Reports

Kyverno generates policy reports showing compliance:

kubectl get policyreport -A
kubectl get clusterpolicyreport

Example report:

apiVersion: wgpolicyk8s.io/v1alpha2
kind: PolicyReport
metadata:
  name: polr-ns-default
  namespace: default
results:
  - message: "validation rule 'require-team-label' passed."
    policy: require-labels
    result: pass
    source: kyverno
  - message: "validation rule 'require-limits' failed."
    policy: require-resource-limits
    result: fail
    source: kyverno

Integrate with Prometheus for alerting on policy violations:

# ServiceMonitor for Kyverno metrics
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: kyverno
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: kyverno
  endpoints:
    - port: metrics

Policy Sets

Kyverno maintains curated policy sets:

# Install Pod Security Standards (Baseline)
kubectl apply -f https://raw.githubusercontent.com/kyverno/policies/main/pod-security/baseline/

# Install Pod Security Standards (Restricted)
kubectl apply -f https://raw.githubusercontent.com/kyverno/policies/main/pod-security/restricted/

These implement Kubernetes Pod Security Standards as Kyverno policies.

My Production Policy Set

Here are the policies I run on every cluster:

# require-probes.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-probes
spec:
  validationFailureAction: Enforce
  rules:
    - name: require-readiness-probe
      match:
        any:
          - resources:
              kinds:
                - Deployment
      exclude:
        any:
          - resources:
              namespaces:
                - kube-system
                - kyverno
      validate:
        message: "Readiness probe is required for all deployments."
        pattern:
          spec:
            template:
              spec:
                containers:
                  - readinessProbe:
                      "?*": "?*"
---
# disallow-default-namespace.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-default-namespace
spec:
  validationFailureAction: Enforce
  rules:
    - name: deny-default
      match:
        any:
          - resources:
              kinds:
                - Pod
                - Deployment
                - Service
              namespaces:
                - default
      validate:
        message: "Using the 'default' namespace is not allowed."
        deny: {}
---
# require-requests.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-requests
spec:
  validationFailureAction: Enforce
  rules:
    - name: require-cpu-memory-requests
      match:
        any:
          - resources:
              kinds:
                - Pod
      exclude:
        any:
          - resources:
              namespaces:
                - kube-system
      validate:
        message: "CPU and memory requests are required."
        pattern:
          spec:
            containers:
              - resources:
                  requests:
                    memory: "?*"
                    cpu: "?*"

Testing Policies

Use the Kyverno CLI to test policies before applying:

# Install CLI
brew install kyverno

# Test policy against a resource
kyverno apply policy.yaml --resource pod.yaml

# Test all policies in a directory
kyverno apply ./policies/ --resource ./manifests/

Create a test suite:

# test/values.yaml
policies:
  - policy.yaml
resources:
  - resources/valid-pod.yaml
  - resources/invalid-pod.yaml
results:
  - policy: require-labels
    resource: valid-pod.yaml
    result: pass
  - policy: require-labels
    resource: invalid-pod.yaml
    result: fail

Run tests:

kyverno test ./test/

Background Scanning

Kyverno can scan existing resources, not just new ones:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: audit-existing
spec:
  validationFailureAction: Audit
  background: true  # Enable background scanning
  rules:
    - name: check-labels
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "Label 'team' is missing."
        pattern:
          metadata:
            labels:
              team: "?*"

This generates reports for all existing pods, not just new ones.

Exceptions

Sometimes you need to bypass policies:

apiVersion: kyverno.io/v1
kind: PolicyException
metadata:
  name: allow-privileged-monitoring
  namespace: monitoring
spec:
  exceptions:
    - policyName: disallow-privileged
      ruleNames:
        - deny-privileged
  match:
    any:
      - resources:
          kinds:
            - Pod
          namespaces:
            - monitoring
          names:
            - node-exporter*

Document why each exception exists. Exceptions should be rare.

Integration with CI/CD

Validate manifests in your pipeline before they reach the cluster:

# .gitlab-ci.yml
validate-policies:
  stage: test
  image: ghcr.io/kyverno/kyverno-cli:latest
  script:
    - kyverno apply ./policies/ --resource ./k8s/
  rules:
    - if: $CI_MERGE_REQUEST_ID

Catch policy violations before merge, not during deployment.

Why This Matters

Policies are documentation that enforces itself.

Without policy engines:

  • Standards exist in wikis nobody reads
  • Reviews catch issues sometimes, inconsistently
  • Non-compliance accumulates silently

With Kyverno:

  • Standards are code, versioned in Git
  • Enforcement is automatic, immediate
  • Violations are visible in reports

This is low-friction governance. Not because it’s less strict, but because compliance happens automatically. Developers don’t need to remember every rule — the system remembers for them.


The best policies are the ones you never have to think about. Kyverno turns your Kubernetes standards into self-enforcing code.