← Back to Blog RSS

Checkov and Terraform: What is Checkov and how does it work?

Terraform Checkov Security

Checkov catches misconfigurations in Terraform code before they reach production, scanning every pull request against hundreds of built-in policies before a human reviews the raw plan. A scan passing in CI shows the code at merge time, without guaranteeing that the infrastructure it describes stays compliant once it is deployed and changing.

TL;DR
$ cat checkov-terraform.tldr
• Checkov is a free, open source static analysis tool that scans Terraform code against hundreds of built-in security and compliance policies.
• Wiring Checkov into a GitHub Actions workflow means every pull request gets scanned automatically, before a human ever reviews the raw plan.
• A passing Checkov scan is a snapshot of code at a single moment, not what will actually be running weeks or months later.
• Pairing a Checkov scan with a queryable, auditable state backend gives teams a way to prove compliance long after the scan ran.

Terraform makes infrastructure fast to ship, but misconfigurations can slip through. A missing encryption flag on an S3 bucket, a security group left open to the world, a policy that nobody thought to write down. These are easy mistakes to make and easy for a reviewer to miss buried inside a hundred-line pull request, especially when the reviewer is reading Terraform code rather than the resources it will actually create.

Multiply that across a growing set of modules, environments, and cloud infrastructure configurations, and a single overlooked line becomes a real exposure sitting in production.

Checkov has become the standard way teams catch these issues automatically, before a human ever has to trace through the plan by hand. It reads infrastructure as code, checks it against a library of security and compliance policies, and fails the check before the misconfiguration ever gets the chance to merge.

This article covers what Checkov actually is, how a scan works under the hood, and how to wire it into a GitHub Actions workflow so every pull request gets checked without anyone remembering to run it manually.

The reality is, however, that a clean scan on day one says nothing about whether the infrastructure stays that way once it is deployed and someone, or something, starts changing it. Discover how a queryable state backend like Stategraph closes that particular gap, as a complement to what Checkov already does well.

What is Checkov?

Checkov is an open source static analysis tool that scans infrastructure as code for security and compliance issues before any of it gets deployed.

It started life at Bridgecrew and is now maintained under Palo Alto's Prisma Cloud, and the tool itself has stayed free and open source throughout, with no cap on how many times you can run a scan.

Terraform is the format most people scan with Checkov, but the tool also reads CloudFormation, Kubernetes manifests, ARM templates, AWS SAM, and Serverless Framework configuration, plus a handful of other formats.

Out of the box, it ships with hundreds of built-in policies covering common frameworks like CIS benchmarks, HIPAA, and PCI DSS, so a team adopting Checkov for the first time gets broad coverage of common misconfigurations without writing a single custom policy file.

That combination of versatile format support and a large policy set is a big part of why Checkov ended up as the default choice for teams that want a static code analysis tool without paying for one.

Checkov also reaches beyond raw configuration files, scanning container images, Dockerfiles, and pod metadata in Kubernetes manifests, so the same command line interface that checks a Terraform folder for open security groups can also flag a container running with excessive privileges or missing CPU limits.

What does a Checkov scan actually check for?

A Checkov scan reads either your raw Terraform configuration files or a generated Terraform plan file and evaluates every resource it finds against Checkov's policy library, flagging anything that violates a known security or compliance rule.

An S3 bucket resource, for example, can fail CKV_AWS_18 for missing access logging or CKV_AWS_21 for missing versioning, and each failed check comes back with a specific policy ID a writer or engineer can look up directly rather than guessing at what triggered it.

The same logic applies across resource types: open security groups, unencrypted volumes, overly broad IAM policies with privilege escalation potential, and public storage buckets are all common failed checks that show up in a first scan of an existing Terraform folder.

The detailed reports Checkov produces list passed checks alongside failed checks, so a team can see coverage improving over time rather than only ever seeing a wall of red.

There are two ways to run the scan itself, and they check different things.

  1. Running checkov -d . against a Terraform folder analyzes the configuration files as written, which is fast and catches most issues, but it does not know what values your variables actually resolve to at apply time.
  2. Generating a plan file first, with terraform plan -out tf.plan followed by terraform show -json tf.plan > tf.json and then checkov -f tf.json, scans the fully resolved plan instead, which more accurately reflects what Terraform is actually about to create.

For a specific file rather than a whole folder, Checkov accepts a direct path with the same -f flag, which is useful for a quick check during local development before pushing anything.

Setting up Checkov for Terraform

Installing Checkov is a single command either way: pip install checkov or brew install checkov if you would rather manage it through Homebrew.

Once it's installed, running a scan against a Terraform folder needs nothing more than pointing it at the directory, and the output will arrive as a readable list of passed checks and failed checks, or in JSON format or JUnit XML if you need to feed the results into another tool.

