Scan Terraform and Kubernetes IaC for misconfigurations in CI
For developers shipping Terraform, Kubernetes manifests, or Helm charts through CI/CD. This shows the exact commands to scan infrastructure as code locally and in CI, fail builds on real findings, and verify the pipeline blocks risky changes before they reach your cloud account.
TL;DR — Add an IaC scanner to your repo and CI so pull requests fail before
terraform applyorkubectl applyruns. The fastest reliable setup istrivy configagainst Terraform/Kubernetes directories with--exit-code 1 --severity HIGH,CRITICAL, plus a baseline config file to skip known false positives deliberately. Reading time: ~5 min
Goal
When you finish, your repository will fail locally and in CI if Terraform, Kubernetes YAML, or Helm-rendered manifests contain high-severity misconfigurations, and you will have a repeatable command that returns a non-zero exit code before anything is deployed.
Prerequisites
- Git access to the repository that contains Terraform, Kubernetes YAML, or Helm charts
- CI access to edit the pipeline definition (GitHub Actions, GitLab CI, Jenkins, or similar)
trivy >= 0.55— check with:
trivy --version
- If you scan Helm charts:
helm >= 3.14— check with:
helm version
- If you scan Terraform modules that use variables: a representative
.tfvarsfile or CI environment values - Permission to add a config file such as
.trivy.yamlto the repo
Steps
Step 1: Install the scanner locally
Run one of these commands on your workstation:
brew install trivy
sudo apt-get update && sudo apt-get install -y wget gnupg lsb-release && wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo gpg --dearmor -o /usr/share/keyrings/trivy.gpg && echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/trivy.list && sudo apt-get update && sudo apt-get install -y trivy
trivy --version
You should see a version line like Version: 0.56.2.
Step 2: Scan your repo once and inspect real findings
From the repo root, run:
trivy config . --severity HIGH,CRITICAL --format table
If you only want common IaC paths:
trivy config terraform/ k8s/ helm/ --severity HIGH,CRITICAL --format table
You should see a table with findings or No misconfigurations detected.
Typical output shape when there are findings:
2026-10-30T10:12:41Z INFO Misconfiguration scanning is enabled
2026-10-30T10:12:43Z INFO Detected config files num=18
terraform/main.tf (terraform)
=============================
Tests: 12 (SUCCESSES: 10, FAILURES: 2)
Failures: 2 (HIGH: 1, CRITICAL: 1)
AVD-AWS-0107 (CRITICAL): S3 bucket allows public access
AVD-AWS-0089 (HIGH): Security group allows ingress from 0.0.0.0/0 to port 22
Step 3: Add a repo config so CI behavior is stable
Create .trivy.yaml in the repo root:
severity:
- HIGH
- CRITICAL
exit-code: 1
format: table
misconfiguration:
scanners:
- terraform
- kubernetes
- helm
skip-dirs:
- .terraform
- vendor
- node_modules
Then run:
trivy config . --config .trivy.yaml
You should see the same findings as before, but now the command exits with code 1 when HIGH/CRITICAL issues exist.
Step 4: Render Helm charts before scanning
If your repo contains Helm charts, scan the rendered manifests instead of raw templates:
mkdir -p .rendered
helm template app ./charts/app --namespace production --values ./charts/app/values.yaml > .rendered/app.yaml
trivy config .rendered/app.yaml --severity HIGH,CRITICAL --exit-code 1
You should see findings against concrete Kubernetes resources, not unresolved {{ .Values }} templates.
Step 5: Add the scan to CI before deploy steps
For GitHub Actions, create .github/workflows/iac-scan.yml:
name: iac-scan
on:
pull_request:
push:
branches: [main]
jobs:
trivy-config:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Trivy
run: |
sudo apt-get update
sudo apt-get install -y wget gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo gpg --dearmor -o /usr/share/keyrings/trivy.gpg
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt-get update
sudo apt-get install -y trivy
- name: Render Helm charts
run: |
mkdir -p .rendered
if [ -d charts/app ]; then helm template app ./charts/app --namespace production --values ./charts/app/values.yaml > .rendered/app.yaml; fi
- name: Scan IaC
run: |
trivy config . .rendered --config .trivy.yaml
You should see the workflow fail on a pull request when a HIGH/CRITICAL finding is introduced.
Step 6: Block deployment on scan failure
Put the scan job before any apply/deploy job and require it to pass. In GitHub Actions, make the deploy job depend on the scan job:
deploy:
runs-on: ubuntu-latest
needs: trivy-config
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- run: echo "deploy step here"
You should see the deploy job marked skipped when the scan job fails.
View as text
[Pull Request] ---- 1. push/change ----> [CI Scan] [CI Scan] ---- 2. pass ----> [Deploy Job] [CI Scan] ---- 3. fail ----> [Blocked Merge/Deploy]
Step 7: Handle one known false positive explicitly
If a finding is accepted temporarily, skip that exact rule ID instead of lowering severity globally. Update .trivy.yaml:
severity:
- HIGH
- CRITICAL
exit-code: 1
format: table
misconfiguration:
scanners:
- terraform
- kubernetes
- helm
skip-dirs:
- .terraform
- vendor
- node_modules
skip-checks:
- AVD-KSV-0012
Then rerun:
trivy config . --config .trivy.yaml
You should see that specific rule omitted while other HIGH/CRITICAL findings still fail the run.
Verify it works
Run a local scan and check the exit code:
trivy config . --config .trivy.yaml; echo $?
Expected results:
0when no HIGH/CRITICAL findings exist1when at least one HIGH/CRITICAL finding exists
To prove CI blocks deployment, add a deliberate bad manifest in a test branch:
apiVersion: v1
kind: Pod
metadata:
name: bad-pod
spec:
containers:
- name: app
image: nginx:latest
securityContext:
privileged: true
Commit and push it. The scan job should fail, with output shaped like:
k8s/bad-pod.yaml (kubernetes)
===============================
Tests: 8 (SUCCESSES: 7, FAILURES: 1)
Failures: 1 (HIGH: 1)
AVD-KSV-0017 (HIGH): Container is privileged
Your deploy/apply job should not run.
Common pitfalls
Scanning raw Helm templates instead of rendered manifests
Mistake: running trivy config charts/app/ directly on templates with many unresolved values.
Symptom: noisy or missing findings because the scanner cannot evaluate final resource settings.
Fix: render first with helm template ... > .rendered/app.yaml and scan .rendered/app.yaml.
Letting .terraform directories get scanned
Mistake: scanning vendored modules and generated files under .terraform/.
Symptom: duplicate findings, slow CI, and failures from code you do not own directly.
Fix: add .terraform to skip-dirs in .trivy.yaml.
Using --exit-code 0 in CI from copied examples
Mistake: keeping a demo flag that always returns success.
Symptom: findings are printed, but the pipeline still goes green and deploys.
Fix: set exit-code: 1 in .trivy.yaml or pass --exit-code 1 on the command line.
Lowering severity to avoid one bad finding
Mistake: changing the threshold to CRITICAL only because one HIGH issue is inconvenient.
Symptom: the pipeline stops catching real high-risk issues like open security groups or privileged containers.
Fix: keep HIGH,CRITICAL and add the exact rule ID to skip-checks with a review note in the PR.
Scanning only changed files in monorepos without dependency context
Mistake: wiring CI to scan only the diff paths.
Symptom: missed findings when a shared module or Helm values file changes behavior outside the changed file list.
Fix: scan the full IaC directories (terraform/, k8s/, rendered Helm output) on every PR; optimize later with path filters only at the job trigger level.
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI