OPA Gatekeeper: Policy Enforcement in Kubernetes (2026)

OPA Gatekeeper is a Kubernetes admission controller that enforces policies written in Rego — the Open Policy Agent query language. It lets platform teams define guardrails (required labels, restricted registries, resource limit requirements, privilege restrictions) that are enforced at admission time, before any resource is accepted into the cluster.

How Gatekeeper Works

Gatekeeper operates as a Kubernetes ValidatingAdmissionWebhook. When you create or update a resource, the API server calls the Gatekeeper webhook before persisting the object. Gatekeeper evaluates the resource against all active Constraints and either allows or denies the admission request.

The policy model has two layers:

  • ConstraintTemplate — defines a new CRD kind and the Rego logic that evaluates resources against that policy. Think of it as a policy class.
  • Constraint — an instance of a ConstraintTemplate that specifies which resources to target (by kind, namespace, label selector) and any parameters the policy needs. Think of it as applying the policy class with specific arguments.
Rego: Rego is a declarative query language from OPA. A Rego policy expresses what is forbidden — if the policy body is true, the resource is denied with the provided message. You do not write allow rules in Gatekeeper; you write deny conditions.

Installing Gatekeeper

# Install via manifest
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/v3.15.0/deploy/gatekeeper.yaml

# Or via Helm
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm install gatekeeper gatekeeper/gatekeeper \
  --namespace gatekeeper-system \
  --create-namespace

# Verify
kubectl get pods -n gatekeeper-system
# NAME                                             READY   STATUS
# gatekeeper-audit-7d6f9b8c5-x4qpv                1/1     Running
# gatekeeper-controller-manager-5c8b7f9d6-m2lrp   1/1     Running
# gatekeeper-controller-manager-5c8b7f9d6-n3kqs   1/1     Running

ConstraintTemplate: Writing Rego Policies

A ConstraintTemplate defines the CRD kind and the Rego evaluation logic:

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: requirelabels
spec:
  crd:
    spec:
      names:
        kind: RequireLabels        # New CRD kind this template creates
      validation:
        openAPIV3Schema:
          type: object
          properties:
            labels:
              type: array
              items:
                type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package requirelabels

        violation[{"msg": msg}] {
          provided := {label | input.review.object.metadata.labels[label]}
          required := {label | label := input.parameters.labels[_]}
          missing := required - provided
          count(missing) > 0
          msg := sprintf("Missing required labels: %v", [missing])
        }

When you apply this ConstraintTemplate, Gatekeeper creates a new CRD called RequireLabels that you can then instantiate as a Constraint.

Constraint: Applying Policies

A Constraint is an instance of a ConstraintTemplate that specifies the scope and parameters:

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: RequireLabels
metadata:
  name: require-app-labels
spec:
  match:
    kinds:
      - apiGroups: ["apps"]
        kinds: ["Deployment"]
    namespaces:
      - production
      - staging
  parameters:
    labels:
      - app
      - version
      - team

After applying this, any Deployment in the production or staging namespace that is missing the app, version, or team label will be rejected at admission:

Error from server ([require-app-labels] Missing required labels: {"team","version"}):
error when creating "deploy.yaml": admission webhook "validation.gatekeeper.sh" denied the request

Common Policy Examples

Restrict Container Registries

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: allowedregistries
spec:
  crd:
    spec:
      names:
        kind: AllowedRegistries
      validation:
        openAPIV3Schema:
          type: object
          properties:
            registries:
              type: array
              items:
                type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package allowedregistries

        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not starts_with_allowed(container.image)
          msg := sprintf("Container %v uses disallowed registry: %v", [container.name, container.image])
        }

        starts_with_allowed(image) {
          registry := input.parameters.registries[_]
          startswith(image, registry)
        }

---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: AllowedRegistries
metadata:
  name: prod-registry-restriction
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
  parameters:
    registries:
      - "gcr.io/my-company/"
      - "123456789.dkr.ecr.us-east-1.amazonaws.com/"

Require Resource Limits

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: requireresourcelimits
spec:
  crd:
    spec:
      names:
        kind: RequireResourceLimits
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package requireresourcelimits

        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not container.resources.limits.cpu
          msg := sprintf("Container %v missing CPU limit", [container.name])
        }

        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not container.resources.limits.memory
          msg := sprintf("Container %v missing memory limit", [container.name])
        }