Not every failed check is a problem. A security group might be intentionally open on a specific port for a legitimate reason, and re-litigating that decision on every single scan wastes everyone's time. Checkov handles this with inline suppression, a #checkov:skip= comment placed directly above the resource block along with the policy ID and a reason, or with a .checkov.yml config file if you want to define custom policies or skip rules globally across the whole Terraform folder rather than resource by resource.

This suppression pattern is actually very useful, as a scan that keeps flagging the same accepted exception trains people to stop reading the output at all. A static analysis tool that gets ignored is worse than no tool.

Teams with internal policies that go beyond the built-in set can define custom policies of their own in a Python or YAML file, enabling Checkov to enforce internal security best practices alongside industry standards, like the CIS benchmarks it already ships with.

Integrating Checkov with Terraform in GitHub Actions workflows

Remembering to run a command is exactly the kind of step that gets skipped under deadline pressure, so most teams do not run Checkov by hand before every pull request. The fix is to wire it into a GitHub Actions workflow so the scan runs automatically on every push and every pull request, using the official bridgecrewio/checkov-action.

A minimal workflow checks out the repository, then runs the action against a target directory such as the Terraform folder in your repo, triggered on push and pull_request events targeting main.

Early in rollout, most teams set soft_fail to true, which reports findings in the pull request without blocking the merge, giving the team a chance to work through the backlog of existing issues before turning enforcement on.

Once that backlog is cleared, flipping soft_fail to false makes the check part of the actual CI/CD gate, so a pull request with unresolved findings cannot merge at all.

name: checkov
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run Checkov
        uses: bridgecrewio/checkov-action@master
        with:
          directory: ./infra
          framework: terraform
          soft_fail: true

A typical setup points the action at an input folder such as ./infra, sets the framework to terraform, and lets the action post results directly on the pull request so reviewers see failed checks without leaving GitHub.

Exact input names for the action and how it formats output in the pull request change over time, so before publishing anything into a real workflow, it's worth checking the current README at github.com/bridgecrewio/checkov-action and the official integration docs at checkov.io against whatever is written here.

Here is what Checkov looks like in GitHub:

Checkov results posted on a GitHub pull request, listing passed and failed checks with their policy IDs.

Where scanning at commit time stops covering you

A Checkov scan happens against code at one moment, the moment someone opens a pull request.

It's genuinely useful, and it is also the entire limit of what it can tell you. Checkov has no way of knowing whether a resource drifted after it was deployed, whether someone made a manual change in the cloud console outside of Terraform entirely, or whether a policy that passed cleanly at merge time still holds six months later once the infrastructure around it has grown into something nobody scanned as a whole.

Misconfigurations that a scan would have caught at merge time can sit live in production for a long stretch before anyone notices, because nothing is watching the infrastructure after the plan applies.

A security group tightened in code and approved in review can be reopened manually the following week by someone fixing an unrelated incident, and Checkov has no way to see that it happened, because its job ends the moment the plan is generated.

Closing the gap between a clean scan and a compliant deployment

Once Terraform state lives in Stategraph rather than a flat state file, every applied change becomes part of a queryable, auditable history instead of a snapshot that only reflects the most recent apply.

Teams can check what is actually running and who changed it, not just what the code said the last time somebody merged a pull request.

This is a complement to Checkov, not a replacement for it.

Checkov stops a bad configuration from being written in the first place; Stategraph's job starts after that, once the configuration is live.

What Stategraph adds is visibility into what is actually deployed, backed by a transaction log and resource-level access control, so when someone asks whether a specific resource has stayed compliant since it was created, there is an actual record to point to instead of a scan result from months ago that has nothing to say about what happened since.

Conclusion

Checkov and a solid CI/CD pipeline stop most misconfigurations before they ever ship, and wiring a scan into GitHub Actions means that protection runs on every pull request without anyone having to remember to do it by hand. What a scan cannot do is watch the infrastructure after it is deployed, and that is where pairing it with a state backend built for visibility closes the loop. See how Stategraph handles this in our docs.

Checkov Terraform FAQs

What's the best way to integrate Checkov with Terraform pipelines?

Wire it into a GitHub Actions workflow using the official checkov-action, triggered on every push and pull request, so every change gets scanned automatically instead of relying on someone to run it manually.

How do you use Checkov to scan Terraform code?

Run checkov -d . against a Terraform folder to scan the raw configuration files, or generate a plan file first and scan that with checkov -f tf.json for a result that reflects what will actually get created.

Does a passing Checkov scan mean my infrastructure is compliant forever?

No. A Checkov scan checks the code at the moment of the pull request, not the resources after they are deployed. Drift, manual changes, and policies that erode over time are all invisible to a scan that only ever runs at merge.