Open Policy Agent (OPA, pronounced “oh-pa”) is a general-purpose policy engine. A program sends it structured data (a Kubernetes admission request, a Terraform plan, an HTTP request, a CI job description), OPA evaluates that data against policies written in a declarative language called Rego, and it answers with a decision: allow or deny, or any structured result such as a list of violations. OPA does not enforce anything itself; it separates the decision from the enforcement point, so the same language and tooling can govern many systems. Gatekeeper is the project that brings this to Kubernetes. It is a separate repository in the same GitHub organisation (open-policy-agent/gatekeeper) that runs as an admission controller and turns policies into Kubernetes resources: reusable ConstraintTemplates that hold the logic, and Constraints that apply them with parameters. OPA is a CNCF graduated project (accepted 29 March 2018, incubating 2 April 2019, graduated 29 January 2021). Gatekeeper is listed in the CNCF Landscape as its own item, “OPA/Gatekeeper”, with no maturity level of its own, so the graduated status belongs to OPA and Gatekeeper is its Kubernetes integration. This brief explains both from the problem they solve to installing Gatekeeper, writing real policies, rolling them out without breaking a cluster, testing them, and operating and securing the result, with an honest comparison to Kyverno.
CNCF daily · Sprint 4, post 9 of 15 · Security & Compliance
At a glance
| Category | Provisioning › Security & Compliance |
| CNCF status | Open Policy Agent: Graduated (accepted 29 March 2018, incubating 2 April 2019, graduated 29 January 2021). Gatekeeper: a Landscape item inside the OPA organisation, with no separate CNCF maturity level |
| Origin | OPA was created at Styra in 2016 and donated to the CNCF in 2018 |
| Written in | Go |
| License | Apache 2.0 |
| Latest OPA release | v1.22.0, tagged 29 October 2026 (the 1.x line uses Rego v1 syntax by default) |
| Latest Gatekeeper release | v3.23.1, tagged 27 August 2026; a v3.24.0 beta was tagged on 13 July 2026 |
| Policy languages | Rego (OPA), and CEL in Gatekeeper templates through the Kubernetes Validating Admission Policy engine |
| Gatekeeper resources | ConstraintTemplate, constraints created from each template, mutators (Assign, AssignMetadata, ModifySet, AssignImage), Config, SyncSet, Provider, ExpansionTemplate |
| Enforcement actions | deny (default), warn, dryrun and scoped, per enforcement point |
| Test tools | opa test for Rego unit tests, gator verify and gator test for Gatekeeper policies |
| Install | Helm chart or the release manifest |
The problem OPA and Gatekeeper solve
Every organisation has rules: images must come from an approved registry, every workload needs an owner label, containers must not run privileged, ingress hosts must be unique, and a Terraform plan must not open a database to the internet. Written in a wiki, such rules are followed unevenly. Hard-coded into each tool, they are written in a different way every time: a Kubernetes admission webhook in Go, a script in CI, a custom check in an API gateway. Each copy drifts, none of them can be tested in the same way, and none can answer the audit question “which resources already break this rule?”
OPA’s answer is to treat policy as code that lives outside the enforcing program:
- One language. Rego expresses rules over JSON-shaped data, whatever produced it.
- One decision interface. The caller sends
inputand receives a decision; OPA does not care whether the caller is Kubernetes, Envoy, a CI job or a microservice. - Testable and reviewable. Policies are plain text files with unit tests, so they go through Git review and CI.
Gatekeeper adds what Kubernetes needs on top of the bare engine. Policies become Kubernetes resources, so they are applied, versioned and rolled out like the rest of cluster configuration. A shared, parameterised library of templates exists. Violations are recorded by an audit loop for the resources that were created before the policy existed. And a dry-run mode lets you see what a policy would block before it blocks anything.
Key concepts
- Rego. OPA’s declarative query language. A rule says “this is true if all these conditions hold”. You write what must hold, not the steps to check it.
- Input, data and policy.
inputis what the caller sends.datais extra context loaded into OPA. A policy is a set of Rego rules, organised in packages. - Rego v1. OPA 1.0 made the newer syntax the default: rule bodies use the
ifkeyword, and sets and multi-value rules usecontains. Gatekeeper still defaults to the older v0 syntax in templates and accepts v1 as an opt-in per template (from Gatekeeper 3.19). - ConstraintTemplate. A Gatekeeper resource that defines a new kind of policy. It declares the name of the constraint kind it creates, the schema for that kind’s parameters, and the Rego (or CEL) that decides violations.
- Constraint. An instance of a template’s kind. It says which resources it covers (
match), the parameter values (parameters) and what to do on a violation (enforcementAction). violationrule. The Rego entry point in a Gatekeeper template. Each object it produces is one violation, with a message and optional details. No violations means the request is allowed.- Audit. A periodic loop that evaluates existing resources against all constraints and writes the results into each constraint’s status.
- Enforcement action and enforcement point. What happens on a violation, and where the constraint is evaluated: the admission webhook,
gatorin CI, the audit loop, or Kubernetes’ own Validating Admission Policy. - Mutator. A separate resource kind that changes an object during admission, for example adding a label or setting
imagePullPolicy. - Provider. An in-cluster service that Gatekeeper can call for external data, such as image signature results.
Architecture
Policies are ordinary Kubernetes resources; the webhook judges new requests and the audit loop judges what already exists, with the same rules. Blue is a user or repository, slate Kubernetes, purple a Gatekeeper component, green the policy resources and orange the results. Open the zoomable diagram, or download it as SVG or PNG.
| Component | Responsibility |
|---|---|
| Users and CI | Create and change Kubernetes resources, and run gator against manifests before they reach a cluster |
| Kubernetes API server | Calls the Gatekeeper validating and mutating webhooks for each matching request |
| Gatekeeper webhook | The controller-manager pods (three replicas by default in the Helm chart) that receive admission reviews and answer allow, warn or deny, and apply mutators |
| Policy engine | Embeds the OPA library to run Rego, and the Kubernetes CEL engine for CEL-based templates |
| Policy resources | ConstraintTemplate and constraint objects stored in the cluster; the engine reads them as rules and parameters |
| Policy repository | The Git repository, reviewed and tested, from which GitOps applies templates and constraints |
| Audit | A separate deployment that lists existing resources on an interval (60 seconds by default) and evaluates them with the same constraints |
| Results | Violations in each constraint’s status, JSON audit logs and Prometheus metrics |
Design principle. Policy is data in the cluster, and the engine is generic. Gatekeeper’s authors write a template once, with Rego in it, and platform teams then create many constraints from it with different parameters and scopes, so most users never edit Rego.
From a tested policy to a decision and an audit report
- Test. The author writes a template and test cases, and
gator verifychecks them in CI. - Apply. The template and a constraint are applied to the cluster, with the constraint first in
dryrunorwarnmode. - Create. A user or pipeline creates or updates a resource.
- Review. The Kubernetes API server calls the Gatekeeper webhook with an admission review.
- Evaluate. The webhook evaluates the object against the matching constraints. The result is allow, allow with warnings, or deny with the violation messages.
- Audit. On its interval, the audit loop evaluates existing resources against the same constraints and records violations in the constraint status.
Recap: test before applying, observe before enforcing, and let audit find what the webhook never saw.
Rego in ten minutes
Rego looks unusual at first because it is not a procedural language. Three ideas cover most of what you need.
Rules are conditions that must all hold. Every expression in a rule body is a condition, and the rule is true only if all of them are true. For a simple rule in Rego v1 syntax:
package example
default allow := false
allow if {
input.method == "GET"
input.user.role == "reader"
}
default supplies a value when no rule body matches, so allow is false unless the conditions hold.
Sets and objects are built from rules. A rule can contribute elements to a set, which is how Gatekeeper’s violation works:
package example
deny contains msg if {
some container in input.spec.containers
endswith(container.image, ":latest")
msg := sprintf("container %v uses the latest tag", [container.name])
}
some container in ... iterates, and each iteration that satisfies the body adds one message. There is no loop to write and no return statement: the set holds every message for which the conditions held.
Undefined means false. A reference to a field that does not exist makes the expression undefined, so the rule body stops, and no error is thrown. This is why a missing label does not crash a policy, and also why a typo in a field name makes a rule silently never match. Tests catch that, which is the main reason to write them.
Other things you will meet: not for negation, every for “for all”, comprehensions such as {x | ...} to build sets, built-in functions (sprintf, startswith, regex.match, count) and with to override input in tests. The OPA documentation’s policy language page covers them in full, and the online Rego Playground lets you try policies against sample input.
OPA outside Kubernetes
Gatekeeper is one way to use OPA, and a short tour of the others helps explain why Rego is worth learning once:
- Server.
opa run --serverexposes a REST API. An application sendsinputtoPOST /v1/data/<path>and receives the decision. By default OPA binds tolocalhost, so exposing it takes a deliberate--addrsetting, and it should then be paired with TLS and authentication. - Library. In Go, OPA can be embedded in the application, which removes the network hop.
- WebAssembly. Policies can be compiled to Wasm and evaluated by any Wasm runtime.
- Bundles and decision logs. A running OPA can download policy and data bundles from a remote HTTP server and report decision logs and status, which is how fleets of OPA instances are managed.
- Envoy and other integrations. OPA has documented integrations for Envoy and Istio authorisation, Kafka, Docker and SSH authorisation, and Terraform and CI checks, using the same Rego policy format.
- CLI.
opa eval,opa test,opa fmtandopa buildare the daily tools for writing and checking policy.
Install Gatekeeper
Gatekeeper needs cluster-admin rights to install, because it registers webhooks and CRDs. Use Helm and pin a version.
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update
helm install gatekeeper gatekeeper/gatekeeper \
--namespace gatekeeper-system --create-namespace \
--version <chart-version>
Choose the chart version that matches the Gatekeeper release you have tested (the repository’s default branch carries a 3.24 beta chart, so read the chart’s README for the value names of your version). The alternative is applying the release manifest with kubectl apply -f. Then verify:
kubectl get pods -n gatekeeper-system
kubectl get crd | grep gatekeeper.sh
You should see the controller-manager pods and an audit pod, and CRDs for templates, constraints, mutators, config and providers. The chart’s defaults worth knowing:
| Helm value | Default | Meaning |
|---|---|---|
replicas |
3 |
Number of controller-manager (webhook) pods |
auditInterval |
60 |
Seconds between audit runs; 0 disables audit |
constraintViolationsLimit |
20 |
Maximum violations written to each constraint’s status |
validatingWebhookFailurePolicy |
Ignore |
What the API server does if the webhook is unreachable |
validatingWebhookTimeoutSeconds |
3 |
Webhook timeout |
mutatingWebhookFailurePolicy |
Ignore |
Same for the mutating webhook |
enableExternalData |
true |
Allows the external data feature |
enableK8sNativeValidation |
true |
Allows CEL templates and Validating Admission Policy integration |
logDenies |
false |
Log every denied request in the webhook log |
controllerManager.exemptNamespaces |
empty | Namespaces allowed to carry the ignore label |
A first policy: required labels
Start with a template, written here in Rego v1 syntax, which requires the code form of the target:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
code:
- engine: Rego
source:
version: "v1"
rego: |
package k8srequiredlabels
violation contains {"msg": msg, "details": {"missing_labels": missing}} if {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | some label in input.parameters.labels}
missing := required - provided
count(missing) > 0
msg := sprintf("you must provide labels: %v", [missing])
}
Applying a template makes Gatekeeper create a new CRD for the kind K8sRequiredLabels, with a parameters schema built from the template. The schema must be structural: every field needs a type, or the API server rejects the template. Now a constraint that uses it, in dry-run mode first:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: namespaces-need-owner
spec:
enforcementAction: dryrun
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
excludedNamespaces: ["kube-*", "gatekeeper-system"]
parameters:
labels: ["owner"]
The input.review.object is the object under review, and input.parameters is what the constraint supplied. Within a few audit intervals, kubectl get k8srequiredlabels namespaces-need-owner -o yaml shows status.violations listing every existing namespace without an owner label, and status.totalViolations shows the full count. Nothing has been blocked yet, which is the point.
The same idea in CEL
When a rule is a simple check on one object, the template can use Kubernetes’ CEL expression language through the K8sNativeValidation engine, with no Rego:
targets:
- target: admission.k8s.gatekeeper.sh
code:
- engine: K8sNativeValidation
source:
validations:
- expression: 'has(object.metadata.labels) && variables.params.labels.all(l, l in object.metadata.labels)'
messageExpression: '"missing required label, requires all of: " + variables.params.labels.join(", ")'
Gatekeeper evaluates CEL in the webhook, in the audit loop and in gator, and can generate native Kubernetes ValidatingAdmissionPolicy resources so the API server evaluates them in-process. Use CEL for simple checks, and Rego when you need loops over lists, referential data (other objects in the cluster), external data or helper libraries. If a template defines both engines, only the CEL one is evaluated, so define one per template.
Examples from the policy library
You rarely write common policies from scratch. The gatekeeper-library repository provides templates and sample constraints for allowed repositories, required labels, container limits, privileged containers, host networking, unique ingress hosts and the Pod Security Standards. The gator policy command (alpha, from v3.22) searches and installs them:
gator policy update
gator policy search labels
gator policy install --bundle pod-security-baseline --enforcement-action=warn
A bundle installs the templates and ready-made constraints, and --enforcement-action=warn is the safe way to start.
Match, parameters and scope
The match block of a constraint decides which requests it sees. All top-level matchers must hold:
| Matcher | Use |
|---|---|
kinds |
API groups and kinds to cover, such as Pod or Namespace |
scope |
Cluster, Namespaced or * |
namespaces, excludedNamespaces |
Names, with prefix globs such as kube-* |
labelSelector |
Select objects by their labels |
namespaceSelector |
Select by the labels of the containing namespace |
name |
One object name or a prefix glob |
Parameters are free-form within the template’s schema, so one template serves many teams: a constraint for the payments namespace can allow one registry, and another for the data namespace can allow two.
Audit before enforce
The safest rollout has four stages, and Gatekeeper supports each natively through enforcementAction:
dryrun. The constraint evaluates and records violations through audit, and the webhook never blocks. Read the violations, fix the real problems and decide which are exceptions.warn. The API response carries a warning thatkubectlprints, so users see the problem and nothing is blocked. This is where developers learn about the rule.deny. The default. The request is rejected with the violation messages. Switch to it per constraint when the audit is clean.scoped. For different behaviour per enforcement point, for examplewarnat admission butdenyingatorin CI. The audit point must be listed explicitly.
spec:
enforcementAction: scoped
scopedEnforcementActions:
- action: warn
enforcementPoints:
- name: "validation.gatekeeper.sh"
- action: deny
enforcementPoints:
- name: "gator.gatekeeper.sh"
Do not skip to deny for a cluster-wide constraint. A policy that blocks kube-system components or an operator’s own pods can stop a cluster from healing itself.
Exemptions
Exemptions come in layers, from narrow to broad, and the narrow ones are almost always right.
- Per constraint.
match.excludedNamespaces, label selectors ornameglobs inside the constraint itself. - Cluster-wide with the
Configresource. The resource must be namedconfigin thegatekeeper-systemnamespace. It excludes namespaces from specific processes (audit,webhook,sync,mutation-webhook, or*):
apiVersion: config.gatekeeper.sh/v1alpha1
kind: Config
metadata:
name: config
namespace: gatekeeper-system
spec:
match:
- excludedNamespaces: ["kube-*"]
processes: ["*"]
- excludedNamespaces: ["ci-sandbox"]
processes: ["webhook"]
- Skipping the webhook entirely. A namespace labelled
admission.gatekeeper.sh/ignorebypasses the webhook, but only if Gatekeeper was also told, with--exempt-namespace(or the HelmcontrollerManager.exemptNamespaces), that the namespace may carry the label. That second step exists so that permission to edit a namespace does not equal permission to exempt it from policy. These exempted resources are still audited unless excluded there too. - In the template. Rego can read parameters such as an
exemptImageslist, and thelibsmechanism supports shared helper functions, so one template handles a reviewed list of exceptions without code changes.
Record every exemption with the reason and an expiry in Git. Unreviewed exemptions are where policy programmes decay.
Mutation
Mutators change objects during admission, before validation sees the final result. They use their own resources and not templates:
apiVersion: mutations.gatekeeper.sh/v1
kind: Assign
metadata:
name: pull-always
spec:
applyTo:
- groups: [""]
kinds: ["Pod"]
versions: ["v1"]
match:
scope: Namespaced
kinds:
- apiGroups: ["*"]
kinds: ["Pod"]
excludedNamespaces: ["kube-system"]
location: "spec.containers[name:*].imagePullPolicy"
parameters:
assign:
value: Always
The four kinds are AssignMetadata (labels and annotations, with stricter rules), Assign (anything else), ModifySet (add or remove list entries such as container arguments) and AssignImage (change the registry, repository, tag or digest of an image). applyTo is required except for AssignMetadata, and it does not accept globs, because Gatekeeper uses it to detect mutators that disagree about a kind’s schema. Mutators are stable since Gatekeeper 3.10. Keep them few and deterministic: two mutators that keep changing each other’s output create confusing behaviour, and a mutator that changes what a validation constraint sees is hard to debug. The metric gatekeeper_mutator_conflicting_count reports conflicts.
External data
Some decisions need facts that do not live in the cluster: whether an image is signed, whether a tag resolves to a digest, a directory lookup. Rego’s http.send can call out, but Gatekeeper’s documentation warns that it lets template authors reach the in-cluster network and creates injection risks, and mutation cannot use it. The external data feature (beta) solves this with a registered Provider:
apiVersion: externaldata.gatekeeper.sh/v1beta1
kind: Provider
metadata:
name: image-check
spec:
url: https://image-check.provider-ns:8090/validate
timeout: 3
caBundle: <base64 CA certificate>
A template then calls external_data({"provider": "image-check", "keys": images}) and receives results and errors per key. Gatekeeper makes the calls over mutual TLS, handles caching and batching, and allows only registered hosts. Community providers exist for Ratify, Cosign and GitHub artifact attestations. Treat external data as a latency-sensitive dependency: for mutation, the effective timeout is capped by the remaining admission deadline, and a slow provider on the admission path can cause failures in any mode, and the audit-only enforcement point is a reasonable place for slow checks.
Referential policies and data replication
Some rules need other objects, for example “no two ingresses may use the same host”. Gatekeeper replicates chosen kinds into the policy engine, readable in Rego as data.inventory. Replication is configured with SyncSet resources (alpha) or the Config resource’s sync section:
apiVersion: syncset.gatekeeper.sh/v1alpha1
kind: SyncSet
metadata:
name: sync-ingresses
spec:
gvks:
- group: "networking.k8s.io"
version: "v1"
kind: "Ingress"
Replicated data costs memory in the Gatekeeper pods, so sync only the kinds a policy needs. Also note a Gatekeeper limitation: a referential rule at admission sees the cached state, which is eventually consistent, so two requests arriving at the same moment can both pass.
Testing policies
Policies are code and need tests at two levels.
Rego unit tests with opa test check the logic in isolation. Rules whose names start with test_ are run, and with input as {...} supplies the data:
package example_test
import data.example
test_latest_tag_denied if {
msgs := example.deny with input as {"spec": {"containers": [{"name": "web", "image": "shop/web:latest"}]}}
count(msgs) == 1
}
test_pinned_tag_allowed if {
msgs := example.deny with input as {"spec": {"containers": [{"name": "web", "image": "shop/web:1.4.2"}]}}
count(msgs) == 0
}
opa test . -v
opa fmt --write .
gator verify tests complete Gatekeeper policies: a template, a constraint and sample objects, with the exact result asserted. A suite file declares tests and cases:
kind: Suite
apiVersion: test.gatekeeper.sh/v1alpha1
tests:
- name: required-labels
template: template.yaml
constraint: constraint.yaml
cases:
- name: labelled
object: samples/namespace-with-owner.yaml
assertions:
- violations: no
- name: unlabelled
object: samples/namespace-without-owner.yaml
assertions:
- violations: yes
message: "you must provide labels"
gator verify ./...
gator test -f manifests/ -f policy/
gator test evaluates a set of manifests against templates and constraints and returns a non-zero exit status on violations, which suits a CI step that checks rendered Helm or Kustomize output before it reaches a cluster. Run both in the same pipeline that deploys the policies, so a template that does not compile never reaches the cluster. A case can also load an inventory of objects for referential rules.
Security
- Gatekeeper is a high-value target. It sees every admission request and can block them, so restrict who can change templates, constraints, mutators,
ConfigandProviderresources with RBAC, and manage them through reviewed Git changes. - Fail-open is the default. The webhook’s
failurePolicyisIgnore, so if Gatekeeper is down or unreachable, requests are admitted unchecked and audit has to catch the gap later. SettingFailcloses that hole but turns a Gatekeeper outage into a cluster-wide write outage, so only do it with three or more replicas, anti-affinity, a PodDisruptionBudget, correct resource limits, and namespace exemptions for the system components Gatekeeper itself needs. - Webhook scope. The default webhook skips namespaces carrying the
admission.gatekeeper.sh/ignorelabel. Control who can set that label, and keep the--exempt-namespacelist short. - Template authors are trusted. Rego in a template runs in the Gatekeeper process, and
http.send, where allowed, reaches the network. Prefer the Provider model for external calls, and do not let untrusted tenants create templates. - Supply chain. Gatekeeper images carry SBOM and SLSA provenance attestations from v3.12.0, and vulnerabilities are reported to the project’s security mailing list. Pin image digests and verify them.
- OPA server. If you run OPA as a service, bind it to localhost unless it must be reached, and then use TLS and authentication (
--authentication=tokenortls, and an authorisation policy), as the OPA security documentation describes. - Emergency recovery. If Gatekeeper blocks something essential, deleting the
gatekeeper-validating-webhook-configurationobject disables admission checks, and redeploying it restores them. Keep that step in your runbook and restrict who can do it.
Monitoring and day-2 operations
Metrics. Gatekeeper exposes Prometheus metrics, by default on port 8888. The ones worth alerting on:
| Metric | Why |
|---|---|
gatekeeper_violations |
Audited violations by severity; the trend should fall after rollout |
gatekeeper_audit_last_run_end_time |
A stale value means audit is failing or stuck |
gatekeeper_audit_duration_seconds |
Audit runtime growing toward the interval is a scaling signal |
gatekeeper_validation_request_count and gatekeeper_validation_request_duration_seconds |
Admission volume, outcomes and latency |
gatekeeper_mutation_request_count and gatekeeper_mutator_conflicting_count |
Mutation load and conflicts |
gatekeeper_constraint_templates, gatekeeper_constraints |
Policy inventory by status; template ingestion errors show here |
gatekeeper_sync, gatekeeper_watch_manager_watched_gvk |
Replication health and what is being watched |
gatekeeper_provider_error_count |
External data failures |
Logs and events. The audit pod writes JSON logs with an event_type of violation_audited. Enable logDenies to log each denial in the webhook, and the alpha --emit-admission-events flag to emit a Kubernetes event for each admission violation.
Sizing and tuning.
- Webhook and audit run in separate pods, so audit running out of memory does not affect admission.
- Cap
--max-serving-threadsfor large clusters; too many threads cause CPU throttling and jitter, and too few raise latency. - Audit memory grows with
--constraint-violations-limitand with listing large kinds. Reduce--audit-chunk-sizeto limit peak memory, and use--audit-match-kind-onlyso audit lists only the kinds that constraints match. - The default limit of 20 violations per constraint status exists because API objects have a size limit in etcd; values above about 500 are not recommended. For full violation data, use logs or the export feature.
- Keep templates efficient: avoid heavy nested iteration over large
inputcollections, and measure with the engine’s metrics.
Upgrades. Gatekeeper’s CRDs are installed with the chart, and upgrades should go through a staging cluster. Read the release notes for changes to defaults and deprecated flags. For example, the --sync-vap-enforcement-scope flag is documented as deprecated and planned for removal in v3.24, and the documentation warns that exemption behaviour for generated Validating Admission Policies changed, so some namespaces can start being denied after an upgrade.
Gatekeeper and OPA versions
Gatekeeper embeds the OPA library, so its Rego behaviour follows the OPA version in its release, and the repository’s opa-versions page tracks the pairing. Because Gatekeeper still defaults to v0 syntax in templates, a policy copied from a recent OPA example with if and contains needs version: "v1" under source, or it will fail to compile. Run gator verify after every Gatekeeper upgrade.
Gatekeeper compared with Kyverno
Both projects do admission control, audit and mutation, and both are mature. They differ in language and in where each reaches. (There is a separate brief on Kyverno in this series.)
| Gatekeeper with OPA | Kyverno | |
|---|---|---|
| Policy language | Rego (and CEL for simple checks) | YAML with pattern matching, and CEL in newer policy types |
| Learning curve | Steeper; Rego is a new paradigm, but a library covers common rules | Gentler for Kubernetes users, since policies look like Kubernetes resources |
| Reuse beyond Kubernetes | Strong: the same Rego runs in Envoy, CI, Terraform checks and services | Kubernetes-focused, with a JSON policy engine for other documents |
| Validate and mutate | Yes, with separate resource kinds | Yes, in one policy format |
| Generate resources | No Kyverno-style generation of other resources; it validates and mutates | Yes, a core feature, with clean-up policies |
| Image verification | Through external data providers such as Ratify or Cosign | Built in, with Cosign and Notation |
| Audit | Constraint status, logs and metrics | Policy reports in the Policy Report format |
| Testing | opa test and gator verify |
The Kyverno CLI |
| Complex logic | Rego handles joins, comprehensions and referential data well | Possible, but less natural for deep logic |
A practical rule: pick Kyverno when your team lives in Kubernetes YAML and wants generation and built-in image verification with low overhead. Pick Gatekeeper when you want one policy language across Kubernetes and other systems, when you have complex referential logic, or when your organisation already uses OPA. Many clusters also use Kubernetes’ built-in ValidatingAdmissionPolicy for the simple checks, and Gatekeeper can manage and generate those too.
Usage cases
- Baseline guardrails. Required labels, approved registries, resource limits, no privileged containers and no host networking, rolled out as
dryrun, thenwarn, thendeny. - Uniqueness and referential rules. Ingress hosts or namespace names that must not collide, using replicated data.
- Defaults through mutation. Adding labels, setting
imagePullPolicyor routing images to an internal registry. - Supply-chain checks. Image signature and attestation checks through an external data provider.
- Shift-left.
gatorin CI checks manifests against the same constraints before merge, and OPA checks Terraform plans and other configuration. - Compliance evidence. Audit results as a continuous report of which resources violate which rule.
The Sprint 4 pharmaceutical plant project uses Gatekeeper to put admission guardrails on its Kubernetes clusters on Google Cloud.
Project status
OPA is a CNCF graduated project with a steady release cadence; v1.22.0 is the latest tag as of 29 October 2026, and the 1.x line changed Rego’s default syntax, so old policies needed a migration. Its changelog shows ongoing work, including breaking changes to how bundles are rooted at their manifest file in the unreleased section, so read the release notes before upgrading a fleet. Recent Gatekeeper releases are v3.23.0 on 9 July 2026, v3.23.1 on 27 August 2026 and a v3.24 beta tagged on 13 July 2026. Recent Gatekeeper work has centred on CEL and Validating Admission Policy integration, with several features still alpha or beta (SyncSet, Config, gator policy, violation export and external data), so check each feature’s stated state before you depend on it.
Adoption
OPA’s ADOPTERS.md lists organisations that have spoken publicly about their use or added themselves. Entries include 2U, Appsflyer (authorisation for hundreds of microservices), Atlassian (API authorisation, with decision logs), bol.com (admission control on its Kubernetes clusters), BNY Mellon (sidecar authorisation) and Capital One (Kubernetes admission policies), among many others, with uses ranging from admission control to microservice authorisation and CI checks. These descriptions are the organisations’ own, collected in the project file. For Gatekeeper specifically, the project documentation and library are the best public record, and its adopters are not listed separately.
Alternatives
| Solution | Model | Best suited to |
|---|---|---|
| Gatekeeper with OPA | Admission controller with Rego and CEL constraint templates | Teams that want one policy language across Kubernetes and other systems |
| Kyverno | Kubernetes-native policies written in YAML or CEL | Kubernetes teams that want low-friction policies, generation and image verification |
| Kubernetes ValidatingAdmissionPolicy | Built-in CEL rules evaluated inside the API server | Simple checks with no extra components to run |
| Pod Security Admission | Built-in enforcement of the Pod Security Standards | Basic pod hardening without custom policy |
| Kubewarden | Policies compiled to WebAssembly | Teams that want to write policies in a general-purpose language |
| Plain OPA with kube-mgmt | OPA sidecar with a replication helper | Teams that need OPA’s bundle and decision-log management features, as an alternative the OPA documentation itself describes for that case |
Strengths and limitations
| Strengths | Limitations |
|---|---|
| One expressive language for Kubernetes, CI, services and gateways | Rego has a real learning curve, and undefined-means-false hides typos |
| Templates and constraints separate policy logic from policy use | Fail-open by default; failing closed needs careful availability work |
| Audit, dry-run and warn make safe rollout natural | Several features are alpha or beta, and defaults change between releases |
| Mutation, external data and referential rules in one tool | Referential data costs memory and is eventually consistent |
opa test, gator and a shared policy library for testing and reuse |
Not a generate-resources tool, and image verification needs extra components |
Recommendation
Adopt Gatekeeper where you want policy-as-code across more than Kubernetes, or where your policies need real logic. Start with the library, install it with three replicas, and run every new constraint as dryrun, then warn, then deny, with tests in CI through opa test and gator verify. Exempt system namespaces deliberately, keep exemptions in Git, decide the fail-open question in writing with availability measures behind it, and alert on audit freshness and webhook latency. If your needs are simple and your team prefers YAML, evaluate Kyverno or the built-in ValidatingAdmissionPolicy first, and use OPA directly for non-Kubernetes decisions where Rego pays off.
References: OPA documentation · Rego policy language · Policy testing · OPA security · OPA adopters · Gatekeeper documentation · Gatekeeper install · How to use Gatekeeper · ConstraintTemplates · Audit · Exempting namespaces · Mutation · External data · The gator CLI · Failing closed · Gatekeeper metrics · Gatekeeper policy library

