MutatingPolicy

Automatically mutates resources by modifying or adding fields, both at admission and in existing resources, based on defined match rules and conditions.

The Kyverno MutatingPolicy type extends the Kubernetes MutatingAdmissionPolicy type for complex mutation operations and other features required for Policy-as-Code. A MutatingPolicy is a superset of a MutatingAdmissionPolicy and contains additional fields for Kyverno specific features.

Comparison with MutatingAdmissionPolicy

The table below compares major features across the Kubernetes MutatingAdmissionPolicy and Kyverno MutatingPolicy types.

FeatureMutatingAdmissionPolicyMutatingPolicy
EnforcementAdmissionAdmission, Background, Pipelines, …
PayloadsKubernetesKubernetes
DistributionKubernetesHelm, CLI, Web Service, API, SDK
CEL LibraryBasicExtended
BindingsManualAutomatic
Auto-generation-Pod Controllers, MutatingAdmissionPolicy
External Data-Kubernetes resources or API calls
Caching-Global Context, image verification results
Background scans-Periodic, On policy creation or change
Exceptions-Fine-grained exceptions
Reporting-Policy WG Reports API
Testing-Kyverno CLI (unit), Chainsaw (e2e)
Existing Resources-Background mutation support

Additional Fields

The MutatingPolicy extends the Kubernetes MutatingAdmissionPolicy with the following additional fields for Kyverno features. A complete reference is provided in the API specification.

evaluation

The spec.evaluation field defines how the policy is applied and how the payload is processed. It can be used to enable, or disable, admission request processing and existing resource mutation for a policy.

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: MutatingPolicy
 3metadata:
 4  name: sample
 5spec:
 6  evaluation:
 7    admission:
 8      enabled: true
 9    mutateExisting:
10      enabled: false
11  ...
  • admission.enabled: When set to true, the policy is applied during admission requests (CREATE, UPDATE operations). This is the primary use case for mutating resources as they are being created or updated.

  • mutateExisting.enabled: When set to true, the policy is applied to existing resources in the cluster through background processing. This allows you to mutate resources that already exist without requiring them to be recreated or updated.

Refer to the API Reference for details.

webhookConfiguration

The spec.webhookConfiguration field defines properties used to manage the Kyverno admission controller webhook settings.

1apiVersion: policies.kyverno.io/v1alpha1
2kind: MutatingPolicy
3metadata:
4  name: add-labels
5spec:
6  webhookConfiguration:
7   timeoutSeconds: 15
8  ...

In the policy above, webhookConfiguration.timeoutSeconds is set to 15, which defines how long the admission request waits for policy evaluation. The default is 10s, and the allowed range is 1–30s. After this timeout, the request may fail or ignore the result based on the failure policy. Kyverno reflects this setting in the generated MutatingWebhookConfiguration.

Refer to the API Reference for details.

autogen

The spec.autogen field defines policy auto-generation behaviors, to automatically generate policies for pod controllers and generate MutatingAdmissionPolicy types for Kubernetes API server execution.

Here is an example of generating policies for deployments, jobs, cronjobs, and statefulsets and also generating a MutatingAdmissionPolicy from the MutatingPolicy declaration:

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: MutatingPolicy
 3metadata:
 4  name: add-default-labels
 5spec:
 6  autogen:
 7   mutatingAdmissionPolicy:
 8    enabled: true
 9   podControllers:
10     controllers:
11      - deployments
12      - jobs
13      - cronjobs
14      - statefulsets

Generating a MutatingAdmissionPolicy from a MutatingPolicy provides the benefits of faster and more resilient execution during admission controls while leveraging all features of Kyverno.

Refer to the API Reference for details.

Kyverno CEL Libraries

Kyverno enhances Kubernetes’ CEL environment with libraries enabling complex mutation logic and advanced features. For comprehensive documentation of all available CEL libraries, see the CEL Libraries documentation.

Resource Library Examples

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: MutatingPolicy
 3metadata:
 4  name: add-registry-labels
 5spec:
 6  matchConstraints:
 7    resourceRules:
 8      - apiGroups: [""]
 9        apiVersions: ["v1"]
