Terraform Tutorial · Modules and Quality · Lesson 24 of 25
Terraform CI/CD and Best Practices
Run Terraform safely in a team: plans on pull requests, applies from CI, formatting and security checks, and the habits that keep infrastructure code healthy.
A typical pipeline
- On every pull request:
terraform fmt -check,terraform validate,terraform test, a security scan (Checkov, Trivy or tfsec), thenterraform plan, with the plan posted as a PR comment. - Review the plan, not just the code.
- On merge:
terraform applyof the saved plan file (plan -out=tfplan, thenapply tfplan), with a manual approval for production.
terraform init -input=false
terraform plan -input=false -out=tfplan
terraform apply -input=false tfplan
CI should authenticate with short-lived credentials (for example GitHub Actions OIDC to an AWS IAM role), not stored access keys.
Best practices checklist
- Remote state with locking and versioning; one state per environment.
- Pin Terraform, provider and module versions; commit
.terraform.lock.hcl. - Small, composable modules; no copy-paste between environments.
- Consistent naming and tags (
ManagedBy = "terraform"). - Secrets come from a secrets manager, never from
.tfor.tfvarsin Git. - Never change Terraform-managed resources by hand; if you must,
plan -refresh-onlyafterwards. - Keep plans small: many small applies are safer than one huge one.
locals {
required_tags = ["Project", "Environment", "ManagedBy"]
tags = {
Project = "shop"
Environment = "prod"
ManagedBy = "terraform"
}
missing = [for t in local.required_tags : t if !contains(keys(local.tags), t)]
}
output "tag_policy" {
value = length(local.missing) == 0 ? "all required tags present" : "missing: ${join(", ", local.missing)}"
}
Output
tag_policy = "all required tags present"
Run this on your computer — see “Get Started” for setup.
Test yourself
Why apply a saved plan file in CI?
It is fasterSo exactly the reviewed changes are appliedIt skips state lockingIt avoids init
Show answer
Answer: So exactly the reviewed changes are applied