Do not start a new admission rule in Deny mode. A ValidatingAdmissionPolicyBinding takes a list of validation actions, so you can bind the same policy with Warn or Audit first, read what would have been rejected, fix the workloads, and only then add Deny.
Policy and binding are separate
A validating admission policy is made of a ValidatingAdmissionPolicy, which holds the logic in CEL, and a ValidatingAdmissionPolicyBinding, which links it to scope and parameters. The docs state that you need at least a policy and a matching binding for anything to take effect. The feature is described as stable since Kubernetes v1.30.
That split is what makes staged rollout easy. The policy stays the same. Only the binding changes.
The three actions
Each binding sets validationActions:
- Deny: a failed validation rejects the request.
- Warn: the failure is returned to the client as a warning.
- Audit: the failure is recorded in the audit event for the request.
The docs say Deny and Warn cannot be used together. Audit can be combined with either.
A staged rollout
Start with a policy, here the docs' replica limit example:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "demo-policy.example.com"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: "object.spec.replicas <= 5"
Then bind it with Warn and Audit, scoped to a test namespace by label:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "demo-binding-test.example.com"
spec:
policyName: "demo-policy.example.com"
validationActions: [Warn, Audit]
matchResources:
namespaceSelector:
matchLabels:
environment: test
Developers see warnings in their client output, and the audit log records the failures, so you can count them. When the noise is gone, change the list to Deny and widen the namespace selector in steps.
Click through it at your own pace, or open it full screen.
Watch the failure policy
The docs distinguish two kinds of failure. A failed validation is always enforced through the validation actions. A failure caused by the policy itself, for example a bad expression, is enforced only when failurePolicy is Fail or unset, and ignored otherwise. A broken expression with Fail can block real requests, so test the CEL before you widen scope. The docs mention a CEL playground for this.
Use parameters for thresholds
If the limit differs per environment, put it in a parameter resource through paramKind, and reference it from the binding with paramRef. Then the staging binding and production binding can share one policy with different numbers.
What to measure before flipping to Deny
- How many distinct workloads triggered the warning in a week.
- Whether each owner has a fix or an exception.
- Whether the audit events show system components, which you may need to scope out.
Safeguard's policies, gates and guardrails apply the same staged thinking to pull requests and pipelines. Cluster admission itself stays with Kubernetes.
Quick check
Can one binding use both Deny and Warn?
No. The docs say Deny and Warn cannot be used together. Audit can be added to either.
Sources
Last reviewed: 19 October 2026.