Conftest: Test Configs Against OPA Policies

Khimananda Oli 8 min read Virtualization
Conftest: Test Configs Against OPA Policies

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.

IaC FilesTerraform / K8s / DockerConftest EngineParser + EvaluatorRego Policiespolicy/*.regoCI Gate ResultPass / Fail / Block
High-level workflow: Conftest ingests infrastructure configs and evaluates them against Rego policies to produce deterministic CI gate results.

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])
}
Input Documentmetadata.labelsspec.containers[]securityContextPolicy Rulesdeny[msg]warn[msg]exceptionsOutput ResultsPASS (0 fails)WARN (2 warns)FAIL (1 deny)
Evaluation engine internals: Input documents are parsed and matched against deny/warn rulesets to generate structured compliance output.

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.

FeatureConftestKyvernoTerraform SentinelCheckov
Primary ScopeAny config file (YAML, JSON, HCL, Dockerfile)Kubernetes admission controlTerraform Cloud/Enterprise onlyIaC static analysis
Policy LanguageRego (OPA)YAML CRDsSentinel (proprietary)Python / YAML
CI IntegrationNative CLI, zero dependenciesRequires cluster or CLI wrapperTied to TF Cloud APICLI + SaaS option
Learning CurveModerate (Rego is unique)Low (declarative YAML)ModerateLow (pre-built checks)
Best ForPipeline gates, multi-stack testingRuntime K8s enforcementTerraform Cloud shopsQuick 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.

Policy Enforcement LifecycleDeveloper Laptop → CI Pipeline → Staging Deploy → Production RuntimePre-CommitLocal ConftestFast FeedbackCI PipelineConftest GateBlock Bad DeploysAdmission CtrlKyverno / OPA-GKRuntime EnforcementAudit / DriftPeriodic ScansCompliance EvidenceKey Takeaway: Defense in DepthConftest prevents violations from entering the pipeline; admission controllers catch what slips through.Neither alone is sufficient for production-grade compliance in 2026.
Defense-in-depth model: Conftest operates at pre-commit and CI stages, complementing runtime admission controllers for complete coverage.

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.

Frequently Asked Questions

Conftest is a CLI tool that tests configuration files against Open Policy Agent Rego policies. It parses YAML, JSON, HCL, and other formats into structured data, then evaluates them using OPA without requiring a running OPA server or external API dependencies for local validation workflows.

Download the latest binary from GitHub releases for your architecture and move it to /usr/local/bin. Alternatively use brew install conftest on macOS or apt install conftest if available in your distribution repositories. Verify installation by running conftest --version to confirm the current stable release works correctly.

Yes, Conftest natively parses Kubernetes YAML and Helm chart outputs. Write Rego policies targeting input.metadata or input.spec fields to enforce namespace restrictions, resource limits, or security contexts. Run conftest test manifest.yaml in CI pipelines to block non-compliant deployments before they reach the cluster API server.

Conftest supports YAML, JSON, TOML, HCL, Dockerfile, XML, and EDN formats automatically based on file extension. For unsupported formats, use the --parser flag to specify a custom parser or convert configurations to JSON before testing. This flexibility covers most infrastructure-as-code and application configuration files used in modern DevOps stacks.

Create a policy directory with a .rego file defining package main and deny rules. Reference input to access parsed configuration data. Use conftest test --policy ./policies config.yaml to evaluate. Start simple with equality checks before adding complex logic like array iteration or nested object validation for production use cases.

No. Conftest embeds the OPA evaluation engine directly and runs entirely offline as a standalone binary. This eliminates network dependencies and server management overhead compared to querying a remote OPA instance. Policies are evaluated locally against provided configuration files during CI runs or developer workstation checks.

Add the open-policy-agent/conftest-action step in your workflow YAML after checkout. Specify the policy directory and target configuration paths. The action fails the job if any deny rules trigger, providing inline annotations on pull requests. Pin to a specific action version tag for reproducible builds across all repository branches.

Yes, generate a JSON plan using terraform plan -out=tfplan && terraform show -json tfplan > plan.json then run conftest test plan.json. This validates actual planned changes including computed values rather than static HCL syntax. Policies should target input.resource_changes array elements for accurate drift and compliance detection.

Conftest specializes in configuration file testing with automatic format parsing and opinionated defaults for infrastructure validation. OPA CLI is a general-purpose policy engine requiring manual input formatting and server setup for some features. Choose Conftest for CI config gates and OPA CLI for runtime authorization decisions or embedded library usage.

Use the --trace flag to see detailed evaluation steps and variable bindings during rule execution. Add print statements in Rego for intermediate values. Test individual rules with conftest verify to isolate logic errors. Ensure input structure matches expectations by dumping parsed config with conftest parse before writing complex validation rules.

Yes, store shared Rego policies in a dedicated Git repository and reference them via --update git::https://github.com/org/policies.git//path in CI. Teams can version and distribute standardized compliance rules centrally. Individual repos override or extend base policies using bundle composition, ensuring consistent governance without duplicating policy code across dozens of microservice configurations.

Yes, Conftest is licensed under Apache 2.0 and remains free for commercial and internal use. There are no enterprise editions or paid tiers as of 2026. Organizations can modify, redistribute, and integrate it into proprietary toolchains without licensing fees or vendor lock-in concerns for infrastructure policy testing workflows.

Define allow rules alongside deny rules to create explicit exceptions. Use metadata annotations in configuration files or separate exception data files loaded via --data flag. Structure policies so deny triggers only when allow conditions are unmet. Document exception criteria clearly in Rego comments to maintain auditability during compliance reviews.

Yes, use the --output junit flag to generate XML reports compatible with Jenkins, GitLab CI, and Azure DevOps test result parsers. Upload artifacts in pipeline post-steps to visualize pass/fail trends over time. Combine with --fail-on-warn to treat policy warnings as failures when strict compliance reporting is required for audit purposes.

Large bundles with hundreds of rules slow evaluation due to Rego interpretation overhead. Split policies into focused packages and use partial evaluation where possible. Avoid deep nesting and expensive built-ins like regex on large arrays. Profile with --benchmark flag and consider pre-compiling bundles with opa build for faster repeated executions in high-frequency CI environments.