IaC Error Guide: 'FAIL - policy ... deny' Conftest Policy Violation
Fix Conftest and OPA policy violations: diagnose matched deny rules, wrong namespaces, unparsed input, broken Rego paths, and schema mismatches between policy and manifests.
- #iac
- #troubleshooting
- #errors
- #opa
Overview
Conftest evaluates your configuration files (Kubernetes manifests, Terraform plans, Dockerfiles) against Open Policy Agent (OPA) Rego policies. A FAIL means a deny rule in your Rego matched the input document and returned a violation message. Conftest collects every deny (and warn) result from the configured namespace and exits non-zero if any deny fired.
A typical failure looks like this:
FAIL - deployment.yaml - main - Containers must not run as root (securityContext.runAsNonRoot must be true)
3 tests, 2 passed, 0 warnings, 1 failure, 0 exceptions
The format is FAIL - <file> - <namespace> - <message>. The message is whatever string your deny rule returned. Crucially, a FAIL is not always a real policy breach — it can also mean Conftest could not parse the input (so the document was empty), the rule is in a namespace you did not query, or the Rego path itself is wrong. Distinguishing a genuine violation from a misconfigured check is the core of triaging Conftest.
Symptoms
conftest testexits non-zero with one or moreFAIL -lines.- The summary line shows
1 failure(or more) and a non-zero exit code in CI. - Conftest reports
0 tests— no rules ran at all, often a namespace or parse problem. - A manifest you believe is compliant still fails, or one you expect to fail passes.
conftest test deployment.yaml --policy policy/
FAIL - deployment.yaml - main - Deployment must set a CPU limit
FAIL - deployment.yaml - main - image must not use the 'latest' tag
4 tests, 2 passed, 0 warnings, 2 failures, 0 exceptions
Common Root Causes
1. A deny rule genuinely matched the input
The most common case: the manifest really does violate a policy, and the deny rule fired as designed. The returned message tells you exactly what to fix.
conftest test deployment.yaml --policy policy/
FAIL - deployment.yaml - main - Containers must not run as root (securityContext.runAsNonRoot must be true)
3 tests, 2 passed, 0 warnings, 1 failure, 0 exceptions
Add securityContext.runAsNonRoot: true to the container spec and re-run. This is the expected, healthy behavior of policy-as-code.
2. Rules are in a namespace you did not query
Conftest evaluates data.main by default. If your deny rules live under package kubernetes.security, they never run unless you select that namespace — so a non-compliant file passes silently.
conftest test deployment.yaml --policy policy/
conftest test deployment.yaml --policy policy/ --namespace kubernetes.security
0 tests, 0 passed, 0 warnings, 0 failures, 0 exceptions
FAIL - deployment.yaml - kubernetes.security - privileged containers are not allowed
0 tests from the first run is the tell: the default namespace had no rules. Pass --namespace to match your package declaration, or use --all-namespaces.
3. The input file was not parsed (unknown or wrong type)
Conftest infers a parser from the file extension. A .tf plan saved as .json, a templated YAML with Go template syntax, or an unsupported extension yields an empty input, so rules that index into it never match — or match unexpectedly.
conftest parse deployment.yaml
Error: failed to parse: yaml: line 18: mapping values are not allowed in this context
conftest parse prints what Conftest actually loaded. If it errors or shows {}, the policy is evaluating against nothing. Fix the file or force a parser with --parser yaml.
4. A bug in the Rego or the wrong policy path
A deny rule references a field that does not exist, has a typo, or the --policy flag points at the wrong directory. The rule either never fires (false pass) or always fires (false fail).
opa eval -d policy/ -i input.json 'data.main.deny' --format pretty
[
"Deployment must set a CPU limit"
]
opa eval runs the exact rule against a parsed input and shows the raw result set. If the set is empty when you expect a hit, or full when you expect none, the rule logic or its field path is wrong. Run opa fmt -w policy/ to normalize and surface obvious syntax slips.
5. Missing data document or exception not loaded
Policies that consult external data (an allow-list of approved registries, an exceptions file) fail open or closed when that data is not supplied with --data. The rule reads an undefined value and behaves unexpectedly.
conftest test deployment.yaml --policy policy/ --data data/
FAIL - deployment.yaml - main - registry 'docker.io' is not in the approved registry list
2 tests, 1 passed, 0 warnings, 1 failure, 0 exceptions
Without --data data/, the approved-registry list is undefined and every image fails. Load the data document so the rule can evaluate against it, and confirm any exception entries are included.
6. Schema mismatch between policy expectation and the manifest
The Rego indexes input.spec.template.spec.containers[_], but the manifest is a bare Pod (input.spec.containers[_]) or a CronJob (input.spec.jobTemplate.spec.template.spec.containers[_]). The path does not resolve, so a real violation slips past the rule.
opa eval -d policy/ -i input.json 'input.spec.template.spec.containers' --format pretty
undefined
undefined means the policy’s assumed path does not exist for this resource kind. Make the rule handle each kind explicitly, or normalize the input, so the container list is found regardless of workload type.
Diagnostic Workflow
Step 1: Confirm the file parses
conftest parse deployment.yaml
If this errors or returns {}, fix the input or set --parser before doing anything else — policies cannot evaluate an empty document.
Step 2: Run with the correct namespace
conftest test deployment.yaml --policy policy/ --all-namespaces
--all-namespaces runs every package’s deny/warn rules. If failures appear that the default run missed, your rules live in a non-default namespace.
Step 3: Inspect the raw rule result with opa eval
conftest parse deployment.yaml > input.json
opa eval -d policy/ -i input.json 'data.main.deny' --format pretty
This shows the exact set of violation messages the rule produces for the parsed input — the ground truth behind Conftest’s FAIL lines.
Step 4: Check the field paths the policy assumes
opa eval -d policy/ -i input.json 'input.kind' --format pretty
opa eval -d policy/ -i input.json 'input.spec.template.spec.containers' --format pretty
If input.kind is not what the rule expects, or the container path returns undefined, the policy is indexing the wrong schema for this resource.
Step 5: Load data, format, and self-test the policy
opa fmt -w policy/
conftest verify --policy policy/ --data data/
conftest verify runs the Rego unit tests (test_ rules) so you confirm the policy logic itself is correct before blaming the manifest.
Example Root Cause Analysis
CI fails a CronJob manifest with no obvious reason — the container clearly sets resource limits, yet:
FAIL - cronjob.yaml - main - Deployment must set a CPU limit
2 tests, 1 passed, 0 warnings, 1 failure, 0 exceptions
First, parse it — conftest parse cronjob.yaml succeeds, so the input is valid. Next, check what the policy sees:
conftest parse cronjob.yaml > input.json
opa eval -d policy/ -i input.json 'input.spec.template.spec.containers' --format pretty
undefined
The container path is undefined. The rule assumes a Deployment shape (spec.template.spec.containers), but a CronJob nests its pod template one level deeper under spec.jobTemplate. Because the path does not resolve, the rule’s limit check evaluates against nothing and the deny message fires unconditionally.
The fix is to make the rule resolve containers per kind:
# add a CronJob path (spec.jobTemplate.spec.template.spec.containers) to the rule, then:
opa fmt -w policy/
conftest verify --policy policy/
conftest test cronjob.yaml --policy policy/
After teaching the rule about jobTemplate, the CronJob passes and a genuinely under-specified CronJob would now be caught. The root cause was a schema mismatch, not a non-compliant manifest.
Prevention Best Practices
- Always pin
--namespace(or use--all-namespaces) explicitly in CI so a typo in apackagename can never cause rules to silently not run. - Keep Rego unit tests (
test_rules) next to every policy and gate merges onconftest verify— it catches logic bugs before a real manifest ever fails. - Handle each workload
kind(Deployment, Pod, CronJob, StatefulSet) explicitly, or normalize the input, so container paths resolve regardless of resource type. - Commit the
--datadocuments (approved registries, exceptions) alongside the policies and load them consistently in every environment. - Run
conftest parseon representative manifests in CI to guarantee inputs are parseable before evaluation. See more in infrastructure-as-code guides. - When a policy failure is ambiguous, the free incident assistant can summarize the
opa evaloutput into whether the manifest or the rule is at fault.
Quick Command Reference
# Confirm the file parses into a non-empty document
conftest parse deployment.yaml
# Run all namespaces, not just the default 'main'
conftest test deployment.yaml --policy policy/ --all-namespaces
# Run a specific namespace
conftest test deployment.yaml --policy policy/ --namespace kubernetes.security
# See the raw deny set the policy produces
conftest parse deployment.yaml > input.json
opa eval -d policy/ -i input.json 'data.main.deny' --format pretty
# Check the schema path the policy assumes
opa eval -d policy/ -i input.json 'input.kind' --format pretty
# Load external data and self-test the policy
conftest test deployment.yaml --policy policy/ --data data/
opa fmt -w policy/
conftest verify --policy policy/
Conclusion
A Conftest FAIL means a deny rule returned a violation — but the rule firing is not always the same as the manifest being wrong. Work through the causes before “fixing” the file:
- A
denyrule genuinely matched a non-compliant manifest — fix the manifest. - The rules live in a namespace you did not query, so nothing ran (or the wrong set ran).
- The input did not parse, leaving the policy to evaluate an empty document.
- A bug in the Rego or a wrong
--policypath makes the rule fire or miss incorrectly. - A required
--datadocument or exception was not loaded, so the rule read an undefined value. - A schema mismatch — the policy’s assumed field path does not exist for this resource
kind.
Use conftest parse, opa eval, and conftest verify to separate a true policy violation from a misconfigured check, then fix the right layer.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.