What IaC scanning finds
Misconfigurations, before deploy.
A linter confirms your Terraform parses. A runtime scanner catches a public bucket after it already exists. Static analysis sits in the middle: it reads Terraform, Kubernetes manifests, CloudFormation, and Dockerfiles, then flags insecure configuration before any of it reaches an account. An unencrypted volume. A security group open to 0.0.0.0/0. An IAM policy with Action:*. A container running as root. Those ship quietly when nobody reads the diff closely enough.
Checkov is a Python package from Prisma/Bridgecrew. It ships with hundreds of built-in policies, and you run it on a laptop or in CI. The work is the same across Checkov, tfsec, Trivy, Terrascan, and KICS: read config, match rules, report violations. Learn that loop once and the rest of the family makes sense.
Where the scan sits in your pipeline
Shift-left is the habit of moving a check earlier, closer to the person who can fix it in a minute. For infrastructure it looks like this. You write code, you open a pull request, and a scanner grades every resource against a catalog of checks before anyone can merge. A source scan needs no cloud credentials at all. Checkov parses the files sitting on disk and compares each attribute to a rule. Cheap enough to run on every commit, fast enough that a developer gets an answer in minutes. The gate stops being "deploy and hope" and becomes "fix the misconfiguration while the change is still a diff."
$ checkov -d ./infra --framework terraform --compact --quiet
terraform scan results:Passed checks: 38, Failed checks: 4, Skipped checks: 0Check: CKV_AWS_20: "S3 Bucket has public access block"FAILED for resource: aws_s3_bucket.uploadsFile: /infra/s3.tf:3-11Guide: https://docs.prismacloud.io/...Check: CKV_AWS_79: "Ensure Instance Metadata Service V2 is required"FAILED for resource: aws_instance.appFile: /infra/ec2.tf:14-28
What a scanner is genuinely good at
Checkov is strongest on the boring, well-known failure classes, because each one maps to a field you can read straight off the page: encryption switched off, something exposed publicly, an over-broad IAM policy, missing logging, weak TLS settings, containers running as root, GitHub Actions pinned to a moving tag instead of a fixed version. Most checks boil down to one question. Does this attribute equal the safe value? Graph checks, the ones with a CKV2_ prefix, ask a bigger one. Does the relationship between these resources create exposure? The built-in catalog covers AWS, Azure, GCP, Kubernetes, Docker, CloudFormation and more, thousands of rules kept current by the vendor and the community.
$ checkov -d . --check CKV_AWS_18,CKV_AWS_21,CKV_K8S_22 --compact
Check: CKV_AWS_18: "Ensure the S3 bucket has access logging enabled"FAILED for resource: aws_s3_bucket.logsCheck: CKV_AWS_21: "Ensure all data stored in the S3 bucket have versioning enabled"FAILED for resource: aws_s3_bucket.logsCheck: CKV_K8S_22: "Use read-only root filesystem"FAILED for resource: Deployment.default.payments-api
What a scanner cannot see
A green scan proves less than it feels like it proves. A scanner cannot watch runtime behaviour, cannot resolve business logic that only settles after deploy, and cannot see risk that never appears in config at all. Your bucket can pass every encryption check while the application quietly writes secrets into a plain text file. Rules of your own making, like "only these instance types in prod", need a custom check or OPA/Conftest running alongside Checkov. Static scanning is one layer. Policy-as-code, secrets detection, image CVE scanning and runtime detection cover the rest.
Check IDs are the shared vocabulary
Every finding arrives with a stable identifier: CKV_AWS_18, CKV2_AWS_6, and so on. You will type these IDs into suppressions, baselines, CI config and Slack threads, and they mean exactly the same thing in every one of those places. A CKV_ check evaluates a single resource. A CKV2_ check is a graph check that spans relationships between resources. The middle of the ID tells you the framework: CKV_AWS_*, CKV_K8S_*, CKV_DOCKER_*, CKV_GHA_*. When you triage, read the ID before the sentence beside it. The sentence is a friendly summary. The ID is the handle every other tool understands.
$ checkov -d . --framework terraform -o json | jq -r '.results.failed_checks[] | "\(.check_id) \(.resource)"' | head
CKV_AWS_18 aws_s3_bucket.logsCKV_AWS_79 aws_instance.appCKV2_AWS_6 aws_s3_bucket.uploads
Source scan vs plan scan (a preview)
A source scan reads the .tf and YAML files on disk: fast, no cloud credentials, ideal in the editor and on every commit. It has one blind spot. Values that only settle once terraform plan runs, such as variables, module outputs and dynamic blocks, may never appear as the insecure value the plan would really create. Plan scanning, covered in cv-plan, closes that gap by reading terraform show -json output. Teams who have been at this a while run both. Source for speed, plan for authority before apply.
$ checkov -d . --framework terraform --compact$ checkov -f tfplan.json --framework terraform_plan --compact
terraform scan results:Passed checks: 812, Failed checks: 37terraform_plan scan results:Passed checks: 156, Failed checks: 2# plan may surface failures invisible in source — both layers matter
Where this course goes
The topics ahead follow the order you meet these problems in real life: first install, then reading a failure, then repos with four frameworks jammed into them, then suppressions, custom rules, plan scanning, baselines, how the rival scanners compare, where tools overlap, CI gates, and finally running the whole thing as a programme with owners and deadlines. Every topic stands on its own, so start wherever your current problem is.
The layers either side of the scanner
Pair Checkov with secrets detection (Gitleaks at commit time, Checkov's secrets framework as a backstop), image CVE scanning (Trivy or Grype), and runtime controls (Falco, cloud guardrails). Checkov catches misconfiguration in code. It will not rotate a leaked key or patch an operating system package inside a container image. Give each domain one owner so your gates do not end up arguing with each other.
$ checkov -d . --framework terraform --compact --quiet$ gitleaks detect --source . --redact$ trivy config . --severity HIGH,CRITICAL
checkov Failed checks: 4gitleaks: no leaks foundTrivy HIGH/CRITICAL misconfigurations: 3# three layers, three owners — not three copies of the same gate
Graph checks (CKV2_) catch the risk that lives in the wiring. A security group rule can look perfectly sensible on its own and turn dangerous the moment it is attached to an instance with a public IP address. Plan scanning and baselines are the other two things Checkov does its own way, and scanners built purely on Rego (the policy language behind OPA) tend to handle both quite differently.
These findings are boring right up until they are catastrophic. A public S3 bucket. A security group open to the world. An IAM policy with admin rights. A container running as root. Boring mistakes cause most incidents, and a scanner performs the boring review tirelessly, which frees human attention for architecture and threat modelling instead of checking whether logging is enabled on bucket number forty.
How this compares to runtime tools and policy-as-code
Runtime tools answer a different question. CloudTrail alerts, GuardDuty and Falco tell you what is happening in the account right now. Checkov tells you what you will build if this merge lands. OPA with Conftest evaluates Rego against structured input, which is powerful for policy specific to your company, though you still have to decide what input to feed it: manifests, plans, something else. Checkov turns up with hundreds of ready-made checks and a command your developers can run in thirty seconds. Most teams take Checkov (or a sibling scanner) for breadth and add OPA where Rego already powers their admission control.
Secrets scanners find credentials committed to git. Image scanners find known vulnerabilities buried in image layers. Checkov finds buckets without encryption and pods without resource limits. None of the three substitutes for the others. Reading infrastructure code before it deploys is the job Checkov was built for.
$ checkov -d . --framework terraform,kubernetes,dockerfile --compact$ checkov -d . --framework secrets --quiet
terraform: Passed 812, Failed 37kubernetes: Passed 204, Failed 11dockerfile: Passed 12, Failed 2secrets: Failed 1Check: CKV_SECRET_6 on /app/config.yml
One repository, several frameworks, one scanner across all of them. That mix is what most real codebases look like, which is why the topics ahead handle frameworks before they get clever about custom rules and gates. Start with install (cv-install) and move at whatever pace the repo you are stuck with demands.
Try this
Make a throwaway folder holding a single Terraform file, or clone a small public sample, install Checkov if you have not already, and run the commands below. You are proving the loop works: point at files, get check IDs back, confirm the resources were parsed. You are not proving your cloud is safe.
$ mkdir -p /tmp/cv-what && cd /tmp/cv-what$ printf 'resource "aws_s3_bucket" "demo" { bucket = "demo-example" }\n' > main.tf$ checkov -d . --framework terraform --compact --quiet$ checkov -d . -o json | jq 'if type=="array" then .[].summary else .summary end'
Check: CKV_AWS_18: "Ensure the S3 bucket has access logging enabled"FAILED for resource: aws_s3_bucket.demo{"passed": 4,"failed": 6,"skipped": 0,"parsing_errors": 0,"resource_count": 1}# resource_count: 1 means Checkov saw the bucket — not a hollow green scan
Takeaway
Source scans are cheap and need no cloud credentials, so you can run them on every commit. They cannot see values that only appear after terraform plan, and they cannot prove anything about runtime behavior. Pair Checkov with secrets detection, image CVE scanning, and cloud runtime controls so each layer answers one question.
Next: install Checkov (cv-install) and read real failure lines until the check ID, the resource address, and the Guide link are usable. After that, triage (cv-results) and CI gates are a worklist, not noise.