---
title: "OPA Gatekeeper: Policy as Code for Kubernetes"
description: "How to use OPA Gatekeeper to define, test, and enforce Kubernetes policies using ConstraintTemplates, Constraints, and audit mode."
url: https://agent-zone.ai/knowledge/kubernetes/opa-gatekeeper-policy/
section: knowledge
date: 2026-02-21
categories: ["kubernetes"]
tags: ["opa","gatekeeper","policy","security","admission-control","rego"]
skills: ["policy-as-code","rego-development","constraint-management","security-hardening"]
tools: ["kubectl","helm","gatekeeper"]
levels: ["intermediate"]
word_count: 1056
formats:
  json: https://agent-zone.ai/knowledge/kubernetes/opa-gatekeeper-policy/index.json
  html: https://agent-zone.ai/knowledge/kubernetes/opa-gatekeeper-policy/?format=html
  api: https://api.agent-zone.ai/api/v1/knowledge/search?q=OPA+Gatekeeper%3A+Policy+as+Code+for+Kubernetes
---


# OPA Gatekeeper: Policy as Code for Kubernetes

Gatekeeper is a Kubernetes-native policy engine built on Open Policy Agent (OPA). It runs as a validating admission webhook and evaluates policies written in Rego against every matching API request. Instead of deploying raw Rego files to an OPA server, Gatekeeper uses Custom Resource Definitions: you define policies as ConstraintTemplates and instantiate them as Constraints. This makes policy management declarative, auditable, and version-controlled.

## Architecture

Gatekeeper deploys as a set of pods in the `gatekeeper-system` namespace. It registers a ValidatingWebhookConfiguration with the API server so that matching requests are forwarded to Gatekeeper for evaluation. Gatekeeper also runs a periodic audit process that checks existing resources against all active constraints, catching resources that were created before a policy was deployed.

The two core CRDs are:

- **ConstraintTemplate** -- defines the Rego logic and the schema for parameters. Think of it as a policy class.
- **Constraint** -- an instance of a ConstraintTemplate with specific parameters and scope. Think of it as a policy instance.

## Installation

Install Gatekeeper via Helm:

```bash
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 \
  --set replicas=2 \
  --set audit.resources.requests.memory=256Mi \
  --set audit.resources.limits.memory=512Mi
```

Gatekeeper requires roughly 500MB of RAM for the audit controller, more in large clusters. Set resource requests accordingly or the audit pod will get OOMKilled.

Verify the installation:

```bash
kubectl get pods -n gatekeeper-system
kubectl get crd | grep gatekeeper
```

You should see `constrainttemplates.templates.gatekeeper.sh` and related CRDs.

## ConstraintTemplate: Defining Policy Logic

A ConstraintTemplate defines the Rego code and declares what parameters the policy accepts:

```yaml
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
    rego: |
      package k8srequiredlabels

      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])
      }
```

The `spec.crd` section defines a CRD schema. When you apply this template, Gatekeeper creates a new CRD called `K8sRequiredLabels` in the cluster. The `spec.targets[].rego` section contains the policy logic. The `violation` rule is the standard convention -- any result from `violation` means the resource fails the check.

Inside the Rego code, `input.review.object` is the Kubernetes resource being evaluated, and `input.parameters` contains the values from the Constraint instance.

## Constraint: Instantiating a Policy

Once the ConstraintTemplate is applied, create a Constraint using the generated CRD:

```yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: require-team-label
spec:
  enforcementAction: deny
  match:
    kinds:
    - apiGroups: ["apps"]
      kinds: ["Deployment"]
    - apiGroups: [""]
      kinds: ["Service"]
    namespaces: ["production", "staging"]
    excludedNamespaces: ["kube-system", "gatekeeper-system"]
  parameters:
    labels:
    - "team"
    - "environment"
```

Key fields:

- **enforcementAction** -- `deny` blocks non-compliant resources, `dryrun` audits only (allows but records violations), `warn` allows but returns a warning to the user.
- **spec.match.kinds** -- which resource types to evaluate. Be specific to avoid unnecessary overhead.
- **spec.match.namespaces / excludedNamespaces** -- scope the constraint. Always exclude `kube-system` and `gatekeeper-system`.
- **spec.parameters** -- values passed to the Rego code via `input.parameters`.