10        resources: ["pods"]
11        operations: ["CREATE", "UPDATE"]
12  variables:
13    - name: cm
14      expression: >-
15        resource.Get("v1", "configmaps", "kube-system", "allowed-registry")
16    - name: allowedRegistry
17      expression: "variables.cm.data[?'registry'].orValue('')"
18  mutations:
19    - patchType: ApplyConfiguration
20      applyConfiguration:
21        expression: |
22          Object{
23            metadata: Object.metadata{
24              labels: {"registry": string(variables.allowedRegistry)}
25            }
26          }

HTTP Library Examples

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: MutatingPolicy
 3metadata:
 4  name: add-external-team-labels
 5spec:
 6  matchConstraints:
 7    resourceRules:
 8      - apiGroups: [""]
 9        apiVersions: ["v1"]
10        resources: ["pods"]
11        operations: ["CREATE", "UPDATE"]
12  variables:
13    - name: externalData
14      expression: >-
15        http.Get("http://test-api-service.default.svc.cluster.local:80")
16  mutations:
17    - patchType: ApplyConfiguration
18      applyConfiguration:
19        expression: |
20          Object{
21            metadata: Object.metadata{
22              labels: {"external-team": string(variables.externalData.metadata.team)}
23            }
24          }

User Library Examples

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: MutatingPolicy
 3metadata:
 4  name: add-service-account-labels
 5spec:
 6  matchConstraints:
 7    resourceRules:
 8      - apiGroups: [""]
 9        apiVersions: ["v1"]
10        resources: ["pods"]
11        operations: ["CREATE", "UPDATE"]
12  variables:
13    - name: sa
14      expression: parseServiceAccount(request.userInfo.username)
15  mutations:
16    - patchType: ApplyConfiguration
17      applyConfiguration:
18        expression: |
19          Object{
20            metadata: Object.metadata{
21              labels: {
22                "service-account": string(variables.sa.Name),
23                "namespace": string(variables.sa.Namespace)
24              }
25            }
26          }

Image Library Examples

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: MutatingPolicy
 3metadata:
 4  name: add-image-labels
 5spec:
 6  matchConstraints:
 7    resourceRules:
 8      - apiGroups: [""]
 9        apiVersions: ["v1"]
10        resources: ["pods"]
11        operations: ["CREATE", "UPDATE"]
12  variables:
13    - name: images
14      expression: >-
15        object.spec.containers.map(e, image(e.image))
16        + object.spec.?initContainers.orValue([]).map(e, image(e.image))
17        + object.spec.?ephemeralContainers.orValue([]).map(e, image(e.image))
18    - name: firstImage
19      expression: "variables.images[0]"
20  mutations:
21    - patchType: ApplyConfiguration
22      applyConfiguration:
23        expression: |
24          Object{
25            metadata: Object.metadata{
26              labels: {
27                "image-registry": string(variables.firstImage.registry()),
28                "image-repository": string(variables.firstImage.repository())
29              }
30            }
31          }

GlobalContext Library Examples

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: MutatingPolicy
 3metadata:
 4  name: add-global-context-labels
 5spec:
 6  matchConstraints:
 7    resourceRules:
 8      - apiGroups: [""]
 9        apiVersions: ["v1"]
10        resources: ["pods"]
11        operations: ["CREATE", "UPDATE"]
12  variables:
13    - name: globalData
14      expression: >-
15        globalContext.Get("team-cluster-values", "")
16  mutations:
17    - patchType: ApplyConfiguration
18      applyConfiguration:
19        expression: |
20          Object{
21            metadata: Object.metadata{
22              labels: {
23                "team": string(variables.globalData.team),
24                "environment": string(variables.globalData.environment)
25              }
26            }
27          }

Patches with ApplyConfiguration

ApplyConfiguration patches use a merge-style approach, written as CEL expressions that return an Object. This method is more intuitive and less error-prone than JSONPatch for most mutations.