Block Privileged Containers

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: noprivilegedcontainers
spec:
  crd:
    spec:
      names:
        kind: NoPrivilegedContainers
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package noprivilegedcontainers

        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          container.securityContext.privileged == true
          msg := sprintf("Privileged containers are not allowed: %v", [container.name])
        }

Audit Mode

Gatekeeper's audit controller periodically evaluates all existing resources against active Constraints and records violations — without blocking anything. This is invaluable for:

  • Discovering policy violations that existed before Gatekeeper was installed.
  • Rolling out a new policy in audit-only mode before enforcing it.
  • Continuous compliance reporting.
# Check audit violations on a Constraint
kubectl describe requirelabels require-app-labels
# Status:
#   Audit Timestamp:  2026-06-11T10:00:00Z
#   By Pod:
#     ...
#   Total Violations: 3
#   Violations:
#     Enforcement Action: deny
#     Kind:               Deployment
#     Message:            Missing required labels: {"team"}
#     Name:               legacy-api
#     Namespace:          production

Set a Constraint to audit-only (warn but not block) during rollout:

spec:
  enforcementAction: warn    # Options: deny (default), warn, dryrun

Mutations

Gatekeeper also supports mutations — automatically modifying resources at admission time before they are persisted. Mutations are defined with AssignMetadata, Assign, and ModifySet resources:

# Automatically add a default label to all Pods
apiVersion: mutations.gatekeeper.sh/v1
kind: AssignMetadata
metadata:
  name: add-team-label
spec:
  match:
    scope: Namespaced
    kinds:
      - apiGroups: ["*"]
        kinds: ["Pod"]
  location: "metadata.labels.managed-by"
  parameters:
    assign:
      value: gatekeeper
Mutations vs. Validation: Use mutations to inject defaults or annotations automatically. Use validation (Constraints) to enforce requirements that must be explicitly set by the workload owner.

Gatekeeper Policy Library

The Gatekeeper Policy Library provides a set of ready-to-use ConstraintTemplates covering common security and operational requirements:

  • PSP-equivalent policies — block privileged containers, require read-only root filesystem, restrict host namespaces.
  • Image verification — require image digests, block :latest tag.
  • Networking — require NetworkPolicy, restrict external IPs.
  • Operations — require Pod disruption budgets, require probes, enforce naming conventions.
# Install from the library
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/library/general/requiredlabels/template.yaml

Best Practices

  • Start in audit mode — deploy all new Constraints with enforcementAction: warn first. Review violations, fix workloads, then switch to deny.
  • Exclude system namespaces — add excludedNamespaces: [kube-system, gatekeeper-system, cert-manager] to avoid blocking critical cluster infrastructure.
  • Test Rego locally — use opa eval or the OPA Playground to validate your Rego before deploying. Broken Rego in a ConstraintTemplate can block all admissions.
  • Use the policy library — don't write from scratch what the library already covers. The library templates are well-tested and follow best practices.
  • Monitor webhook latency — Gatekeeper adds latency to every API admission call. If the webhook is unavailable, the failurePolicy determines cluster behavior (Ignore vs. Fail). Use Ignore to prevent cluster outages if Gatekeeper is down.

FAQ

What replaced PodSecurityPolicy?
PodSecurityPolicy was deprecated in Kubernetes 1.21 and removed in 1.25. Its replacements are: Kubernetes Pod Security Admission (PSA, built-in) for standard security profiles, and OPA Gatekeeper for custom policies beyond what PSA covers.
Can Gatekeeper check cross-resource constraints?
Yes, via the data.inventory feature (referential constraints). This lets a policy inspect other cluster resources — for example, checking that an Ingress host doesn't conflict with an existing Ingress.
How does Gatekeeper differ from Kyverno?
Kyverno uses YAML-native policy syntax instead of Rego. If your team prefers not to learn Rego, Kyverno is a strong alternative with similar capabilities. Gatekeeper's Rego is more expressive for complex logic.
← Kubernetes Hub
Get Weekly Tech Insights

Join 5,000+ engineers getting the best Kubernetes, Java, Cloud and AI articles every week.