## Practical Example: Require Resource Limits

Here is a complete example that requires all pods to have CPU and memory limits set, deployed first in dryrun mode:

```yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sresourcelimits
spec:
  crd:
    spec:
      names:
        kind: K8sResourceLimits
      validation:
        openAPIV3Schema:
          type: object
          properties:
            containerExemptions:
              type: array
              items:
                type: string
  targets:
  - target: admission.k8s.gatekeeper.sh
    rego: |
      package k8sresourcelimits

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

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

      is_exempt(name) {
        exemptions := input.parameters.containerExemptions
        exemptions[_] == name
      }
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sResourceLimits
metadata:
  name: require-resource-limits
spec:
  enforcementAction: dryrun
  match:
    kinds:
    - apiGroups: [""]
      kinds: ["Pod"]
    excludedNamespaces: ["kube-system", "gatekeeper-system"]
  parameters:
    containerExemptions:
    - "istio-proxy"
```

Start with `enforcementAction: dryrun`, then check for violations:

```bash
kubectl get k8sresourcelimits require-resource-limits -o yaml
```

Look at the `status.violations` section. It lists every existing resource that violates the constraint:

```bash
kubectl get k8sresourcelimits require-resource-limits \
  -o jsonpath='{range .status.violations[*]}{.namespace}/{.name}: {.message}{"\n"}{end}'
```

Once you have fixed all violations, switch to enforcement:

```bash
kubectl patch k8sresourcelimits require-resource-limits \
  --type=merge -p '{"spec":{"enforcementAction":"deny"}}'
```

## Common Policies from the Gatekeeper Library

The [gatekeeper-library](https://github.com/open-policy-agent/gatekeeper-library) repository provides pre-built ConstraintTemplates for common policies:

- **K8sBlockNodePort** -- block Services of type NodePort
- **K8sContainerLimits** -- require resource limits on containers
- **K8sDisallowedTags** -- block the `latest` tag on container images
- **K8sPSPPrivilegedContainer** -- block privileged containers
- **K8sAllowedRepos** -- restrict which container registries are allowed
- **K8sPSPRunAsNonRoot** -- require containers to run as non-root
- **K8sRequiredProbes** -- require readiness/liveness probes

Use these as starting points rather than writing Rego from scratch.

## Mutation Support

Gatekeeper also supports mutating resources via dedicated CRDs:

- **Assign** -- set a field value on matching resources
- **AssignMetadata** -- add labels or annotations
- **ModifySet** -- add or remove items from a list (e.g., add a toleration)

```yaml
apiVersion: mutations.gatekeeper.sh/v1
kind: AssignMetadata
metadata:
  name: add-environment-label
spec:
  match:
    scope: Namespaced
    kinds:
    - apiGroups: ["*"]
      kinds: ["Pod"]
    namespaces: ["production"]
  location: "metadata.labels.environment"
  parameters:
    assign:
      value: "production"
```

## Gatekeeper vs Kyverno

Both tools solve the same problem. The key differences:

- **Gatekeeper** uses Rego for policy logic. Rego is powerful and expressive but has a learning curve. Gatekeeper has a mature audit system and a large library of pre-built policies.
- **Kyverno** defines policies entirely in YAML -- no separate policy language. This is simpler to learn and fits naturally into GitOps workflows. However, complex logic is harder to express in YAML than in Rego.

For teams already using OPA elsewhere (API gateways, CI pipelines), Gatekeeper is the natural choice. For teams that want to get started quickly with simple policies, Kyverno has a lower barrier to entry.

## Common Gotchas

**Exempt system namespaces.** Always exclude `kube-system` and `gatekeeper-system` from constraints. If Gatekeeper blocks its own pods from starting, you have a deadlock that requires editing the webhook configuration directly.

**No constraint ordering.** All constraints are evaluated independently. You cannot say "run policy A before policy B." If two constraints conflict, both violations are reported. Design constraints to be independent of each other.

**Audit lag.** The audit controller runs on an interval (default 60 seconds). After deploying a new constraint in dryrun mode, wait at least one audit cycle before checking `status.violations`.

**Rego debugging.** When Rego rules do not work as expected, use `kubectl describe constrainttemplate <name>` to check for compilation errors. The status section will show Rego syntax errors. Test Rego logic locally with `opa eval` before deploying to the cluster.