Basic ApplyConfiguration

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: MutatingPolicy
 3metadata:
 4  name: add-service-account
 5spec:
 6  matchConstraints:
 7    resourceRules:
 8      - apiGroups: [""]
 9        apiVersions: ["v1"]
10        resources: ["pods"]
11        operations: ["CREATE"]
12  mutations:
13  - patchType: ApplyConfiguration
14    applyConfiguration:
15      expression: >
16        Object{
17          metadata: Object.metadata{
18            labels: Object.metadata.labels{
19              foo: "bar"
20            }
21          }
22        } 

Conditional ApplyConfiguration

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: MutatingPolicy
 3metadata:
 4  name: conditional-labels
 5spec:
 6  matchConstraints:
 7    resourceRules:
 8      - apiGroups: [""]
 9        apiVersions: ["v1"]
10        resources: ["pods"]
11        operations: ["CREATE", "UPDATE"]
12  mutations:
13    - patchType: ApplyConfiguration
14      applyConfiguration:
15        expression: |
16          has(object.metadata.labels) && has(object.metadata.labels.environment) ?
17          Object{
18            metadata: Object.metadata{
19              labels: {"managed": "true"}
20            }
21          } : 
22          Object{
23            metadata: Object.metadata{
24              labels: {"environment": "dev", "managed": "true"}
25            }
26          }

RFC 6902 JSON Patches

JSONPatch provides fine-grained control over mutations using RFC 6902 JSON Patch operations. This method is useful for complex mutations that require precise control over the patch operations.

Basic JSONPatch

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: MutatingPolicy
 3metadata:
 4  name: add-labels-jsonpatch
 5spec:
 6    matchConstraints:
 7        resourceRules:
 8      - apiGroups: [""]
 9            apiVersions: ["v1"]
10        resources: ["pods"]
11            operations: ["CREATE"]
12    mutations:
13    - patchType: JSONPatch
14        jsonPatch:
15            expression: |
16                has(object.metadata.labels) ?
17                [
18                    JSONPatch{
19                        op: "add",
20                        path: "/metadata/labels/managed",
21                        value: "true"
22                    }
23                ] : 
24                [
25                    JSONPatch{
26                        op: "add",
27                        path: "/metadata/labels",
28                        value: {"managed": "true"}
29                    }
30                ]

Multiple JSONPatch Operations

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: MutatingPolicy
 3metadata:
 4  name: complex-jsonpatch
 5spec:
 6  matchConstraints:
 7    resourceRules:
 8      - apiGroups: [""]
 9        apiVersions: ["v1"]
10        resources: ["pods"]
11        operations: ["CREATE", "UPDATE"]
12  mutations:
13    - patchType: JSONPatch
14      jsonPatch:
15        expression: |
16          [
17            JSONPatch{
18              op: "add",
19              path: "/metadata/labels/environment",
20              value: "production"
21            },
22            JSONPatch{
23              op: "add",
24              path: "/metadata/annotations/kyverno.io~1managed",
25              value: "true"
26            },
27            JSONPatch{
28              op: "replace",
29              path: "/spec/securityContext/runAsNonRoot",
30              value: true
31            }
32          ]

Conditional JSONPatch with Escape

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: MutatingPolicy
 3metadata: 
 4  name: escape-jsonpatch
 5spec:
 6    matchConstraints: 
 7        resourceRules:
 8      - apiGroups: [""]
 9                apiVersions: ["v1"]
10                resources: ["pods"]
11                operations: ["CREATE"]
12    mutations:
13    - patchType: JSONPatch
14      jsonPatch:
15        expression: |
16          [
17            JSONPatch{
18              op: "add",
19              path: "/metadata/labels/" + jsonpatch.escapeKey("app.kubernetes.io/name"),
20              value: "my-app"
21            }
22          ]

Looping with CEL Functions

Kyverno supports looping through elements within resources using CEL functions like map(), allowing you to apply mutations to multiple elements within a resource.

