Safeguard
Developer Tools•

ValidatingAdmissionPolicy vs admission webhooks: when to use CEL

ValidatingAdmissionPolicy is stable since Kubernetes v1.30 and runs CEL rules inside the API server. When it replaces a webhook and when it does not.

Safeguard Security Team
3 min read

ValidatingAdmissionPolicy lets you write validation rules in the Common Expression Language and run them inside the API server, with no webhook service to host. It has been stable since Kubernetes v1.30. Use it for simple checks on a single object. Keep a webhook when you need external data or logic CEL cannot express.

How it works

You create two resources. The policy defines which requests it matches and the CEL validations. The binding connects the policy to a scope and sets what happens on failure. The docs state that without a binding the policy has no effect.

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"

An optional parameter resource, such as a ConfigMap, can supply values, so one policy can serve many namespaces with different limits.

Validation actions

A binding sets validationActions. Deny rejects the request, Warn returns a warning to the client, and Audit records the failure in the audit event. Deny and Warn cannot be combined. That makes a staged rollout easy: bind with Warn and Audit first, review the results, then switch to Deny.

Policy versus webhook

Reasons to prefer a policy:

  • No extra service to run, patch or keep available. A broken webhook can block API writes.
  • Rules are plain YAML and live in version control.
  • Evaluation is in process.

Reasons to keep a webhook:

  • You need to call an external system, such as an inventory or a signature check.
  • The logic is too complex for an expression.
  • You need to mutate objects. ValidatingAdmissionPolicy validates only.

Mind failurePolicy. The docs say failures caused by the failure policy are enforced only when it is Fail or unset, and ignored otherwise. Decide on purpose whether a broken policy should block or allow.

Good first policies

  • Require resource limits on containers.
  • Require a non-latest image tag or a digest.
  • Limit replicas per environment.
  • Require a team label on namespaces.

For pod hardening, Pod Security admission already covers the common cases, so use CEL for rules specific to your organization.

Safeguard policies, gates and guardrails apply similar rules earlier, at the pull request, so developers learn about a violation before deployment.

Rolling out a first policy

Pick one rule that is easy to explain, such as requiring resource limits. Write the policy, bind it in a test namespace with the Warn and Audit actions, and deploy normal workloads. Read the warnings, fix the manifests, then change the binding to Deny and widen its scope one namespace at a time. Keep the policy files in the same repository as your other cluster configuration.

Quick check

Does a policy work without a binding?

No. The binding supplies the scope and the actions.

Can it change objects?

No. It validates only. Mutation needs a different mechanism.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.