Search across all documentation pages
Run policy on only the YAML a PR changed, and make the job a required check.
conftest test (Rego) or kyverno apply (dry-run) against them.A GitHub Actions workflow that scopes to changed YAML and runs both engines.
# .github/workflows/policy.yaml
name: policy
on:
pull_request:
paths: ["overlays/**", "base/**", "platform/**"]
jobs:
policy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: List changed manifests
id: changed
run: |
FILES=$(git diff --name-only origin/${{ github.base_ref }}... \
-- '*.yaml' '*.yml' | tr '\n' ' ')
echo "files=$FILES" >> "$GITHUB_OUTPUT"
- name: conftest
if: steps.changed.outputs.files != ''
run: |
curl -sL -o conftest.tar.gz \
https://github.com/open-policy-agent/conftest/releases/latest/download/conftest_Linux_x86_64.tar.gz
tar xf conftest.tar.gz && sudo mv conftest /usr/local/bin/
conftest test --policy policy/ ${{ steps.changed.outputs.files }}
- name: kyverno dry-run
if: steps.changed.outputs.files != ''
run: |
curl -sL -o kyverno.tar.gz \
https://github.com/kyverno/kyverno/releases/latest/download/kyverno-cli_linux_x86_64.tar.gz
tar xf kyverno.tar.gz && sudo mv kyverno /usr/local/bin/
kyverno apply policy/kyverno/ \
--resource ${{ steps.changed.outputs.files }} \
--warn-exit-code 0 --detailed-resultsA minimal Rego policy that rejects containers running as root.
# policy/security.rego
package main
deny[msg] {
input.kind == "Deployment"
c := input.spec.template.spec.containers[_]
not c.securityContext.runAsNonRoot
msg := sprintf("container %q must set runAsNonRoot: true", [c.name])
}The equivalent Kyverno policy, reused from what the cluster admits.
# policy/kyverno/require-non-root.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-non-root
spec:
validationFailureAction: Enforce
rules:
- name: run-as-non-root
match:
any:
- resources:
kinds: [Deployment]
validate:
message: "containers must set runAsNonRoot: true"
pattern:
spec:
template:
spec:
containers:
- securityContext:
runAsNonRoot: trueRunning policy against the whole repo on every PR is slow and noisy.
It also surfaces pre-existing violations the author did not touch, which stalls unrelated work.
Diffing against the base ref limits the check to what the PR actually changes.
fetch-depth: 0 is required so the runner has the base history to diff against.
Conftest runs Rego and is engine-agnostic; it happily tests any YAML or JSON.
It is a good fit when your policy library is already Rego, or when you gate non-Kubernetes config too.
Kyverno's CLI runs the exact ClusterPolicy objects your admission controller enforces.
Running kyverno apply in the PR is a true dry-run of production admission, so there is no policy drift between CI and cluster.
Many platforms run both: Kyverno for parity with the cluster, conftest for broader repo hygiene.
A green or red check does nothing unless branch protection requires it.
Add the job's context name to required_status_checks.contexts on the protected branch.
required_status_checks:
strict: true
contexts: [policy, kubeconform]Now a policy violation blocks merge, and the reconciler never sees the bad manifest.
If you use Kustomize or Helm, test the rendered output, not the templates.
Policies match on final Kubernetes objects, so render first, then pipe into the engine.
kubectl kustomize overlays/prod | conftest test --policy policy/ -Testing raw Helm or Kustomize sources gives false results.
The policy sees templates, not the objects the cluster gets; always render first.
git diff with the wrong base misses files or flags everything.
Use origin/${{ github.base_ref }}... (three dots) and fetch-depth: 0 so the merge base is correct.
Warnings that exit non-zero break PRs for advisory rules.
Use --warn-exit-code 0 in Kyverno so Audit-level rules inform without blocking.
Policy in the PR is not a substitute for admission control.
A PR check only sees the repo; enforce the same policies in-cluster so out-of-band kubectl apply is still caught.
Deleted files can crash the run.
Filter the diff to existing paths, or the engine errors on a path that no longer exists.
Use the in-cluster Kyverno admission webhook only.
Simplest to run, but feedback comes at deploy time, not in the PR, which is slower for authors.
Use OPA Gatekeeper with gator test in CI.
Strong if you have standardized on Gatekeeper constraints; gator gives the same PR-time dry-run as Kyverno CLI.
Use a hosted policy platform's PR app.
Less to maintain and nice dashboards, but it adds a vendor and a token with repo access.
Use a pre-commit hook for conftest.
Fastest local feedback, but easily bypassed, so keep the required CI check as the real gate.
Does the policy bot need cluster credentials? No. Conftest and kyverno apply run fully offline against files, so no kubeconfig or cluster access is required.
How do I keep CI and cluster policy in sync? Point the Kyverno CLI at the same ClusterPolicy files you deploy to the cluster, ideally from one directory in the repo.
Can I block only some rules and warn on others? Yes. Use validationFailureAction: Enforce for hard rules and Audit for advisory ones, and let warnings exit zero.
What about Docker image policy, like requiring digests? Write a rule that rejects floating tags in image: fields; both Rego and Kyverno pattern matching can enforce digest pinning.
Will this slow down PRs much? Scoped to changed files, the check usually runs in well under a minute, dominated by downloading the CLI binaries.
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).
Reviewed by Chris St. John·Last updated Jul 16, 2026