
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Misconfigured infrastructure is the leading cause of cloud breaches and compliance failures, yet most teams still rely on manual code reviews to catch them. Using Conftest: Test Configs Against OPA Policies shifts this validation left, allowing you to enforce security and operational standards automatically within your CI/CD pipeline before resources are ever provisioned. This approach transforms subjective review checklists into deterministic, executable tests that scale with your engineering velocity.
policy/ directory, and run conftest test against your IaC files in CI. It parses YAML, JSON, HCL, and Dockerfiles natively, returning structured pass/fail results that block non-compliant deployments instantly.How do you install and configure Conftest for OPA policy testing?
Before you can use Conftest to test configs against OPA policies, you need a working local environment that mirrors your CI runner. Conftest is a single static binary with no external dependencies, making it trivial to add to any developer machine or container image. For teams working across macOS, Linux, and Windows, I recommend using a version manager or pinning the exact release in your tooling documentation to avoid "works on my machine" discrepancies during DevSecOps shift-left initiatives.
Installation methods for 2026
- Homebrew (macOS/Linux):
brew install conftest - Scoop (Windows):
scoop install conftest - Direct Binary: Download from GitHub releases, verify the checksum, and move to
/usr/local/bin. - Docker:
docker run --rm -v $(pwd):/app openpolicyagent/conftest test /app
Project structure convention
Conftest expects a specific layout by default. While you can override paths with flags, adhering to the standard reduces cognitive load and simplifies onboarding. Create a policy/ directory at your repository root. All .rego files in this directory are loaded automatically. If you have multiple domains (e.g., Kubernetes vs. Terraform), use subdirectories like policy/k8s/ and policy/terraform/, then target them explicitly with the -p flag.
<project-root>
├── infra/
│ ├── main.tf
│ └── k8s-deployment.yaml
├── policy/
│ ├── base.rego
│ ├── terraform.rego
│ └── k8s_security.rego
└── .github/workflows/policy-check.yml How do you write effective Rego policies for infrastructure?
The core value of using Conftest to test configs against OPA policies lies in the quality of your Rego rules. Rego is a declarative query language, not a procedural scripting language. A common mistake engineers make is trying to write "if/else" logic instead of defining assertions. Think of Rego as asking questions about your data: "Does any container lack a resource limit?" rather than "Loop through containers and check limits."
Basic deny rule pattern
Every policy should start with a clear package declaration and import statements. The deny rule set is the standard entry point for Conftest violations. Each rule in the set represents a distinct failure condition.
package main
import rego.v1
# Deny containers running as root
deny contains msg if {
input.kind == "Deployment"
container := input.spec.template.spec.containers[_]
not container.securityContext.runAsNonRoot
msg := sprintf("Container '%s' must set runAsNonRoot: true", [container.name])
}
# Deny missing resource limits
deny contains msg if {
input.kind == "Deployment"
container := input.spec.template.spec.containers[_]
not container.resources.limits.cpu
msg := sprintf("Container '%s' missing CPU limit", [container.name])
} Handling exceptions and allowlists
Rigid policies break legitimate workflows. Use an ignore annotation pattern or a separate data file for exceptions. This keeps your primary policy clean while acknowledging that some namespaces or workloads require different treatment. In my experience managing SOC 2 compliant environments, having a documented, auditable exception mechanism is just as important as the enforcement itself.
# Allow privileged containers only in kube-system namespace
deny contains msg if {
input.kind == "Pod"
input.metadata.namespace != "kube-system"
container := input.spec.containers[_]
container.securityContext.privileged == true
msg := sprintf("Privileged containers forbidden outside kube-system: %s", [container.name])
} How do you integrate Conftest into CI/CD pipelines?
Running Conftest locally is useful for feedback, but enforcing it requires CI integration. When you use Conftest to test configs against OPA policies in a pipeline, you create an immutable quality gate. I typically place this check immediately after linting but before the plan/apply stage. This ensures we never waste time generating execution plans for infrastructure that violates baseline security requirements.
GitHub Actions example
This workflow snippet demonstrates a minimal but production-ready setup. Note the use of --fail-on-warn for strict environments versus default behavior which only fails on deny rules.
name: Policy Check
on: [pull_request]
jobs:
conftest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Conftest
run: |
wget https://github.com/open-policy-agent/conftest/releases/download/v0.56.0/conftest_0.56.0_Linux_x86_64.tar.gz
tar xzf conftest_0.56.0_Linux_x86_64.tar.gz
sudo mv conftest /usr/local/bin/
- name: Test Kubernetes Manifests
run: conftest test k8s/ -p policy/k8s/ --output json
- name: Test Terraform Plan
run: |
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
conftest test tfplan.json -p policy/terraform/ Testing Terraform plans vs. static HCL
A critical distinction in practice: testing raw .tf files catches syntax and basic attribute issues, but testing the terraform plan -json output catches computed values, module expansions, and provider defaults. For serious compliance work like SOC 2 evidence automation, always test the plan JSON. Static HCL testing is insufficient because it cannot evaluate interpolated variables or conditional resource creation.
How does Conftest compare to other policy-as-code tools?
Engineers often ask why they should choose Conftest over alternatives. The decision depends on your existing ecosystem and the scope of your policy-as-code strategy. Conftest excels as a lightweight, file-centric testing tool, while other tools serve different niches in the governance stack.
| Feature | Conftest | Kyverno | Terraform Sentinel | Checkov |
|---|---|---|---|---|
| Primary Scope | Any config file (YAML, JSON, HCL, Dockerfile) | Kubernetes admission control | Terraform Cloud/Enterprise only | IaC static analysis |
| Policy Language | Rego (OPA) | YAML CRDs | Sentinel (proprietary) | Python / YAML |
| CI Integration | Native CLI, zero dependencies | Requires cluster or CLI wrapper | Tied to TF Cloud API | CLI + SaaS option |
| Learning Curve | Moderate (Rego is unique) | Low (declarative YAML) | Moderate | Low (pre-built checks) |
| Best For | Pipeline gates, multi-stack testing | Runtime K8s enforcement | Terraform Cloud shops | Quick security scanning |
In practice, these tools are complementary rather than mutually exclusive. I frequently deploy Conftest in CI pipelines for pre-commit validation while running Kyverno as an admission controller in the cluster for runtime defense-in-depth. Conftest catches issues before they consume API quota; Kyverno catches drift and manual changes that bypass CI.
How do you debug failing Rego policies and reduce false positives?
The most frequent friction point when teams adopt Conftest to test configs against OPA policies is debugging opaque failures. Rego's declarative nature means there is no step-through debugger in the traditional sense. Instead, you rely on structured output and targeted evaluation commands to isolate issues.
Use the print function and trace output
Add print() statements directly in your Rego rules during development. Unlike older trace() approaches, print() outputs to stderr and works reliably across Conftest versions. Combine this with --output json to get machine-readable results that pinpoint exactly which input field triggered the denial.
# Debugging example
deny contains msg if {
container := input.spec.containers[_]
print(sprintf("DEBUG: Checking container %s, image=%s", [container.name, container.image]))
not startswith(container.image, "gcr.io/my-project/")
msg := sprintf("Untrusted image registry: %s", [container.image])
} Unit testing your policies
Treat your policies like application code. Write Rego tests using the built-in test_ prefix convention. Create mock input documents that represent both valid and invalid configurations. Run conftest verify to execute these unit tests independently of your actual infrastructure files. This practice dramatically reduces regression risk when updating shared policy libraries.
Managing policy sprawl
As your rule count grows beyond 20-30 checks, organize policies into logical packages and consider bundling them as OCI artifacts. Tools like conftest push and conftest pull let you distribute versioned policy bundles across teams. This avoids copy-pasting Rego files between repositories and ensures consistent enforcement standards across your organization's entire infrastructure estate.
Implementing Conftest for Sustainable Compliance
Adopting Conftest to test configs against OPA policies is not a one-time setup; it is an ongoing engineering discipline. Start with five to ten high-impact rules covering your most critical security risks—privileged containers, public S3 buckets, missing encryption—before expanding to operational best practices. Measure your policy violation trends over time to identify training gaps or systemic architecture issues. If you need help designing a policy framework that balances security rigor with developer velocity, reach out to discuss your infrastructure compliance strategy.