Tools, Workflow, Security, Testing, CI/CD, Automation, AI Assistance and Production Best Practices
The End-to-End Practical Handbook for Modern Terraform Engineering
Edition: September 2026
Audience: Terraform Developers, DevOps Engineers, SREs, Platform Engineers, Cloud Engineers, DevSecOps Engineers, Module Authors, Infrastructure Architects, Engineering Managers
Executive Standard
A professional Terraform workflow should never be:
Write Terraform
โ
terraform apply
The production engineering workflow should instead look like:
Developer
โ
IDE + Language Intelligence
โ
AI / Registry Assistance
โ
Terraform Code
โ
Format
โ
Validate
โ
Lint
โ
Security Scan
โ
Test
โ
Documentation
โ
Cost Analysis
โ
Pre-Commit
โ
Git
โ
CI
โ
Terraform Plan
โ
Policy Validation
โ
Human Review
โ
Approval
โ
Remote Apply
โ
Verification
โ
Monitoring / Drift Detection
That difference is essentially the difference between using Terraform and operating a Terraform engineering platform.
As of September 2026, Terraform 1.16.0 is the current stable release, while 1.17 builds remain pre-release. Production organizations should normally stay on stable versions rather than automatically consuming alpha or RC builds.
PART 1 โ TERRAFORM DEVELOPMENT ECOSYSTEM
The Complete Developer Toolchain
Developer
โ
VS Code / Cursor / Claude Code
โ
HashiCorp Terraform Extension
โ
terraform-ls
โ
Terraform MCP Server
โ
tenv
โ
Terraform CLI
โ
terraform fmt
terraform validate
terraform test
โ
TFLint
โ
Trivy / Checkov
โ
terraform-docs
โ
Infracost
โ
pre-commit-terraform
โ
Git
โ
GitHub / GitLab
โ
GitHub Actions / GitLab CI
โ
HCP Terraform / Terraform Enterprise
โ
Sentinel / OPA
โ
Cloud Infrastructure
Responsibilities of Each Layer
| Layer | Responsibility |
|---|---|
| IDE | Developer editing environment |
| Terraform Extension | Terraform-aware editing |
| terraform-ls | Language intelligence |
| Terraform MCP | Current Registry/provider/module knowledge for AI |
| tenv | Terraform/tool version management |
| Terraform CLI | Core Terraform execution engine |
| terraform fmt | Canonical formatting |
| terraform validate | Terraform configuration validation |
| terraform test | Native Terraform testing |
| TFLint | Static linting and provider-aware checks |
| Trivy | Security/misconfiguration scanning |
| Checkov | Additional policy/compliance scanning |
| terraform-docs | Documentation generation |
| Infracost | Cost and FinOps feedback |
| pre-commit | Local automation |
| Git | Version control |
| GitHub/GitLab | Collaboration and pull requests |
| CI/CD | Automated quality gates |
| Atlantis | PR-driven Terraform execution |
| HCP Terraform | State, remote execution and governance |
| Terraform Enterprise | Self-hosted Terraform platform |
| Sentinel / OPA | Policy-as-code |
| Renovate | Dependency/version automation |
| Monitoring | Verification and drift awareness |
A mature platform deliberately assigns one primary responsibility to each tool. Adding multiple tools that solve the exact same problem generally increases maintenance and alert fatigue rather than quality.
PART 2 โ CORE TERRAFORM DEVELOPMENT TOOLS
1. Terraform CLI
What
Terraform CLI is the primary Terraform execution engine.
It:
- Parses HCL.
- Downloads providers/modules.
- Builds dependency graphs.
- Reads and writes Terraform state.
- Generates execution plans.
- Executes infrastructure changes.
- Performs imports and state operations.
- Runs native tests.
Core Commands
terraform init
Initializes a Terraform working directory.
terraform init
Use after:
git clone
new provider
new module
backend change
provider version change
Code language: JavaScript (javascript)
For validation-only automation:
terraform init -backend=false
Code language: JavaScript (javascript)
terraform init installs providers/modules and initializes the backend, and HashiCorp documents it as safe to rerun as configuration evolves.
terraform fmt
Canonical Terraform formatting:
terraform fmt
terraform fmt -recursive
terraform fmt -check
terraform fmt -check -recursive
Recommended:
Developer machine โ terraform fmt -recursive
CI โ terraform fmt -check -recursive
CI should fail rather than modify code.
terraform validate
terraform validate
Checks:
- HCL syntax
- Attribute structures
- References
- Internal Terraform consistency
- Provider/module schema compatibility where available
It does not prove that:
- IAM permissions are sufficient.
- An AWS region supports a resource.
- A subnet actually has capacity.
- Security configuration is acceptable.
- Infrastructure deployment will succeed.
HashiCorp describes validate as checking syntax and internal consistency rather than remote APIs.
terraform plan
terraform plan
Save the plan:
terraform plan -out=tfplan
Machine-readable representation:
terraform show -json tfplan > tfplan.json
Code language: CSS (css)
Production rule:
Review the actual execution plan before applying infrastructure.
Particularly inspect:
+ create
~ update
- destroy
-/+ replace
-/+ is one of the most important symbols in Terraform review because it indicates replacement.
terraform apply
Interactive:
terraform apply
Approved saved plan:
terraform apply tfplan
For production, prefer:
CI / HCP Terraform
โ
authoritative plan
โ
approval
โ
apply exact approved plan
Avoid developers casually applying production infrastructure from laptops.
terraform destroy
terraform destroy
This should usually be heavily restricted for production environments.
For temporary integration environments it can be legitimate:
terraform apply -auto-approve
run-tests
terraform destroy -auto-approve
terraform console
terraform console
Code language: JavaScript (javascript)
Excellent for evaluating:
- expressions
- locals
- functions
- CIDR calculations
- maps
- lists
- transformations
More in Part 17.
terraform output
terraform output
terraform output vpc_id
terraform output -json
Useful for:
- CI integrations
- debugging
- scripts
- downstream automation
Be careful with sensitive outputs.
terraform show
terraform show
terraform show tfplan
terraform show -json tfplan
Machine-readable plan JSON is particularly useful for:
OPA
Checkov
Infracost
custom CI analysis
terraform providers
terraform providers
terraform providers schema -json
terraform providers lock
terraform providers lock is useful when producing lock information for multiple platforms or provider mirrors.
terraform state
Examples:
terraform state list
terraform state show aws_instance.app
terraform state mv OLD NEW
terraform state rm ADDRESS
Code language: PHP (php)
These commands are powerful and dangerous.
Prefer configuration-based approaches such as:
moved {
from = aws_instance.old
to = aws_instance.new
}
Code language: JavaScript (javascript)
over manual state manipulation whenever possible.
terraform import
Modern preferred pattern:
import {
to = aws_s3_bucket.logs
id = "company-production-logs"
}
Code language: JavaScript (javascript)
Then:
terraform plan
terraform apply
CLI import remains useful:
terraform import aws_s3_bucket.logs company-production-logs
Code language: JavaScript (javascript)
terraform test
terraform test
Native Terraform testing is now a first-class part of a professional Terraform workflow.
Test files:
*.tftest.hcl
*.tftest.json
Code language: CSS (css)
HashiCorp warns that tests containing apply operations can create real infrastructure and therefore incur costs.
2. VS Code + HashiCorp Terraform Extension
Why
A developer should receive feedback while writing code, not after pushing a pull request.
The official HashiCorp Terraform extension provides:
- syntax highlighting
- IntelliSense
- completion
- diagnostics
- code navigation
- formatting
- module explorer
- provider awareness
- Terraform test syntax
- Terraform Stacks support
- Terraform Policy support
It uses Terraform Language Server underneath.
Recommended VS Code Settings
.vscode/settings.json
{
"[terraform]": {
"editor.defaultFormatter": "hashicorp.terraform",
"editor.formatOnSave": true,
"editor.formatOnSaveMode": "file"
},
"[terraform-vars]": {
"editor.defaultFormatter": "hashicorp.terraform",
"editor.formatOnSave": true,
"editor.formatOnSaveMode": "file"
},
"[terraform-test]": {
"editor.defaultFormatter": "hashicorp.terraform",
"editor.formatOnSave": true,
"editor.formatOnSaveMode": "file"
},
"terraform.languageServer.enable": true
}
Code language: JSON / JSON with Comments (json)
The official extension recommends file-level format-on-save because terraform fmt formats complete files rather than arbitrary changed ranges.
Useful optional settings:
{
"terraform.validation.enableEnhancedValidation": true,
"terraform.experimentalFeatures.prefillRequiredFields": true,
"terraform.codelens.referenceCount": true
}
Code language: JSON / JSON with Comments (json)
For large repositories:
{
"terraform.languageServer.rootModules": [
"/environments/development",
"/environments/staging",
"/environments/production"
],
"terraform.languageServer.ignoreDirectoryNames": [
".terraform",
".terragrunt-cache"
]
}
Code language: JSON / JSON with Comments (json)
Recommended Extensions
Core:
HashiCorp Terraform
GitLens
YAML
EditorConfig
Depending on workflow:
GitHub Pull Requests
GitLab Workflow
Trivy
Infracost
Docker
Kubernetes
Do not install three competing Terraform formatters or Terraform language extensions simultaneously.
3. terraform-ls
What
terraform-ls is HashiCorp’s Terraform Language Server implementation.
It implements the Language Server Protocol so editors can understand Terraform structure.
Capabilities include:
Completion
Diagnostics
Navigation
Hover information
References
Modules
Providers
Terraform schema awareness
It is actively maintained by HashiCorp.
Do VS Code Users Need to Install It Manually?
Usually:
NO
The official HashiCorp VS Code Terraform extension includes/manages the language server.
Manual installation is mainly useful when:
- another LSP-compatible editor is used
- centralized custom tooling requires it
- debugging language-server behavior
- an editor integration requires an explicit binary
Architecture:
VS Code
โ
HashiCorp Terraform Extension
โ
terraform-ls
โ
Terraform CLI + Provider Schemas
4. Terraform MCP Server
Why MCP Matters
Traditional AI coding:
Developer
โ
AI Model
โ
Model training knowledge
โ
Terraform code
Problem:
The AI may invent:
- obsolete arguments
- removed resources
- incorrect module versions
- invalid provider syntax
Modern approach:
Developer Request
โ
AI Coding Agent
โ
Terraform MCP Server
โ
Terraform Registry
โ
Current Provider / Module / Policy Information
โ
Generated Terraform
HashiCorp’s Terraform MCP Server can expose current Terraform Registry provider, module and policy information to AI clients and can optionally interact with HCP Terraform/Terraform Enterprise.
Strong Security Recommendation
For developer AI agents, default to:
Registry lookup โ ENABLED
Documentation lookup โ ENABLED
Module discovery โ ENABLED
Provider lookup โ ENABLED
Workspace changes โ DISABLED unless specifically required
Terraform operations โ DISABLED unless specifically required
Production apply โ NEVER casually delegated
The Terraform MCP Server explicitly gates Terraform operational capabilities and supports secure local deployment patterns.
Docker Setup
docker run -i --rm hashicorp/terraform-mcp-server
Example VS Code MCP configuration:
{
"mcp": {
"servers": {
"terraform": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"hashicorp/terraform-mcp-server"
]
}
}
}
}
Code language: JSON / JSON with Comments (json)
Public Registry queries do not require an HCP token; private registry/HCP/TFE access requires appropriate authentication.
Interestingly, the current official Terraform VS Code extension itself also exposes:
{
"terraform.mcp.server.enable": true
}
Code language: JSON / JSON with Comments (json)
as an MCP integration option.
Useful Terraform AI Prompts
Using the Terraform Registry through Terraform MCP,
find the current schema for aws_eks_cluster and generate
the smallest production-safe example compatible with the
provider version in this repository.
Code language: JavaScript (javascript)
Inspect the provider and module versions used by this repository.
Do not invent attributes. Verify every unfamiliar argument using
Terraform MCP before generating code.
Code language: JavaScript (javascript)
Review this Terraform module for deprecated provider attributes.
Return only changes supported by the provider version locked in
.terraform.lock.hcl.
Code language: CSS (css)
Generate terraform test plan-only tests for this module.
Do not create real cloud resources.
Code language: JavaScript (javascript)
AI Is Excellent For
Boilerplate
Variables
Outputs
Tests
Documentation
Refactoring
Module scaffolding
Explaining plans
Registry research
AI Must Not Be Blindly Trusted For
IAM
security policies
network exposure
state operations
resource destruction
provider upgrades
production applies
blast-radius decisions
AI accelerates Terraform engineering.
It does not replace Terraform engineering judgment.
PART 3 โ TERRAFORM VERSION MANAGEMENT
5. tenv
Problem
This is fragile:
Laptop Terraform = 1.16
CI Terraform = 1.14
Engineer B = 1.15
Production agent = 1.13
Different Terraform versions may:
- interpret features differently
- modify lock files
- reject syntax
- produce unexpected workflows
tenv
tenv manages:
- Terraform
- OpenTofu
- Terragrunt
- Terramate
- related IaC tooling
It is also positioned as the successor to tfenv/tofuenv for broader version management.
macOS
brew install tenv
Windows
winget install Tofuutils.Tenv
Code language: CSS (css)
or:
choco install tenv
Usage
tenv tf install 1.16.0
tenv tf use 1.16.0
terraform version
Code language: CSS (css)
Project file:
.terraform-version
Code language: CSS (css)
1.16.0
Code language: CSS (css)
Terraform itself should also declare:
terraform {
required_version = "~> 1.16.0"
}
Code language: JavaScript (javascript)
The version manager gives the developer the correct binary.
required_version prevents incompatible binaries from being used.
You want both.
tenv vs tfenv vs asdf vs mise
| Tool | Best Fit |
|---|---|
| tenv | Terraform/OpenTofu/Terragrunt-focused teams |
| tfenv | Simple Terraform-only version switching |
| asdf | Organizations already standardizing many runtimes |
| mise | Modern multi-language/tool version management |
GOLD Recommendation
For a Terraform-centric engineering platform:
tenv
If the company already standardizes all development tooling through mise/asdf:
Use the organizational standard instead of introducing another manager.
Code language: PHP (php)
PART 4 โ TERRAFORM CODE QUALITY
6. terraform fmt
Terraform code formatting must be deterministic.
terraform fmt
terraform fmt -recursive
terraform fmt -check
terraform fmt -check -recursive
Recommended:
IDE โ format on save
Commit โ terraform_fmt hook
CI โ terraform fmt -check -recursive
Formatting should never consume meaningful code-review time.
7. terraform validate
Run:
terraform init -backend=false
terraform validate
Code language: JavaScript (javascript)
Think of validate as:
"Is this internally valid Terraform configuration?"
Code language: JSON / JSON with Comments (json)
Not:
"Is this secure?"
"Will AWS accept it?"
"Is this architecture good?"
Code language: JSON / JSON with Comments (json)
That is why the following progression exists:
terraform validate
โ
TFLint
โ
Trivy
โ
terraform test
โ
terraform plan
8. TFLint
What
TFLint is a pluggable Terraform static-analysis framework.
It can detect:
- deprecated syntax
- unused declarations
- naming problems
- best-practice violations
- provider-specific mistakes
- invalid AWS/Azure/GCP values
- incorrect instance types
Installation
macOS:
brew install terraform-linters/tap/tflint
Windows:
winget install -e --id TerraformLinters.tflint
Code language: CSS (css)
Production .tflint.hcl
Example for AWS:
tflint {
required_version = ">= 0.64.0"
}
config {
format = "compact"
call_module_type = "local"
force = false
disabled_by_default = false
}
plugin "terraform" {
enabled = true
preset = "recommended"
}
plugin "aws" {
enabled = true
version = "0.48.0"
source = "github.com/terraform-linters/tflint-ruleset-aws"
}
Code language: JavaScript (javascript)
At the time of this guide, TFLint 0.64.x is current and the AWS plugin line is 0.48.x.
Use the cloud plugin applicable to your repository.
Do not blindly load:
AWS plugin
Azure plugin
Google plugin
into a project that only uses AWS.
Run
tflint --init
tflint
Monorepository:
tflint --recursive --init
tflint --recursive
terraform validate vs TFLint
terraform validate
โ
Terraform correctness
TFLint
โ
Terraform quality + provider-aware static analysis
Code language: JavaScript (javascript)
Both provide value.
PART 5 โ TERRAFORM SECURITY
9. Trivy
What
Trivy is the recommended default security scanner for this stack because it can scan far more than Terraform alone.
It covers:
- IaC misconfigurations
- exposed infrastructure
- encryption configuration
- IAM risks
- cloud resources
- Kubernetes
- secrets
- containers
- dependencies
Terraform HCL and Terraform plan scanning are supported.
Installation
macOS:
brew install trivy
Terraform Scan
trivy config .
Fail CI on serious findings:
trivy config \
--exit-code 1 \
--severity HIGH,CRITICAL \
.
Code language: PHP (php)
Plan scan:
terraform plan -out=tfplan
trivy config tfplan
JSON:
terraform show -json tfplan > tfplan.json
trivy config tfplan.json
Code language: CSS (css)
What It Should Catch
Examples:
S3 bucket public access
unencrypted storage
0.0.0.0/0 administrative ports
weak IAM
public databases
insecure Kubernetes settings
missing encryption
embedded secrets
Code language: PHP (php)
10. Checkov
Checkov is another mature IaC security and policy scanner.
Capabilities include:
- Terraform HCL scanning
- plan scanning
- compliance rules
- graph-based checks
- custom policies
- multi-IaC support
Installation
pipx install checkov
or:
pip install checkov
Terraform
checkov -d .
Plan:
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json
checkov -f tfplan.json
Plan JSON can contain sensitive data. Treat it like a sensitive CI artifact.
Trivy or Checkov?
Use Trivy Only
Good default when you want:
One scanner
Terraform
Kubernetes
containers
dependencies
secrets
simple CI
Use Checkov Only
Consider it when:
Checkov-specific policies are already standardized
Prisma Cloud integration matters
graph-aware policies are important
Use Both
Only when:
There is demonstrably different policy coverage
AND
the organization has ownership for suppressions and alert tuning.
Bad strategy:
Trivy + Checkov + tfsec + another scanner
because "more scanners = more security"
Code language: JavaScript (javascript)
That usually creates duplicate alerts.
Also note: the current pre-commit-terraform project explicitly treats its tfsec hook as deprecated and recommends Trivy instead.
PART 6 โ TERRAFORM TESTING
11. terraform test
A Terraform module should increasingly be treated like software.
Directory:
modules/vpc/
โโโ main.tf
โโโ variables.tf
โโโ outputs.tf
โโโ tests/
โโโ main.tftest.hcl
Example:
variables {
environment = "test"
vpc_cidr = "10.20.0.0/16"
}
run "plan_vpc" {
command = plan
assert {
condition = aws_vpc.main.cidr_block == "10.20.0.0/16"
error_message = "VPC CIDR does not match the requested value."
}
assert {
condition = aws_vpc.main.enable_dns_support
error_message = "DNS support must be enabled."
}
}
Code language: JavaScript (javascript)
Run:
terraform test
Specific test:
terraform test -filter=tests/main.tftest.hcl
Testing Layers
terraform validate
โ
Structural correctness
TFLint
โ
Static quality
terraform test
โ
Module behavior / assertions
Integration Test
โ
Real infrastructure behavior
Code language: PHP (php)
Plan Tests
Preferred for most PR checks:
run "validate_configuration" {
command = plan
}
Code language: JavaScript (javascript)
Fast and low-risk.
Apply Tests
run "deploy_test" {
command = apply
}
Code language: JavaScript (javascript)
Apply tests may create infrastructure.
Use:
isolated test account
short-lived credentials
tight quotas
automatic cleanup
cost controls
Terraform supports provider mocking, which can further reduce dependence on live APIs in appropriate tests.
Terratest
Terratest uses Go to:
- create infrastructure
- query the deployed system
- assert behavior
- destroy infrastructure
Excellent when you need to prove:
Load balancer responds
EC2 boots correctly
DNS resolves
database connects
Kubernetes service becomes healthy
Kitchen-Terraform
Useful historically and in organizations already standardized around Kitchen ecosystems.
For new general Terraform development, native tests and Terratest tend to be easier defaults.
GOLD Standard
Native terraform test
โ
default
Terratest
โ
when real infrastructure behavior must be verified
Code language: JavaScript (javascript)
PART 7 โ TERRAFORM DOCUMENTATION
12. terraform-docs
Documentation that depends on humans eventually becomes stale.
terraform-docs extracts:
- requirements
- providers
- modules
- resources
- inputs
- outputs
and generates documentation automatically.
Installation
brew install terraform-docs
README Before
# VPC Module
Creates a VPC.
Code language: PHP (php)
README Template
# VPC Module
Creates the organization's standard VPC.
<!-- BEGIN_TF_DOCS -->
<!-- END_TF_DOCS -->
## Usage
Example usage goes here.
Code language: HTML, XML (xml)
Run:
terraform-docs markdown table \
--output-file README.md \
--output-mode inject \
.
Code language: CSS (css)
The inject mode replaces content between its documentation markers while preserving manually written README sections.
.terraform-docs.yml
formatter: "markdown table"
sections:
show:
- requirements
- providers
- modules
- resources
- inputs
- outputs
sort:
enabled: true
by: name
output:
file: README.md
mode: inject
template: |-
<!-- BEGIN_TF_DOCS -->
{{ .Content }}
<!-- END_TF_DOCS -->
Code language: HTML, XML (xml)
GOLD Rule
Humans maintain:
Purpose
architecture
examples
operational guidance
terraform-docs maintains:
Inputs
outputs
providers
requirements
resources
modules
PART 8 โ TERRAFORM COST MANAGEMENT
13. Infracost
Infrastructure code has financial consequences.
Traditional review:
PR changes RDS instance
โ
Looks technically correct
โ
Merge
โ
Cloud bill surprise
FinOps-aware review:
Terraform Change
โ
Terraform / IaC Analysis
โ
Infracost
โ
Monthly Cost Difference
โ
PR Review
Installation
brew install infracost
Setup:
infracost auth login
Current CLI workflow:
infracost scan
Plan:
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json
infracost scan tfplan.json
Current Infracost tooling also includes:
infracost inspect
infracost ci setup
infracost ci setup --ci-pipeline
The newer workflow centers on scan and current VCS integrations rather than only the older breakdown workflow.
GitHub
Infracost currently recommends its GitHub App when permitted.
For GitHub Actions:
- uses: infracost/actions/diff@v4
with:
api-key: ${{ secrets.INFRACOST_API_KEY }}
base-path: base
head-path: head
The older setup action remains available but is considered the legacy integration path for new deployments.
Good Cost Policies
Examples:
Block >$5,000 monthly increase without FinOps approval
Warn >$500
Flag untagged billable resources
Flag expensive instance-family changes
Require owners for high-cost resources
Code language: PHP (php)
Infracost should provide feedback.
It should not automatically decide architecture.
PART 9 โ GIT AUTOMATION
14. pre-commit-terraform
One of the highest-value productivity improvements is moving failures from:
CI after 10 minutes
to:
developer laptop before commit
Workflow
git commit
โ
terraform fmt
โ
terraform validate
โ
TFLint
โ
Trivy
โ
terraform-docs
โ
Commit Accepted
Installation
brew install pre-commit
Install hooks:
pre-commit install
Run manually:
pre-commit run --all-files
Production .pre-commit-config.yaml
pre-commit-terraform v1.108.1 is the current release as of this guide.
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.108.1
hooks:
- id: terraform_fmt
- id: terraform_validate
- id: terraform_tflint
args:
- --args=--config=__GIT_WORKING_DIR__/.tflint.hcl
- id: terraform_trivy
args:
- --args=--severity=HIGH,CRITICAL
- --args=--exit-code=1
- id: terraform_docs
args:
- --hook-config=--path-to-file=README.md
- --hook-config=--add-to-existing-file=true
Code language: PHP (php)
The project provides hooks for formatting, validation, TFLint, Trivy and terraform-docs.
What Runs Locally?
Fast checks:
fmt
validate
TFLint
targeted Trivy
terraform-docs
What Runs in CI?
Everything local plus:
terraform test
full security scan
terraform plan
plan security analysis
Infracost
policy
integration tests
Important principle:
Pre-commit improves developer feedback. CI remains authoritative.
Never trust local hooks as the only gate because they can be skipped.
PART 10 โ TERRAFORM PROJECT STRUCTURE
A practical repository:
terraform/
โโโ modules/
โ โโโ vpc/
โ โ โโโ main.tf
โ โ โโโ variables.tf
โ โ โโโ outputs.tf
โ โ โโโ versions.tf
โ โ โโโ README.md
โ โ โโโ tests/
โ โ โโโ main.tftest.hcl
โ โ
โ โโโ eks/
โ โโโ rds/
โ โโโ iam/
โ
โโโ environments/
โ โโโ development/
โ โ โโโ main.tf
โ โ โโโ providers.tf
โ โ โโโ versions.tf
โ โ โโโ backend.tf
โ โ โโโ terraform.tfvars
โ โ
โ โโโ staging/
โ โโโ production/
โ
โโโ .github/
โ โโโ workflows/
โ โโโ terraform.yml
โ
โโโ .pre-commit-config.yaml
โโโ .tflint.hcl
โโโ .terraform-docs.yml
โโโ .terraform-version
โโโ .gitignore
โโโ README.md
Root Module
A deployable Terraform configuration.
Example:
environments/production
It owns a state.
Child Module
Reusable implementation:
modules/vpc
modules/rds
modules/eks
A child module should not normally own backend state.
Environment Isolation
Prefer:
development root/state/account
staging root/state/account
production root/state/account
over relying on Terraform CLI workspaces as the main isolation mechanism for substantially different production environments.
The strongest boundary is usually:
separate cloud account/subscription/project
+
separate Terraform state
+
separate permissions
.gitignore
.terraform/
*.tfstate
*.tfstate.*
crash.log
crash.*.log
*.tfplan
tfplan*
.terragrunt-cache/
# Secrets/local variable files
*.auto.tfvars
*.auto.tfvars.json
# Keep examples
!*.tfvars.example
Code language: PHP (php)
Do not ignore:
.terraform.lock.hcl
Code language: CSS (css)
Commit it for root modules.
HashiCorp recommends committing the provider dependency lock file so provider selections/checksums are reproducible. Modules themselves are not locked there, so module versions still need explicit constraints.
Repository Strategies
Monorepo
one repo
โโโ networking
โโโ security
โโโ data
โโโ application-platform
Advantages:
- atomic changes
- easier shared standards
- centralized tooling
Limitations:
- CI complexity
- broad permissions
- potentially large blast radius
Multiple Infrastructure Repositories
Advantages:
- stronger ownership boundaries
- simpler permissions
- smaller CI scope
Limitations:
- duplicated tooling
- cross-repository dependency coordination
One Repository Per Module
Excellent for externally reusable/platform modules.
Advantages:
- independent release lifecycle
- semantic versions
- clean module ownership
One Repository Per Environment
Provides strong separation but often duplicates code excessively.
GOLD Pattern for Larger Organizations
Reusable modules
โ
versioned module repositories or registry
โ
Live infrastructure repositories
โ
small root modules
โ
separate environment state
Code language: JavaScript (javascript)
PART 11 โ TERRAGRUNT
15. Terragrunt
Why It Exists
Terraform modules solve reusable infrastructure logic.
Terragrunt primarily helps solve repeated live configuration and orchestration.
Example repeated across 100 roots:
backend configuration
provider configuration
account settings
region settings
module source versions
dependency wiring
Code language: JavaScript (javascript)
Terragrunt lets these be centralized.
Example
live/
โโโ root.hcl
โ
โโโ development/
โ โโโ account.hcl
โ โโโ us-east-1/
โ โ โโโ vpc/
โ โ โ โโโ terragrunt.hcl
โ โ โโโ eks/
โ โ โโโ terragrunt.hcl
โ
โโโ staging/
โโโ production/
Root:
remote_state {
backend = "s3"
config = {
bucket = "company-terraform-state"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "us-east-1"
encrypt = true
use_lockfile = true
}
}
generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
provider "aws" {
region = "us-east-1"
}
EOF
}
Code language: JavaScript (javascript)
Unit:
include "root" {
path = find_in_parent_folders("root.hcl")
}
terraform {
source = "../../../../modules/vpc"
}
Code language: PHP (php)
Modern Terragrunt uses workflows such as:
terragrunt plan
terragrunt run --all plan
terragrunt run --all apply
The modern run --all model is reflected in current Terragrunt documentation and examples.
Terragrunt reached 1.0 in March 2026, introducing stronger stability guarantees for the 1.x line.
Terraform vs Terraform + Terragrunt
Terraform
โ
Best when:
small number of root modules
simple environments
HCP Terraform handles orchestration
little duplicated live configuration
Terraform + Terragrunt
โ
Best when:
many accounts
many regions
many roots
repeated environment configuration
complex dependencies
When Terragrunt Is Unnecessary
Do not introduce it simply because the infrastructure uses Terraform.
For:
3 root modules
1 AWS account
1 region
simple CI
plain Terraform is generally easier.
Terragrunt is an abstraction layer.
Abstractions must pay rent.
PART 12 โ CI/CD
16. GitHub Actions
Pipeline
Checkout
โ
Setup Terraform
โ
terraform fmt -check
โ
terraform init
โ
terraform validate
โ
TFLint
โ
Trivy
โ
terraform test
โ
Cloud OIDC
โ
terraform plan
โ
Cost Analysis
Authentication
Never:
AWS_ACCESS_KEY_ID: AKIA...
AWS_SECRET_ACCESS_KEY: ...
Code language: HTTP (http)
Prefer:
GitHub OIDC
โ
AWS STS
โ
temporary credentials
โ
IAM role
GitHub’s AWS credential action explicitly supports OIDC, eliminating the need for long-lived AWS secrets in GitHub.
Realistic PR Pipeline
name: Terraform CI
on:
pull_request:
paths:
- "**/*.tf"
- "**/*.tfvars"
- "**/*.tftest.hcl"
- ".terraform.lock.hcl"
permissions:
contents: read
id-token: write
pull-requests: write
env:
TF_IN_AUTOMATION: "true"
TF_INPUT: "false"
jobs:
quality:
runs-on: ubuntu-latest
defaults:
run:
working-directory: environments/development
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Terraform
uses: hashicorp/setup-terraform@v4
with:
terraform_version: "1.16.0"
- name: Terraform Format
run: terraform fmt -check -recursive
working-directory: .
- name: Terraform Init for Validation
run: terraform init -backend=false
- name: Terraform Validate
run: terraform validate -no-color
- name: Setup TFLint
uses: terraform-linters/setup-tflint@v6
with:
tflint_version: v0.64.0
cache: true
- name: TFLint Init
run: tflint --init
working-directory: .
- name: TFLint
run: tflint --recursive --format=compact
working-directory: .
- name: Trivy IaC Scan
uses: aquasecurity/trivy-action@v0.36.0
with:
scan-type: config
scan-ref: .
severity: HIGH,CRITICAL
exit-code: "1"
- name: Terraform Test
run: terraform test
Code language: PHP (php)
Current major lines include:
hashicorp/setup-terraform v4
terraform-linters/setup-tflint v6
Trivy Action v0.36.x
actions/checkout v7
Production Supply-Chain Hardening
Readable documentation commonly shows:
uses: vendor/action@v4
Code language: HTTP (http)
Enterprise production workflows should consider pinning third-party actions to immutable full commit SHAs and letting Renovate update those pins automatically.
Authoritative Plan Job
plan:
needs: quality
runs-on: ubuntu-latest
defaults:
run:
working-directory: environments/development
steps:
- uses: actions/checkout@v7
- uses: hashicorp/setup-terraform@v4
with:
terraform_version: "1.16.0"
- name: Authenticate to AWS using OIDC
uses: aws-actions/configure-aws-credentials@v6.2.3
with:
role-to-assume: arn:aws:iam::123456789012:role/github-terraform-plan
aws-region: us-east-1
- name: Terraform Init
run: terraform init
- name: Terraform Plan
run: terraform plan -out=tfplan -no-color
- name: Export Plan JSON
run: terraform show -json tfplan > tfplan.json
The role shown is an example placeholderโnot a credential.
Apply
Production apply should normally happen only after:
PR merged
โ
branch protection passed
โ
authoritative plan
โ
policy passed
โ
environment approval
โ
apply
If using HCP Terraform, an even cleaner division is:
GitHub Actions
โ
lint/security/tests
HCP Terraform
โ
authoritative remote plan
policy
approval
apply
state
audit
Avoid two independent systems both believing they own production execution.
17. GitLab CI
Equivalent architecture:
validate
โ
lint
โ
security
โ
test
โ
plan
โ
approval
โ
apply
Skeleton:
stages:
- validate
- security
- test
- plan
- deploy
variables:
TF_IN_AUTOMATION: "true"
TF_INPUT: "false"
validate:
stage: validate
script:
- terraform fmt -check -recursive
- terraform init -backend=false
- terraform validate
- tflint --init
- tflint --recursive
security:
stage: security
script:
- trivy config --exit-code 1 --severity HIGH,CRITICAL .
test:
stage: test
script:
- terraform test
plan:
stage: plan
script:
- terraform init
- terraform plan -out=tfplan
artifacts:
paths:
- tfplan
apply:
stage: deploy
when: manual
script:
- terraform apply tfplan
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
Code language: PHP (php)
For AWS authentication, use GitLab id_tokens + AWS STS rather than static access keys. GitLab’s current OIDC workflow uses ID tokens; the older CI_JOB_JWT_V2 approach has been removed.
PART 13 โ ATLANTIS
18. Atlantis
Atlantis turns Terraform into a pull-request-driven workflow.
Developer:
Open PR
โ
Atlantis detects change
โ
atlantis plan
โ
Plan posted to PR
โ
Review
โ
atlantis apply
Typical commands:
atlantis plan
atlantis apply
Important Capability: Locking
After Atlantis plans a project, that Terraform project/workspace can remain locked against conflicting PRs until the workflow is resolved.
Atlantis vs GitHub Actions
GitHub Actions
โ
general-purpose CI engine
Atlantis
โ
Terraform-specific PR workflow engine
Atlantis vs HCP Terraform
Atlantis
self-hosted PR automation
Terraform-focused
relatively lightweight
Code language: PHP (php)
HCP Terraform
state platform
remote execution
private registry
RBAC
governance
agents
drift detection
policy
audit
Code language: PHP (php)
Use Atlantis when PR-driven Terraform execution is the key requirement and you want to own the platform.
Use HCP Terraform when centralized Terraform platform capabilities are needed.
Do not automatically operate both.
PART 14 โ HCP TERRAFORM
19. HCP Terraform
HCP Terraform is substantially more than remote state.
Capabilities include:
Remote state
State locking
Remote runs
Workspaces
Projects
Variable sets
Agents
Private module registry
Private provider registry
Run tasks
Policy enforcement
VCS integration
RBAC
Team access
Drift detection
Continuous validation
Audit capabilities
Code language: PHP (php)
HCP Terraform remote execution uses managed workers, while agents provide outbound connectivity to private infrastructure.
Organization
HCP Terraform Organization
โ
Projects
โ
Workspaces / Stacks
โ
Runs
โ
State
Workspace
Traditionally manages one root Terraform configuration/state.
Project
Groups related Terraform workspaces/stacks and helps organize access/governance.
Agent
Used when Terraform needs network access to:
private VPCs
private Kubernetes APIs
on-premises systems
private databases
internal services
Code language: PHP (php)
Agent communication is designed around outbound connectivity to HCP Terraform.
Private Registry
Platform Team
โ
approved module
โ
Private Module Registry
โ
Application Teams
Code language: JavaScript (javascript)
This enables:
versioned modules
standard patterns
discoverability
controlled reuse
HCP Terraform also supports private providers.
Run Tasks
Integrate external checks into Terraform runs:
Terraform Plan
โ
Security Run Task
โ
Cost Run Task
โ
Compliance Run Task
โ
Apply
Drift Detection
Desired:
Terraform state/config
=
real infrastructure
Reality:
Terraform
โ
someone changes cloud console
โ
drift
Code language: JavaScript (javascript)
HCP Terraform health assessments support drift detection and continuous validation.
Terraform CLI vs HCP Terraform vs Terraform Enterprise
Terraform CLI
Terraform execution engine
HCP Terraform
HashiCorp-hosted Terraform platform
Terraform Enterprise
Self-hosted distribution of the Terraform platform
Code language: PHP (php)
Terraform Enterprise is targeted at organizations requiring stronger self-hosting, network, compliance, availability or isolated-environment controls.
Recommended Enterprise Workflow
Developer
โ
Git Branch
โ
Static Checks
โ
PR
โ
HCP Terraform Speculative Plan
โ
Security / Cost Run Tasks
โ
OPA / Sentinel
โ
Review
โ
Merge
โ
Authoritative Run
โ
Approval
โ
Apply
โ
State + Audit + Drift Detection
Code language: PHP (php)
PART 15 โ POLICY AS CODE
20. Sentinel
Sentinel is HashiCorp’s policy-as-code framework.
Examples:
Only approved AWS regions
No public S3 buckets
No oversized EC2
Mandatory tags
Encryption required
Approved providers/modules
Code language: PHP (php)
Policy evaluation:
Terraform Plan
โ
Sentinel
โ
Policy Decision
โ
Apply allowed / denied
Sentinel enforcement levels include advisory and mandatory modes.
Example conceptual policy:
Allowed regions:
us-east-1
us-west-2
terraform plan uses eu-west-1
โ
DENY
21. OPA / Conftest
OPA
Open Policy Agent is a general-purpose policy engine.
Policy language:
Rego
Terraform workflow:
terraform plan
โ
terraform show -json
โ
OPA
โ
Rego policy
โ
allow / deny
OPA specifically documents Terraform plan evaluation as a policy pattern.
Conftest
Conftest provides a convenient CLI for applying Rego policies to structured configuration.
conftest test tfplan.json
Code language: CSS (css)
It is therefore better thought of as:
Rego policy runner for configuration
than as a competing policy language.
Sentinel vs OPA vs Conftest
| Tool | Role | Best Fit |
|---|---|---|
| Sentinel | HashiCorp policy language/runtime | HashiCorp-centered governance |
| OPA | General policy engine | Vendor-neutral platform governance |
| Conftest | Rego CLI/testing utility | Local/CI policy validation |
Current HCP Terraform supports both Sentinel and OPA policy sets.
Therefore:
"HCP Terraform requires Sentinel"
Code language: JSON / JSON with Comments (json)
is outdated advice.
Recommendation
HashiCorp-only enterprise platform:
Sentinel remains reasonable.
Multi-platform organization:
OPA/Rego often creates better policy reuse.
Already operating OPA elsewhere:
Reuse OPA.
Do not introduce two policy languages without a concrete need.
PART 16 โ DEPENDENCY MANAGEMENT
22. Renovate
Terraform has several dependencies:
Terraform CLI
Providers
Modules
GitHub Actions
Terragrunt
TFLint plugins
Without automation:
versions age
security fixes get missed
upgrades become huge
Code language: JavaScript (javascript)
Renovate automatically creates dependency-update PRs.
Terraform support includes providers, modules and core version references.
Example renovate.json
{
"extends": [
"config:recommended"
],
"labels": [
"dependencies"
],
"packageRules": [
{
"matchManagers": [
"terraform",
"terraform-version"
],
"groupName": "terraform dependencies"
},
{
"matchManagers": [
"github-actions"
],
"groupName": "github actions"
}
]
}
Code language: JSON / JSON with Comments (json)
Recommended workflow:
Renovate
โ
Provider update PR
โ
terraform init
โ
lock-file update
โ
tests
โ
security
โ
plan
โ
review
Do not automatically merge major provider versions.
23. tfupdate
tfupdate is a focused CLI that can update:
- Terraform/OpenTofu versions
- providers
- modules
- associated lock information
Good for:
local scripts
migration tooling
custom automation
one-time upgrades
For ongoing repository dependency management:
Renovate > tfupdate
because Renovate also manages:
PR creation
scheduling
grouping
release tracking
many other dependency types
PART 17 โ TERRAFORM CONSOLE
terraform console is one of Terraform’s most underrated productivity tools.
Start:
terraform console
Code language: JavaScript (javascript)
Strings
> upper("production")
"PRODUCTION"
Code language: JavaScript (javascript)
> replace("prod-app", "prod", "staging")
"staging-app"
Code language: JavaScript (javascript)
Lists
> length(["a", "b", "c"])
3
Code language: CSS (css)
> element(["dev", "stage", "prod"], 2)
"prod"
Code language: JavaScript (javascript)
Maps
> lookup({dev="t3.micro", prod="m6i.large"}, "prod")
"m6i.large"
Code language: JavaScript (javascript)
Sets
> toset(["a", "a", "b"])
toset([
"a",
"b",
])
Code language: JavaScript (javascript)
Conditional
> true ? "production" : "development"
"production"
Code language: JavaScript (javascript)
CIDR
> cidrsubnet("10.0.0.0/16", 8, 1)
"10.0.1.0/24"
Code language: JavaScript (javascript)
> cidrhost("10.0.1.0/24", 10)
"10.0.1.10"
Code language: JavaScript (javascript)
for Expression
> [for x in ["dev", "prod"] : upper(x)]
[
"DEV",
"PROD",
]
Code language: JavaScript (javascript)
Map Transformation
> {for x in ["dev", "prod"] : x => upper(x)}
{
"dev" = "DEV"
"prod" = "PROD"
}
Code language: JavaScript (javascript)
Use console before writing complicated expressions directly into a module.
PART 18 โ IDE + AI DEVELOPMENT WORKFLOW
Modern workflow:
VS Code / Cursor / Claude Code
โ
Terraform Extension
โ
terraform-ls
โ
Terraform MCP
โ
Terraform Registry
โ
Generated / Edited Terraform
โ
terraform fmt
โ
terraform validate
โ
TFLint
โ
Trivy
โ
terraform test
โ
terraform plan
AI Should Accelerate
boilerplate
module structures
variable definitions
outputs
tests
documentation
refactoring
provider-document research
plan explanation
migration suggestions
Code language: JavaScript (javascript)
Human Review Must Own
architecture
security boundaries
IAM
network exposure
state manipulation
cost decisions
resource replacement
production approval
GOLD rule:
AI can write Terraform. Terraform’s tooling pipeline decides whether that Terraform deserves to continue toward production.
PART 19 โ TERRAFORM DEVELOPER DAILY WORKFLOW
1. Pull Latest Code
git checkout main
git pull
2. Select Terraform Version
tenv tf use
terraform version
Code language: PHP (php)
3. Create Branch
git checkout -b feat/add-app-vpc
4. Write Terraform
Use:
IDE
terraform-ls
MCP/Registry
approved modules
5. Format
terraform fmt -recursive
6. Validate
terraform init
terraform validate
7. Lint
tflint --init
tflint
8. Security Scan
trivy config .
9. Test
terraform test
10. Generate Documentation
terraform-docs .
11. Plan
terraform plan
Carefully examine:
create
update
destroy
replace
IAM
network
state
12. Cost
infracost scan
13. Commit
git add .
git commit -m "feat: add application VPC"
Code language: JavaScript (javascript)
14. Pre-Commit Runs
fmt
validate
TFLint
Trivy
docs
15. Push
git push -u origin feat/add-app-vpc
16. CI
Runs the authoritative quality gates.
17. Pull Request
Reviewer evaluates both:
code diff
Terraform plan
18. Policy
Sentinel/OPA evaluates organizational rules.
19. Apply
Prefer controlled remote execution.
20. Verify
After apply:
service health
metrics
logs
alerts
cloud state
Terraform outputs
drift
A successful terraform apply means Terraform finished.
It does not automatically mean the application is healthy.
PART 20 โ COMPLETE DEVELOPER TOOLCHAIN
| Tool | Problem Solved | Basic Usage | Limitation | Status |
|---|---|---|---|---|
| Terraform CLI | Infrastructure lifecycle | terraform plan | Not a full governance platform | MUST HAVE |
| VS Code | Editing | Open repository | General-purpose editor | MUST HAVE/Equivalent |
| Terraform Extension | Terraform-aware IDE | Install extension | VS Code-specific | MUST HAVE for VS Code |
| terraform-ls | Language intelligence | Usually extension-managed | Not lint/security | MUST HAVE via IDE |
| Terraform MCP | AI grounding | Connect AI client | AI still needs review | RECOMMENDED |
| tenv | Version consistency | tenv tf use | Another tool to maintain | RECOMMENDED |
| TFLint | Static quality | tflint | Not security scanner | MUST HAVE professionally |
| Trivy | Security/IaC scanning | trivy config . | Policies need tuning | MUST HAVE professionally |
| Checkov | Additional policy scanning | checkov -d . | Can overlap Trivy | OPTIONAL |
| terraform test | Native module tests | terraform test | Apply tests cost money | MUST HAVE professionally |
| Terratest | Integration testing | go test | Slower/more complex | OPTIONAL |
| terraform-docs | Generated docs | terraform-docs | Does not write architecture docs | RECOMMENDED |
| Infracost | Cost feedback | infracost scan | Estimates, not invoices | RECOMMENDED |
| pre-commit-terraform | Local automation | pre-commit run | Can be bypassed | RECOMMENDED |
| Git | Version control | git commit | Not Terraform-specific | MUST HAVE |
| GitHub | Collaboration | PR | Hosted platform | RECOMMENDED |
| GitLab | Collaboration | MR | Alternative to GitHub | RECOMMENDED |
| GitHub Actions | CI/CD | workflow YAML | General CI, DIY governance | RECOMMENDED |
| GitLab CI | CI/CD | .gitlab-ci.yml | Same | RECOMMENDED |
| Terragrunt | Multi-root orchestration | terragrunt run --all plan | Additional abstraction | OPTIONAL |
| Atlantis | PR Terraform automation | atlantis plan | Requires operation | OPTIONAL |
| HCP Terraform | Terraform platform | Remote runs | Platform dependency/cost | ENTERPRISE/TEAMS |
| Terraform Enterprise | Self-hosted platform | Remote runs | Operational complexity | ENTERPRISE |
| Sentinel | HashiCorp policy | policy sets | HashiCorp-specific | ENTERPRISE |
| OPA | Vendor-neutral policy | Rego | Learning curve | ENTERPRISE |
| Conftest | Local Rego validation | conftest test | Runner, not governance platform | OPTIONAL |
| Renovate | Dependency updates | Automated PRs | Requires policy/tuning | RECOMMENDED |
| tfupdate | Targeted version updates | CLI | Narrower than Renovate | OPTIONAL |
PART 21 โ RECOMMENDED STACKS
Beginner Terraform Developer
Terraform CLI
โ
VS Code
โ
Terraform Extension
โ
terraform fmt
โ
terraform validate
โ
Git
Goal:
Learn Terraform itself before introducing orchestration layers.
Professional Terraform Developer
VS Code
โ
Terraform Extension / terraform-ls
โ
tenv
โ
Terraform CLI
โ
fmt + validate
โ
TFLint
โ
Trivy
โ
terraform test
โ
terraform-docs
โ
pre-commit
โ
GitHub Actions / GitLab CI
This should be the default professional baseline.
Senior / Platform Engineer
VS Code / Cursor / Claude Code
โ
Terraform MCP
โ
tenv
โ
Terraform
โ
TFLint
โ
Trivy
โ
terraform test
โ
terraform-docs
โ
Infracost
โ
pre-commit
โ
CI
โ
HCP Terraform
Add Terragrunt only when environment/root-module complexity requires it.
Enterprise Platform
Developer IDE
โ
AI + Terraform MCP
โ
Approved Modules
โ
Local Quality Gates
โ
GitHub / GitLab
โ
CI
โ
Security + Cost
โ
HCP Terraform / Terraform Enterprise
โ
Private Registry
โ
OPA / Sentinel
โ
Approval
โ
Remote Apply
โ
Audit + Drift Detection
Code language: PHP (php)
PART 22 โ GOLD STANDARD TERRAFORM PIPELINE
Developer
โ
IDE
โ
AI/MCP Assistance
โ
Terraform Code
โ
Format
โ
Validate
โ
Lint
โ
Security Scan
โ
Test
โ
Documentation
โ
Cost Analysis
โ
Pre-Commit
โ
Git Push
โ
CI
โ
Terraform Plan
โ
Plan Security Scan
โ
Policy Validation
โ
Code Review
โ
Approval
โ
Terraform Apply
โ
Post-Deployment Verification
โ
Monitoring
โ
Drift Detection
Quality Gates
Gate 1 โ Format
Question:
Is code consistently formatted?
Gate 2 โ Validate
Is it valid Terraform?
Gate 3 โ Lint
Are there obvious Terraform/provider quality problems?
Gate 4 โ Security
Does this introduce known insecure infrastructure?
Code language: JavaScript (javascript)
Gate 5 โ Test
Does the module behave as designed?
Code language: JavaScript (javascript)
Gate 6 โ Documentation
Did interface documentation stay synchronized?
Code language: PHP (php)
Gate 7 โ Cost
What does this change cost?
Code language: JavaScript (javascript)
Gate 8 โ Plan
What will Terraform actually change?
Gate 9 โ Policy
Does the plan comply with organizational requirements?
Code language: JavaScript (javascript)
Gate 10 โ Human Review
Should we make this change?
Code language: JavaScript (javascript)
Gate 11 โ Approval
Is this authorized for this environment?
Code language: JavaScript (javascript)
Gate 12 โ Apply
Execute the approved change.
Gate 13 โ Verification
Did infrastructure AND workload health remain correct?
PART 23 โ LOCAL DEVELOPMENT SETUP
macOS
HashiCorp’s official Homebrew installation:
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
Recommended stack:
brew install tenv
brew install terraform-linters/tap/tflint
brew install trivy
brew install terraform-docs
brew install infracost
brew install pre-commit
brew install git
Verify:
terraform version
tenv --version
tflint --version
trivy --version
terraform-docs --version
infracost --version
pre-commit --version
git --version
Linux
Terraform should preferably come from HashiCorp’s official package repository rather than random binary mirrors.
Other tools can use their official package repositories/installers.
Generic verification:
terraform version
tflint --version
trivy --version
terraform-docs --version
For Checkov:
python3 -m pip install --user pipx
pipx install checkov
Windows
Terraform:
winget search Terraform
tenv:
winget install Tofuutils.Tenv
Code language: CSS (css)
TFLint:
winget install -e --id TerraformLinters.tflint
Code language: CSS (css)
Checkov:
pipx install checkov
HashiCorp notes that some Windows community package-manager distributions are community-maintained rather than official HashiCorp repositories, so enterprise environments should standardize and verify their software distribution source.
Local Setup Checklist
[ ] Git installed
[ ] Terraform version manager installed
[ ] Terraform stable version selected
[ ] VS Code/Cursor installed
[ ] HashiCorp Terraform extension installed
[ ] terraform-ls working
[ ] TFLint installed
[ ] Trivy installed
[ ] terraform-docs installed
[ ] pre-commit installed
[ ] Infracost installed if used
[ ] Terraform MCP configured if AI workflow used
[ ] Cloud SSO/profile configured
[ ] No permanent cloud access keys stored in repository
[ ] pre-commit install completed
PART 24 โ SAMPLE REAL AWS PROJECT
Architecture
Internet
โ
Public Subnet
โ
Security Group
โ
EC2
Inside:
VPC
โโโ Public Subnet A
โโโ Public Subnet B
โโโ Internet Gateway
โโโ Route Table
โโโ EC2
Code language: PHP (php)
Directory:
aws-web/
โโโ versions.tf
โโโ backend.tf
โโโ providers.tf
โโโ variables.tf
โโโ network.tf
โโโ compute.tf
โโโ outputs.tf
โโโ terraform.tfvars.example
โโโ backend.hcl.example
โโโ tests/
โ โโโ main.tftest.hcl
โโโ .tflint.hcl
โโโ .terraform-docs.yml
โโโ .pre-commit-config.yaml
โโโ .github/
โโโ workflows/
โโโ terraform.yml
versions.tf
terraform {
required_version = "~> 1.16.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.59"
}
}
}
Code language: JavaScript (javascript)
AWS provider 6.59.0 was the latest indexed stable release in August 2026 when this guide was verified.
After initialization:
terraform init
git add .terraform.lock.hcl
Code language: CSS (css)
backend.tf
terraform {
backend "s3" {}
}
Code language: JavaScript (javascript)
backend.hcl.example:
bucket = "company-terraform-state"
key = "aws-web/development/terraform.tfstate"
region = "us-east-1"
encrypt = true
use_lockfile = true
Code language: JavaScript (javascript)
Initialize:
terraform init -backend-config=backend.hcl
Modern S3 backends support native S3 state locking through:
use_lockfile = true
Code language: JavaScript (javascript)
HashiCorp currently marks DynamoDB-based S3 backend locking as deprecated. Bucket versioning is also strongly recommended for state recovery.
Do not put credentials in backend.hcl.
HashiCorp specifically recommends environment-based credentials/partial configuration because backend credentials can otherwise be persisted under .terraform and in plan artifacts.
providers.tf
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Environment = var.environment
ManagedBy = "Terraform"
Project = var.project
}
}
}
Code language: JavaScript (javascript)
No credentials.
Local authentication can come from:
AWS IAM Identity Center
AWS profile
environment temporary credentials
credential_process
assume-role
CI should use OIDC.
variables.tf
variable "aws_region" {
description = "AWS region."
type = string
default = "us-east-1"
}
variable "environment" {
description = "Deployment environment."
type = string
validation {
condition = contains(
["development", "staging", "production"],
var.environment
)
error_message = "Environment must be development, staging or production."
}
}
variable "project" {
description = "Project name."
type = string
default = "gold-web"
}
variable "vpc_cidr" {
description = "CIDR for the VPC."
type = string
default = "10.20.0.0/16"
}
variable "ami_id" {
description = "Approved AMI ID."
type = string
validation {
condition = startswith(var.ami_id, "ami-")
error_message = "ami_id must be an AWS AMI ID."
}
}
variable "instance_type" {
description = "EC2 instance type."
type = string
default = "t3.micro"
}
Code language: JavaScript (javascript)
network.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "${var.project}-${var.environment}-vpc"
}
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project}-${var.environment}-igw"
}
}
resource "aws_subnet" "public_a" {
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, 1)
availability_zone = "${var.aws_region}a"
map_public_ip_on_launch = true
tags = {
Name = "${var.project}-${var.environment}-public-a"
}
}
resource "aws_subnet" "public_b" {
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, 2)
availability_zone = "${var.aws_region}b"
map_public_ip_on_launch = true
tags = {
Name = "${var.project}-${var.environment}-public-b"
}
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "${var.project}-${var.environment}-public"
}
}
resource "aws_route_table_association" "public_a" {
subnet_id = aws_subnet.public_a.id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "public_b" {
subnet_id = aws_subnet.public_b.id
route_table_id = aws_route_table.public.id
}
Code language: PHP (php)
compute.tf
resource "aws_security_group" "web" {
name_prefix = "${var.project}-${var.environment}-web-"
description = "HTTP access to demonstration web instance"
vpc_id = aws_vpc.main.id
ingress {
description = "HTTP"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
description = "Outbound connectivity"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
lifecycle {
create_before_destroy = true
}
tags = {
Name = "${var.project}-${var.environment}-web"
}
}
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = var.instance_type
subnet_id = aws_subnet.public_a.id
vpc_security_group_ids = [aws_security_group.web.id]
associate_public_ip_address = true
metadata_options {
http_endpoint = "enabled"
http_tokens = "required"
}
root_block_device {
encrypted = true
}
tags = {
Name = "${var.project}-${var.environment}-web"
}
}
Code language: JavaScript (javascript)
Notice what is deliberately missing:
SSH 0.0.0.0/0
access keys
passwords
secret user_data
unencrypted disk
IMDSv1
outputs.tf
output "vpc_id" {
description = "VPC ID."
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "Public subnet IDs."
value = [
aws_subnet.public_a.id,
aws_subnet.public_b.id
]
}
output "instance_id" {
description = "EC2 instance ID."
value = aws_instance.web.id
}
output "public_ip" {
description = "Public IPv4 address."
value = aws_instance.web.public_ip
}
Code language: JavaScript (javascript)
terraform.tfvars.example
aws_region = "us-east-1"
environment = "development"
project = "gold-web"
vpc_cidr = "10.20.0.0/16"
ami_id = "ami-REPLACE_WITH_APPROVED_AMI"
instance_type = "t3.micro"
Code language: JavaScript (javascript)
tests/main.tftest.hcl
variables {
environment = "development"
aws_region = "us-east-1"
project = "gold-web"
vpc_cidr = "10.20.0.0/16"
ami_id = "ami-1234567890abcdef0"
instance_type = "t3.micro"
}
run "plan_defaults" {
command = plan
assert {
condition = aws_vpc.main.cidr_block == "10.20.0.0/16"
error_message = "Unexpected VPC CIDR."
}
assert {
condition = aws_vpc.main.enable_dns_support
error_message = "VPC DNS support must be enabled."
}
assert {
condition = aws_instance.web.instance_type == "t3.micro"
error_message = "Unexpected instance type."
}
assert {
condition = aws_instance.web.metadata_options[0].http_tokens == "required"
error_message = "IMDSv2 must be required."
}
}
Code language: JavaScript (javascript)
Development Workflow
terraform fmt -recursive
terraform init \
-backend-config=backend.hcl
terraform validate
tflint --init
tflint
trivy config .
terraform test
terraform plan \
-var-file=terraform.tfvars \
-out=tfplan
terraform show tfplan
Code language: JavaScript (javascript)
That single example now has:
networking
compute
variables
outputs
provider constraints
remote state
locking
tests
linting
security
documentation
pre-commit
CI
which is the minimum shape of a professional Terraform repository.
PART 25 โ COMMON DEVELOPER MISTAKES
1. Hardcoded Access Keys
Bad:
provider "aws" {
access_key = "..."
secret_key = "..."
}
Code language: JavaScript (javascript)
Correct:
local โ SSO / temporary profile
CI โ OIDC
2. Hardcoded Secrets
Bad:
password = "SuperSecret123"
Code language: JavaScript (javascript)
Use:
AWS Secrets Manager
Azure Key Vault
GCP Secret Manager
Vault
HCP variable security
But remember:
A secret referenced or generated by Terraform can still reach Terraform state.
3. Committing State
Never:
terraform.tfstate
terraform.tfstate.backup
Code language: CSS (css)
State can contain highly sensitive information.
HashiCorp explicitly warns that Terraform state and plan data may contain sensitive values. Local state is plaintext, while remote state should be encrypted and access-controlled.
4. Committing .terraform
Do not.
It contains downloaded providers/modules and local initialization data.
5. No Provider Constraints
Bad:
aws = {
source = "hashicorp/aws"
}
Code language: JavaScript (javascript)
Better:
aws = {
source = "hashicorp/aws"
version = "~> 6.59"
}
Code language: JavaScript (javascript)
6. Unversioned Modules
Bad:
source = "git::https://example/module.git"
Code language: JavaScript (javascript)
Better:
source = "git::https://example/module.git?ref=v3.2.1"
Code language: JavaScript (javascript)
7. Applying Production From Laptops
Better:
CI / HCP
+ approval
+ audit
+ temporary identity
8. No Code Review
Infrastructure is production code.
Use PRs.
9. No Linting
Use TFLint.
10. No Security Scanning
Use Trivy.
11. No Tests
Use terraform test.
12. No State Locking
Concurrent state modification can corrupt workflow assumptions.
Use backend locking.
For current S3 backends:
use_lockfile = true
Code language: JavaScript (javascript)
13. Overusing Workspaces
Do not use CLI workspaces as a substitute for serious security/environment boundaries.
14. Giant Root Modules
Bad:
10,000 Terraform resources
one state
Every plan becomes slow and high-risk.
Split by lifecycle/ownership/blast radius.
15. Copy-Paste Infrastructure
Extract stable repeating patterns into modules.
16. Poor Variables
Avoid:
variable "config" {
type = any
}
Code language: JavaScript (javascript)
when a precise type is possible.
Prefer:
variable "config" {
type = object({
instance_type = string
replicas = number
})
}
Code language: JavaScript (javascript)
17. Excessive -target
-target is an exceptional recovery/troubleshooting tool.
It should not become the normal deployment mechanism.
18. Manual State Operations
Avoid casual:
terraform state rm
terraform state mv
Prefer declarative migration constructs when possible.
19. Blind AI Terraform
AI output should still pass:
Registry/MCP verification
fmt
validate
lint
security
test
plan
human review
20. Ignoring Cost
A Terraform PR can be syntactically perfect and financially disastrous.
PART 26 โ TERRAFORM SECURITY BEST PRACTICES
[ ] No hardcoded credentials
[ ] CI uses OIDC/workload identity
[ ] Human users use SSO/temporary credentials
[ ] Least-privilege execution roles
[ ] State encrypted at rest
[ ] State encrypted in transit
[ ] State access tightly restricted
[ ] State bucket/object versioning enabled
[ ] State locking enabled
[ ] Secrets not embedded in HCL
[ ] Plan artifacts treated as sensitive
[ ] Trivy/security scanning enabled
[ ] Policy-as-code for organization-wide controls
[ ] Protected main branch
[ ] Production approval enabled
[ ] Terraform version constrained
[ ] Provider versions constrained
[ ] Provider lock file committed
[ ] Reusable modules versioned
[ ] Production apply separated from developer permissions
[ ] Audit trail maintained
[ ] Drift detection established
[ ] Destructive actions reviewed
[ ] Resource replacement reviewed
[ ] AI agents cannot casually apply production infrastructure
Code language: JavaScript (javascript)
PART 27 โ TERRAFORM CODE REVIEW CHECKLIST
Terraform Pull Request Review
CODE
[ ] terraform fmt passes
[ ] terraform validate passes
[ ] TFLint passes
[ ] naming matches standards
[ ] no needless complexity
[ ] no copy/paste that should become a module
VERSIONS
[ ] Terraform version constrained
[ ] providers constrained
[ ] module versions constrained
[ ] .terraform.lock.hcl reviewed when changed
VARIABLES
[ ] variable types are explicit
[ ] descriptions provided
[ ] validations added where useful
[ ] secrets are not defaulted
OUTPUTS
[ ] outputs are genuinely useful
[ ] sensitive values marked sensitive
[ ] unnecessary data is not exposed
SECURITY
[ ] no credentials
[ ] no secrets
[ ] least privilege IAM
[ ] network exposure intentional
[ ] encryption enabled
[ ] Trivy passes
PLAN
[ ] create count expected
[ ] update count expected
[ ] destroy count expected
[ ] replacements understood
[ ] IAM changes reviewed
[ ] network changes reviewed
[ ] state movement understood
[ ] blast radius acceptable
COST
[ ] cost increase understood
[ ] expensive resources justified
TESTING
[ ] terraform test passes
[ ] integration tests added where needed
DOCUMENTATION
[ ] module README current
[ ] architecture notes updated if behavior changed
OPERATIONS
[ ] rollback/recovery strategy understood
[ ] monitoring impact understood
[ ] post-deployment verification defined
Code language: JavaScript (javascript)
PART 28 โ TERRAFORM PRODUCTIVITY BEST PRACTICES
1. Standardize Modules
Developers should not reinvent:
VPC
EKS
RDS
IAM role
KMS
S3 baseline
logging
monitoring
for every project.
2. Standard Module Contract
Every production module should ideally contain:
main.tf
variables.tf
outputs.tf
versions.tf
README.md
examples/
tests/
3. IDE Feedback
Catch errors in seconds, not CI minutes.
4. AI + MCP
Use AI for creation speed, MCP for current Terraform context.
5. Pre-Commit
Automate repetitive checks.
6. Repository Templates
Offer teams a bootstrap:
terraform-new-project
Code language: JavaScript (javascript)
that already contains:
CI
TFLint
Trivy
tests
docs
pre-commit
Renovate
CODEOWNERS
PR template
7. Reusable CI Templates
Don’t maintain 200 hand-written Terraform pipelines.
Create:
company/terraform-ci
centrally.
8. Dependency Automation
Use Renovate.
Small routine upgrades are safer than annual giant upgrades.
9. Automated Documentation
Use terraform-docs.
10. Cost Feedback
Give developers cost information before merge.
11. Remote Execution
Production should not depend on:
Raj's laptop
Sarah's VPN
someone's local AWS profile
Code language: PHP (php)
12. Small State Boundaries
State should align with:
ownership
lifecycle
failure domain
security boundary
blast radius
not arbitrary folder organization.
PART 29 โ TOOL OVERLAP AND DECISION GUIDE
terraform validate vs TFLint
validate โ correctness
TFLint โ quality/provider lint
Use both.
Trivy vs Checkov
Default โ Trivy
Add Checkov only when its policy capabilities provide
specific additional value.
Code language: PHP (php)
Sentinel vs OPA
HashiCorp-focused โ Sentinel is reasonable
Vendor-neutral โ OPA
Existing Rego โ OPA
Do not automatically run both.
tfenv vs tenv
New Terraform-focused setup โ tenv
Existing stable tfenv setup โ migration not urgent
Code language: PHP (php)
Terraform vs Terragrunt
Terraform is mandatory.
Terragrunt is optional orchestration.
GitHub Actions vs Atlantis
GitHub Actions โ general CI
Atlantis โ Terraform PR execution
You can integrate them, but do not add Atlantis merely to duplicate CI.
Atlantis vs HCP Terraform
Atlantis
โ
PR Terraform automation
HCP Terraform
โ
Terraform platform
If HCP Terraform already owns remote runs, Atlantis is usually unnecessary.
terraform test vs Terratest
terraform test
โ
default
Terratest
โ
real external-system/integration verification
Code language: JavaScript (javascript)
terraform-docs vs Manual README
Not competitors.
terraform-docs
โ
machine-derived module interface
Human README
โ
purpose + architecture + examples + operations
Code language: JavaScript (javascript)
Use both in the same README.
GitHub Actions vs HCP Terraform
A very strong enterprise pattern is:
GitHub Actions
โ
code-quality pipeline
HCP Terraform
โ
Terraform execution/governance pipeline
Each owns a different responsibility.
PART 30 โ FINAL GOLD STANDARD RECOMMENDATION
Final Terraform Developer Stack
VS Code / Cursor / Claude Code
โ
HashiCorp Terraform Extension
โ
terraform-ls
โ
Terraform MCP Server
โ
tenv
โ
Terraform CLI
โ
terraform fmt
โ
terraform validate
โ
terraform test
โ
TFLint
โ
Trivy
โ
terraform-docs
โ
Infracost
โ
pre-commit-terraform
โ
Git
โ
GitHub / GitLab
โ
GitHub Actions / GitLab CI
โ
HCP Terraform
โ
Sentinel / OPA
โ
Cloud Infrastructure
โ
Monitoring + Drift Detection
Tool Priority
MUST HAVE
Terraform CLI
Git
Terraform version constraints
Provider constraints
.terraform.lock.hcl
terraform fmt
terraform validate
Remote state
State locking
Code review
CI
Code language: CSS (css)
For professional teams also treat these as baseline:
TFLint
Trivy
terraform test
OIDC/workload identity
STRONGLY RECOMMENDED
VS Code/Cursor + Terraform Extension
terraform-ls
tenv
terraform-docs
pre-commit-terraform
Infracost
Renovate
standard module templates
standard CI templates
Code language: JavaScript (javascript)
OPTIONAL
Terraform MCP Server
Checkov
Terratest
Terragrunt
Atlantis
Conftest
tfupdate
“Optional” does not mean low quality.
It means:
Introduce the tool when the problem it solves actually exists.
ENTERPRISE
HCP Terraform
Terraform Enterprise
Private Module Registry
Private Provider Registry
Sentinel
OPA governance
Run Tasks
RBAC
Audit
Drift Detection
Central agent pools
Central policy sets
Code language: PHP (php)
The Recommended Minimal Professional Stack
If I had to standardize Terraform development for a new engineering organization today without unnecessary complexity, I would start with:
VS Code / Cursor
โ
HashiCorp Terraform Extension
โ
tenv
โ
Terraform 1.16.x
โ
terraform fmt
โ
terraform validate
โ
TFLint
โ
Trivy
โ
terraform test
โ
terraform-docs
โ
pre-commit-terraform
โ
GitHub
โ
GitHub Actions
โ
OIDC
โ
Remote State / HCP Terraform
Then add:
Terraform MCP
for AI-heavy development.
Add:
Infracost
for meaningful cloud-cost environments.
Add:
Terragrunt
only when many accounts/regions/root modules make live Terraform configuration repetitive.
Add:
OPA / Sentinel
when organizational policies require centralized enforcement.
Add:
HCP Terraform / Terraform Enterprise
when centralized execution, RBAC, registry, audit, private connectivity, governance and drift detection justify operating a Terraform platform.
GOLD STANDARD DESIGN PRINCIPLE
Every tool must answer one question:
What engineering problem does this solve?
Code language: JavaScript (javascript)
Examples:
terraform-ls
โ writing Terraform faster and more accurately
tenv
โ version consistency
terraform fmt
โ formatting consistency
terraform validate
โ configuration correctness
TFLint
โ code quality
Trivy
โ infrastructure security
terraform test
โ behavioral correctness
terraform-docs
โ documentation drift
Infracost
โ cost visibility
pre-commit
โ fast developer feedback
GitHub Actions
โ authoritative automation
HCP Terraform
โ execution/state/governance
OPA/Sentinel
โ organization-wide policy
Renovate
โ dependency drift
Terraform MCP
โ grounded AI assistance
If you cannot clearly answer that question for a proposed Terraform tool, do not add it.
TERRAFORM DEVELOPER GOLD STANDARD CHECKLIST
Before merging any Terraform pull request:
SOURCE
[ ] Terraform version pinned/constrained
[ ] Provider versions constrained
[ ] Module versions constrained
[ ] .terraform.lock.hcl correct
QUALITY
[ ] terraform fmt -check passes
[ ] terraform validate passes
[ ] TFLint passes
SECURITY
[ ] Trivy passes
[ ] No credentials committed
[ ] No secrets committed
[ ] IAM reviewed
[ ] Network exposure reviewed
[ ] Encryption reviewed
TESTING
[ ] terraform test passes
[ ] Integration tests pass where required
DOCUMENTATION
[ ] terraform-docs current
[ ] README/architecture documentation current
PLAN
[ ] Terraform plan reviewed
[ ] Creates understood
[ ] Updates understood
[ ] Deletes understood
[ ] Replacements understood
[ ] State movement understood
[ ] Blast radius acceptable
COST
[ ] Cost difference reviewed
[ ] Unexpected billable resources investigated
GOVERNANCE
[ ] OPA/Sentinel policies pass where applicable
[ ] Required reviewers approved
[ ] Production approval satisfied
EXECUTION
[ ] CI uses temporary identity/OIDC
[ ] No long-lived cloud credentials
[ ] Remote state protected
[ ] State locking enabled
[ ] Production apply controlled
POST DEPLOYMENT
[ ] Infrastructure verified
[ ] Application/service health verified
[ ] Monitoring checked
[ ] Unexpected drift absent
Code language: PHP (php)
FINAL GOLD RULE
WRITE
โ
FORMAT
โ
VALIDATE
โ
LINT
โ
SECURE
โ
TEST
โ
DOCUMENT
โ
PRICE
โ
PLAN
โ
REVIEW
โ
POLICY
โ
APPROVE
โ
APPLY
โ
VERIFY
โ
MONITOR
That is the Terraform Developer GOLD Standard.
Terraform is not merely a tool for creating infrastructure.
At production scale it becomes an engineering discipline built around:
repeatability
automation
security
testing
review
governance
cost awareness
controlled execution
continuous verification
The goal is not to make developers run more tools.
The goal is to move mistakes as far left and as cheaply as possible, while making production changes predictable, reviewable, recoverable and auditable.