Iterating Through Containers

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: MutatingPolicy
 3metadata:
 4  name: foreach
 5spec:
 6  matchConstraints:
 7    resourceRules:
 8    - apiGroups: [ "" ]
 9      apiVersions: [ "v1" ]
10      operations: [ "CREATE" ]
11      resources: [ "pods" ]
12  mutations:
13  - patchType: ApplyConfiguration
14            applyConfiguration: 
15      expression: >
16                    Object{
17                        spec: Object.spec{
18            containers: object.spec.containers.map(container, Object.spec.containers{
19              name: container.name,
20              securityContext: Object.spec.containers.securityContext{
21                allowPrivilegeEscalation: false
22              }
23            })
24          } 
25        } 

This example uses CEL’s map() function to iterate through all containers in a pod and add a securityContext with allowPrivilegeEscalation: false to each container.

Conditional Iteration with Filter

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: MutatingPolicy
 3metadata:
 4  name: foreach-conditional
 5spec:
 6matchConstraints:
 7  resourceRules:
 8      - apiGroups: [""]
 9      apiVersions: ["v1"]
10        resources: ["pods"]
11      operations: ["CREATE", "UPDATE"]
12  mutations:
13    - patchType: JSONPatch
14      jsonPatch:
15        expression: |
16          object.spec.containers.map(c, 
17            c.image.startsWith("nginx:") ?
18            JSONPatch{
19              op: "add",
20              path: "/spec/containers/" + string(object.spec.containers.indexOf(c)) + "/env",
21              value: [{"name": "NGINX_ENV", "value": "production"}]
22            } : null
23          ).filter(p, p != null)

This example uses CEL’s map() and filter() functions to conditionally add environment variables only to containers that use nginx images.

Mutating Existing Resources

MutatingPolicy supports background processing to mutate existing resources in the cluster, not just during admission. This is enabled by setting spec.evaluation.mutateExisting.enabled: true.

Important Considerations

  1. Asynchronous Processing: Mutation for existing resources is an asynchronous process. There will be a variable amount of delay between when the trigger is observed and when the existing resource is mutated.

  2. Custom Permissions: Custom permissions are almost always required. Because these mutations occur on existing resources and not during an AdmissionReview, Kyverno may need additional permissions which it does not have by default. See the section on customizing permissions for how to grant additional permissions to the Kyverno background controller’s ServiceAccount. Kyverno will perform these permissions checks when a mutate existing policy is installed.

Same Trigger and Target

When the trigger and target are the same resource type, the policy will mutate ALL existing resources of that type when a trigger occurs:

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: MutatingPolicy
 3metadata:
 4  name: same-trigger-target
 5spec:
 6  evaluation:
 7    mutateExisting:
 8      enabled: true
 9  matchConstraints:
10    resourceRules:
11    - apiGroups: [ "" ]
12      apiVersions: [ "v1" ]
13      operations: [ "CREATE", "UPDATE"]
14      resources: [ "namespaces" ]
15  mutations:
16  - patchType: ApplyConfiguration
17    applyConfiguration:
18      expression: >
19        Object{
20          metadata: Object.metadata{
21            labels: Object.metadata.labels{
22              foo: "bar"
23            }
24          }
25        }

In this example, when any namespace is created or updated, Kyverno will:

  1. Mutate the namespace that triggered the policy
  2. Fetch and mutate ALL existing namespaces that match the criteria

This means if you have 10 existing namespaces and create a new one, all 11 namespaces will be mutated to add the foo: "bar" label.

Different Trigger and Target

You can specify different trigger and target resources using targetMatchConstraints. When a trigger occurs, Kyverno will mutate ALL existing resources that match the target criteria:

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: MutatingPolicy
 3metadata:
 4  name: different-trigger-target
 5spec:
 6  evaluation:
 7    mutateExisting:
 8      enabled: true
 9  matchConstraints:
