Admission control is the last checkpoint before a workload lands in your cluster, and for most teams it's wide open. A developer applies a manifest with a privileged container, a latest tag, or no resource limits, and Kubernetes happily creates it. Retrofitting security after the fact means chasing down misconfigurations that should never have been scheduled in the first place. This is the gap OPA Gatekeeper Kubernetes admission control closes: it intercepts every create and update request at the API server and rejects (or audits) anything that violates your organization's policy.
In this guide you'll install Gatekeeper, write and deploy your first constraint template, enforce a real-world policy (blocking privileged containers and requiring resource limits), verify it's working, and understand how to roll changes out safely with audit mode before you flip to hard enforcement. By the end you'll have a working policy-as-code Kubernetes pipeline you can extend to cover image provenance, registry allowlists, and label standards.
Step 1: Understand How OPA Gatekeeper Kubernetes Admission Control Works
Gatekeeper is a Kubernetes-native policy controller built on top of Open Policy Agent (OPA), the general-purpose policy engine used across cloud-native infrastructure. Rather than embedding raw Rego policy directly into your cluster, Gatekeeper wraps OPA in Kubernetes CRDs — ConstraintTemplate and Constraint — and registers a ValidatingAdmissionWebhook (and optionally a MutatingAdmissionWebhook) that the API server calls on every relevant request.
The flow looks like this:
- A user or CI pipeline runs
kubectl apply. - The API server sends the object to Gatekeeper's webhook before persisting it to etcd.
- Gatekeeper evaluates the object against every active Constraint.
- If any Constraint's Rego logic returns a violation, the request is denied (or logged, depending on enforcement mode).
This is the core mental model for anyone working through an open policy agent kubernetes tutorial: OPA provides the policy evaluation engine, Gatekeeper provides the Kubernetes integration, CRDs, and audit tooling on top of it.
Step 2: Install Gatekeeper in Your Cluster
Install the latest stable release directly from the official manifest. Use Helm if you already manage cluster add-ons that way, since it makes upgrades and configuration overrides easier to track.
# Direct manifest install
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-3.16/deploy/gatekeeper.yaml
# Or via Helm
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update
helm install gatekeeper/gatekeeper --name-template=gatekeeper \
--namespace gatekeeper-system --create-namespace
Confirm the controller and audit pods are running before moving on:
kubectl get pods -n gatekeeper-system
You should see gatekeeper-controller-manager and gatekeeper-audit pods in a Running state. The controller pods handle the admission webhook calls; the audit pod periodically scans existing cluster objects against your constraints so you can catch violations that predate a policy, not just new ones.
Step 3: Write a Gatekeeper Constraint Template
Gatekeeper constraint templates define reusable policy logic in Rego, then expose a schema so the policy can be parameterized without touching code again. Here's a template that blocks privileged containers:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sblockprivileged
spec:
crd:
spec:
names:
kind: K8sBlockPrivileged
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sblockprivileged
violation[{"msg": msg}] {
c := input.review.object.spec.containers[_]
c.securityContext.privileged
msg := sprintf("Privileged container is not allowed: %v", [c.name])
}
Apply it:
kubectl apply -f block-privileged-template.yaml
This step is the heart of policy as code kubernetes practice: the Rego logic lives in version control, gets reviewed like any other code change, and is deployed the same way as the rest of your infrastructure.
Step 4: Create a Constraint from the Template
The ConstraintTemplate defines the logic; a Constraint activates it against a specific scope. This separation is what lets platform teams write one template and let different teams apply it with different parameters or namespace scopes.
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sBlockPrivileged
metadata:
name: block-privileged-containers
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
excludedNamespaces:
- "kube-system"
- "gatekeeper-system"
enforcementAction: dryrun
Apply it with kubectl apply -f block-privileged-constraint.yaml. Note enforcementAction: dryrun — this is deliberate and covered next.
Step 5: Roll Out in Dry-Run / Audit Mode First
Never flip a new constraint straight to deny in a live cluster. Start with dryrun (or omit enforcementAction, which defaults to deny — so set it explicitly) so Gatekeeper logs violations without blocking anything. Check what would have been rejected:
kubectl get k8sblockprivileged block-privileged-containers -o yaml
Look at status.violations in the output. This shows every existing object in scope that fails the policy, which is exactly what you want to see before enforcement goes live — it tells you how much remediation work is ahead of you and prevents an outage caused by a policy you just wrote.
Step 6: Enforce and Add a Resource-Limits Policy
Once the violation list is empty (or accepted and remediated), switch enforcement on:
kubectl patch k8sblockprivileged block-privileged-containers \
--type=merge -p '{"spec":{"enforcementAction":"deny"}}'
Layer in a second, equally common policy — requiring CPU/memory limits — using the same template/constraint pattern:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredresources
spec:
crd:
spec:
names:
kind: K8sRequiredResources
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredresources
violation[{"msg": msg}] {
c := input.review.object.spec.containers[_]
not c.resources.limits.cpu
msg := sprintf("Container %v is missing CPU limits", [c.name])
}
Most teams end up maintaining a small library of these templates for privileged mode, host networking, image registries, runAsNonRoot, and required labels — this is where community template libraries like gatekeeper-library save significant time over writing every policy from scratch.
Step 7: Verify and Test Your Policies
Confirm enforcement is actually working by attempting to deploy a violating pod:
kubectl run test-privileged --image=nginx --overrides='
{
"spec": {
"containers": [{
"name": "test-privileged",
"image": "nginx",
"securityContext": { "privileged": true }
}]
}
}' --dry-run=server
With enforcementAction: deny, this should fail with an admission webhook error referencing your constraint's message. If it doesn't:
- Check the webhook is registered:
kubectl get validatingwebhookconfigurations gatekeeper-validating-webhook-configuration. - Check namespace exclusions: Gatekeeper skips
kube-systemand its own namespace by default; confirm your test namespace isn't accidentally excluded. - Check the ConstraintTemplate compiled: run
kubectl describe constrainttemplate k8sblockprivilegedand look forCreated: truein the status; Rego syntax errors surface here. - Check constraint status for stale sync:
kubectl get constraintsacross all kinds showsenforcementActionand whether the constraint status is populated — an empty status usually means the audit controller hasn't run yet. - Webhook timeouts: if the API server logs show admission timeouts under load, increase
failurePolicyhandling and webhook timeout settings in the Gatekeeper deployment, and check controller pod resource limits.
How Safeguard Helps
Gatekeeper gives you the enforcement mechanism, but writing, testing, and maintaining a comprehensive constraint library across every cluster is an ongoing engineering effort — and gaps in coverage are exactly where supply chain risk slips through admission control. Safeguard extends this foundation by continuously scanning your Kubernetes manifests, Helm charts, and CI/CD pipelines for the misconfigurations Gatekeeper policies are meant to catch, so you can validate policy-as-code Kubernetes coverage before it ever reaches a live admission webhook.
Safeguard maps findings back to the specific constraint templates that should be catching them, flags clusters where enforcement is still stuck in dry-run, and correlates admission control gaps with the artifacts and images moving through your software supply chain — from build to registry to runtime. Instead of treating Gatekeeper as a standalone control, Safeguard gives security and platform teams a single view of policy drift, unenforced constraints, and the upstream risks (vulnerable base images, unsigned artifacts, risky registries) that admission control alone won't stop. That combination turns Gatekeeper from a point solution into part of a verifiable, end-to-end supply chain security posture.