10    resourceRules:
11    - apiGroups: [ "" ]
12      apiVersions: [ "v1" ]
13      operations: [ "CREATE", "UPDATE"]
14      resources: [ "namespaces" ]
15      resourceNames: ["test-namespace"]
16  targetMatchConstraints:
17  namespaceSelector:
18    matchLabels:
19        test: "enabled"
20    resourceRules:
21    - apiGroups: [ "" ]
22      apiVersions: [ "v1" ]
23      operations: [ "CREATE", "UPDATE"]
24      resources: [ "configmaps" ]
25  mutations:
26  - patchType: ApplyConfiguration
27    applyConfiguration:
28      expression: >
29        Object{
30          metadata: Object.metadata{
31            labels: Object.metadata.labels{
32              foo: "bar"
33            }
34          }
35        }

In this example:

  • Trigger: When the namespace test-namespace is created or updated
  • Target: ALL existing ConfigMaps in namespaces with the label test: enabled will be mutated to add the foo: "bar" label

This means if you have 5 ConfigMaps in matching namespaces and update the trigger namespace, all 5 ConfigMaps will be mutated.

Mutate Ordering

The order of mutations is important when multiple mutations are applied. Kyverno processes mutations in the order they appear in the policy. However, the order is not guaranteed across multiple MutatingPolicies - if multiple policies apply to the same resource, their execution order is not deterministic.

Sequential Mutations

When multiple mutations are defined within the same policy, they are processed in order. This allows you to create mutations that depend on the results of previous mutations:

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: MutatingPolicy
 3metadata:
 4  name: simple-database-policy
 5spec:
 6  matchConstraints:
 7    resourceRules:
 8      - apiGroups: [""]
 9        apiVersions: ["v1"]
10        resources: ["pods"]
11        operations: ["CREATE"]
12  mutations:
13    # First mutation
14  - patchType: ApplyConfiguration
15    applyConfiguration:
16      expression: |
17        object.spec.containers.exists(c, c.image.contains("cassandra") || c.image.contains("mongo")) ?
18        Object{
19          metadata: Object.metadata{
20            labels: Object.metadata.labels{
21              type: "database"
22            }
23          }
24        } : Object{}
25
26  # Second mutation  
27  - patchType: ApplyConfiguration
28    applyConfiguration:
29      expression: |
30        object.metadata.labels.type == "database" ?
31        Object{
32          metadata: Object.metadata{
33            labels: Object.metadata.labels{
34              backup: "yes"
35            }
36          }
37        } : Object{}

In this example:

  1. First mutation: Checks if any container uses cassandra or mongo images and adds type: database label
  2. Second mutation: Checks if the type: database label exists, then adds backup: yes

The mutations must be ordered from top to bottom in the order of their dependencies within the same policy to ensure consistent results.

Reinvocation Policy

The reinvocationPolicy controls whether mutations are reapplied when earlier mutations modify the object:

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: MutatingPolicy
 3metadata:
 4  name: reinvocation-example
 5spec:
 6  reinvocationPolicy: IfNeeded
 7  matchConstraints:
 8    resourceRules:
 9      - apiGroups: [""]
10        apiVersions: ["v1"]
11        resources: ["pods"]
12        operations: ["CREATE"]
13mutations:
14    - patchType: ApplyConfiguration
15        applyConfiguration:
16        expression: |
17            Object{
18                metadata: Object.metadata{
19              labels: {"stage": "initial"}
20                }
21            }

Reinvocation Policy Options:

  • Never: Apply mutation once per binding (default)
  • IfNeeded: Re-apply if a previous mutation modifies the object

The IfNeeded policy is useful when mutations depend on one another, as it ensures that subsequent mutations can react to changes made by earlier mutations.

Mutation with Generation

GitOps Considerations

When using MutatingPolicy in GitOps workflows, the same considerations apply as with ClusterPolicy mutate rules. MutatingPolicy can interoperate with GitOps solutions, but there are important considerations to prevent “fighting” between the mutating admission controller and GitOps controllers.

For comprehensive guidance on GitOps integration, including specific configurations for Flux and ArgoCD, see the GitOps Considerations section in the ClusterPolicy mutate documentation.


Last modified July 23, 2025 at 11:13 AM PST: Add docs for MutatingPolicy (#1601) (d1aa986)