{"id":78465,"date":"2026-09-05T01:00:10","date_gmt":"2026-09-05T01:00:10","guid":{"rendered":"https:\/\/www.devopsschool.com\/blog\/?p=78465"},"modified":"2026-09-05T01:00:32","modified_gmt":"2026-09-05T01:00:32","slug":"terraform-developer-productivity-master-guide","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/blog\/terraform-developer-productivity-master-guide\/","title":{"rendered":"Terraform Developer Productivity Master Guide"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Tools, Workflow, Security, Testing, CI\/CD, Automation, AI Assistance, Governance, and Production Best Practices<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Verified baseline:<\/strong>&nbsp;5 September 2026<br><strong>Audience:<\/strong>&nbsp;Terraform developers, DevOps\/SRE, platform\/cloud engineers, module authors, DevSecOps teams, and engineering leaders.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">This handbook treats Terraform development as an engineering system, not a collection of unrelated commands. The goal is a fast local feedback loop, reproducible plans, secure authentication, reviewable changes, automated quality gates, policy enforcement, controlled production execution, and continuous drift\/health feedback.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Executive standard<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A strong default stack for a professional Terraform team is:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">VS Code \/ Cursor \/ Claude Code\n        \u2193\nHashiCorp Terraform Extension + terraform-ls\n        \u2193\nTerraform MCP Server for current Registry\/HCP context\n        \u2193\ntenv\n        \u2193\nTerraform CLI\n        \u2193\nterraform fmt\nterraform validate\nterraform test\n        \u2193\nTFLint\n        \u2193\nTrivy\n        \u2193\nterraform-docs\n        \u2193\nInfracost\n        \u2193\npre-commit-terraform\n        \u2193\nGit\n        \u2193\nGitHub \/ GitLab\n        \u2193\nCI quality gates + OIDC\/workload identity\n        \u2193\nHCP Terraform \/ Terraform Enterprise or another controlled runner\n        \u2193\nSentinel \/ OPA \/ Terraform Policy where appropriate\n        \u2193\nCloud infrastructure\n        \u2193\nPost-deployment validation + drift\/health monitoring\n<\/code><\/span><\/pre>\n\n\n<h3 class=\"wp-block-heading\">Tool classification<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Tool \/ capability<\/th><th class=\"has-text-align-left\" data-align=\"left\">Default classification<\/th><th class=\"has-text-align-left\" data-align=\"left\">Why<\/th><\/tr><\/thead><tbody><tr><td>Terraform CLI<\/td><td><strong>MUST HAVE<\/strong><\/td><td>Core IaC engine<\/td><\/tr><tr><td>Git<\/td><td><strong>MUST HAVE<\/strong><\/td><td>Reviewable, auditable change history<\/td><\/tr><tr><td><code>terraform fmt<\/code><\/td><td><strong>MUST HAVE<\/strong><\/td><td>Canonical formatting<\/td><\/tr><tr><td><code>terraform validate<\/code><\/td><td><strong>MUST HAVE<\/strong><\/td><td>Configuration validity<\/td><\/tr><tr><td>Remote state + locking<\/td><td><strong>MUST HAVE for teams<\/strong><\/td><td>Prevents local-state drift and concurrent writes<\/td><\/tr><tr><td>Provider\/module version constraints<\/td><td><strong>MUST HAVE<\/strong><\/td><td>Reproducibility<\/td><\/tr><tr><td><code>.terraform.lock.hcl<\/code>&nbsp;in Git<\/td><td><strong>MUST HAVE for root configurations<\/strong><\/td><td>Reproducible provider selection\/checksums<\/td><\/tr><tr><td>VS Code + HashiCorp extension<\/td><td><strong>STRONGLY RECOMMENDED<\/strong><\/td><td>Fast authoring feedback<\/td><\/tr><tr><td><code>terraform-ls<\/code><\/td><td><strong>STRONGLY RECOMMENDED<\/strong><\/td><td>IDE intelligence; usually bundled<\/td><\/tr><tr><td><code>tenv<\/code><\/td><td><strong>STRONGLY RECOMMENDED<\/strong><\/td><td>Reproducible CLI versions<\/td><\/tr><tr><td>TFLint<\/td><td><strong>STRONGLY RECOMMENDED<\/strong><\/td><td>Terraform\/provider-aware linting<\/td><\/tr><tr><td>Trivy<\/td><td><strong>STRONGLY RECOMMENDED<\/strong><\/td><td>IaC misconfiguration + optional secret scanning<\/td><\/tr><tr><td><code>terraform test<\/code><\/td><td><strong>STRONGLY RECOMMENDED<\/strong><\/td><td>Native module\/root tests<\/td><\/tr><tr><td><code>terraform-docs<\/code><\/td><td><strong>STRONGLY RECOMMENDED for modules<\/strong><\/td><td>Prevents stale README interfaces<\/td><\/tr><tr><td><code>pre-commit-terraform<\/code><\/td><td><strong>STRONGLY RECOMMENDED<\/strong><\/td><td>Cheap local automation<\/td><\/tr><tr><td>CI plan workflow<\/td><td><strong>MUST HAVE for production teams<\/strong><\/td><td>Reproducible review gate<\/td><\/tr><tr><td>OIDC\/workload identity<\/td><td><strong>MUST HAVE for CI cloud auth<\/strong><\/td><td>Avoids static cloud credentials<\/td><\/tr><tr><td>Infracost<\/td><td><strong>RECOMMENDED<\/strong><\/td><td>PR-level cost feedback<\/td><\/tr><tr><td>Renovate<\/td><td><strong>RECOMMENDED<\/strong><\/td><td>Dependency hygiene<\/td><\/tr><tr><td>Terraform MCP Server<\/td><td><strong>RECOMMENDED with AI coding<\/strong><\/td><td>Current Registry\/HCP context<\/td><\/tr><tr><td>Checkov<\/td><td><strong>OPTIONAL \/ context dependent<\/strong><\/td><td>Strong policy\/compliance scanning; can overlap Trivy<\/td><\/tr><tr><td>Terratest<\/td><td><strong>OPTIONAL<\/strong><\/td><td>Real integration\/E2E testing when native tests are insufficient<\/td><\/tr><tr><td>Terragrunt<\/td><td><strong>OPTIONAL<\/strong><\/td><td>Useful for large repeated multi-account\/region layouts<\/td><\/tr><tr><td>Atlantis<\/td><td><strong>OPTIONAL<\/strong><\/td><td>PR-driven execution if it fits the operating model<\/td><\/tr><tr><td>HCP Terraform<\/td><td><strong>RECOMMENDED \/ ENTERPRISE<\/strong><\/td><td>Remote runs, state, governance, registry, RBAC<\/td><\/tr><tr><td>Terraform Enterprise<\/td><td><strong>ENTERPRISE<\/strong><\/td><td>Self-hosted HCP Terraform distribution<\/td><\/tr><tr><td>Sentinel \/ OPA<\/td><td><strong>ENTERPRISE \/ governance<\/strong><\/td><td>Policy gates<\/td><\/tr><tr><td>Conftest<\/td><td><strong>OPTIONAL<\/strong><\/td><td>Lightweight vendor-neutral Rego execution<\/td><\/tr><tr><td><code>tfupdate<\/code><\/td><td><strong>OPTIONAL \/ niche<\/strong><\/td><td>Focused Terraform version rewriting; Renovate is broader<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">2026-specific guidance<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Terraform MCP Server is now a first-class option<\/strong>\u00a0for AI-assisted Terraform. Use it to ground an AI agent in current provider\/module documentation instead of trusting model memory.<\/li>\n\n\n\n<li><strong>Native\u00a0<code>terraform test<\/code>\u00a0is the default first testing layer.<\/strong>\u00a0Add Terratest when you truly need live system behavior, cross-service checks, retries, or non-Terraform assertions.<\/li>\n\n\n\n<li><strong>Trivy is the normal replacement for tfsec<\/strong>\u00a0in new workflows.<\/li>\n\n\n\n<li><strong>For new S3 backends use native S3 state locking<\/strong>\u00a0(<code>use_lockfile = true<\/code>). Do not design a new backend around DynamoDB locking.<\/li>\n\n\n\n<li><strong>HCP Terraform policy choices are broader than Sentinel alone.<\/strong>\u00a0OPA is supported and HashiCorp also has Terraform Policy in HCL; treat beta features cautiously.<\/li>\n\n\n\n<li><strong>Terragrunt 1.x uses the streamlined CLI<\/strong>, e.g.\u00a0<code>terragrunt run --all plan<\/code>; old\u00a0<code>run-all<\/code>\u00a0examples should be considered legacy.<\/li>\n\n\n\n<li><strong>Do not build CI around static AWS\/Azure\/GCP keys.<\/strong>\u00a0Use GitHub\/GitLab OIDC or the cloud&#8217;s workload-identity mechanism.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 1 \u2014 Terraform Development Ecosystem<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">1.1 The end-to-end model<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Developer intent\n   \u2193\nEditor \/ AI coding environment\n   \u2193\nLanguage intelligence\n   \u2193\nVersion-controlled Terraform CLI\n   \u2193\nFast local quality gates\n   \u2193\nGit change\n   \u2193\nCI reproducibility gates\n   \u2193\nPlan\n   \u2193\nSecurity \/ cost \/ policy review\n   \u2193\nHuman approval\n   \u2193\nControlled apply\n   \u2193\nVerification\n   \u2193\nDrift \/ health feedback\n<\/code><\/span><\/pre>\n\n\n<h3 class=\"wp-block-heading\">Responsibility of each layer<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Layer<\/th><th class=\"has-text-align-left\" data-align=\"left\">Responsibility<\/th><th class=\"has-text-align-left\" data-align=\"left\">Typical tools<\/th><\/tr><\/thead><tbody><tr><td>Editor<\/td><td>Write\/navigate\/refactor HCL<\/td><td>VS Code, Cursor<\/td><\/tr><tr><td>AI assistant<\/td><td>Boilerplate, explanation, refactoring<\/td><td>Claude Code, Cursor, VS Code AI<\/td><\/tr><tr><td>Language server<\/td><td>Schema-aware completion\/diagnostics\/navigation<\/td><td><code>terraform-ls<\/code><\/td><\/tr><tr><td>AI context server<\/td><td>Current Terraform Registry\/HCP information<\/td><td>Terraform MCP Server<\/td><\/tr><tr><td>Version manager<\/td><td>Select project-approved binaries<\/td><td><code>tenv<\/code>, mise, asdf, tfenv<\/td><\/tr><tr><td>Terraform engine<\/td><td>Init\/plan\/apply\/state\/test<\/td><td>Terraform CLI<\/td><\/tr><tr><td>Formatter<\/td><td>Canonical HCL style<\/td><td><code>terraform fmt<\/code><\/td><\/tr><tr><td>Validator<\/td><td>Terraform language\/module validity<\/td><td><code>terraform validate<\/code><\/td><\/tr><tr><td>Linter<\/td><td>Terraform\/provider best practices<\/td><td>TFLint<\/td><\/tr><tr><td>Security scanner<\/td><td>Misconfiguration\/compliance\/secret checks<\/td><td>Trivy, Checkov<\/td><\/tr><tr><td>Test layer<\/td><td>Behavior assertions<\/td><td><code>terraform test<\/code>, Terratest<\/td><\/tr><tr><td>Documentation<\/td><td>Generate module interface docs<\/td><td><code>terraform-docs<\/code><\/td><\/tr><tr><td>Cost analysis<\/td><td>Estimate cost delta<\/td><td>Infracost<\/td><\/tr><tr><td>Commit automation<\/td><td>Run cheap checks before push<\/td><td>pre-commit \/ pre-commit-terraform<\/td><\/tr><tr><td>VCS<\/td><td>Review\/audit<\/td><td>GitHub, GitLab<\/td><\/tr><tr><td>CI<\/td><td>Reproduce quality gates and create plan<\/td><td>GitHub Actions, GitLab CI<\/td><\/tr><tr><td>Remote runner\/orchestrator<\/td><td>Controlled execution\/state\/governance<\/td><td>HCP Terraform, TFE, Atlantis<\/td><\/tr><tr><td>Policy as code<\/td><td>Organizational guardrails<\/td><td>Terraform Policy, Sentinel, OPA<\/td><\/tr><tr><td>Cloud<\/td><td>Actual infrastructure<\/td><td>AWS, Azure, GCP, SaaS providers<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Core principle: different tools answer different questions<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">fmt       \u2192 Is the code formatted?\nvalidate  \u2192 Is the Terraform configuration structurally valid?\nTFLint    \u2192 Is it suspicious, deprecated, inconsistent, or provider-invalid?\nTrivy     \u2192 Is it insecure or dangerously configured?\ntest      \u2192 Does the <span class=\"hljs-built_in\">module<\/span> satisfy its intended behavior?\nplan      \u2192 What will Terraform change <span class=\"hljs-keyword\">in<\/span> <span class=\"hljs-keyword\">this<\/span> real context?\nInfracost \u2192 What may the cost impact be?\nPolicy    \u2192 Is the change allowed by organizational rules?\nReview    \u2192 Should we make <span class=\"hljs-keyword\">this<\/span> change?\nApply     \u2192 Execute the approved change.\nVerify    \u2192 Did the system reach the expected state?\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">No single tool replaces the others.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 2 \u2014 Core Terraform Development Tools<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">2. Terraform CLI<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">What<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Terraform CLI is the execution engine for Terraform configuration, dependency initialization, planning, applying, state operations, testing, and inspection.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Essential command map<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Command<\/th><th class=\"has-text-align-left\" data-align=\"left\">Use it when<\/th><th class=\"has-text-align-left\" data-align=\"left\">Production note<\/th><\/tr><\/thead><tbody><tr><td><code>terraform init<\/code><\/td><td>First run, provider\/module\/backend changes<\/td><td>Review lock-file changes<\/td><\/tr><tr><td><code>terraform fmt<\/code><\/td><td>During authoring and before commit<\/td><td>Automate<\/td><\/tr><tr><td><code>terraform validate<\/code><\/td><td>After editing modules\/config<\/td><td>Does not validate live cloud behavior<\/td><\/tr><tr><td><code>terraform plan<\/code><\/td><td>Before any change<\/td><td>Review replacements\/destruction<\/td><\/tr><tr><td><code>terraform apply<\/code><\/td><td>Execute an approved plan<\/td><td>Prefer controlled runner<\/td><\/tr><tr><td><code>terraform destroy<\/code><\/td><td>Intentionally remove stack<\/td><td>High-risk; require strong controls<\/td><\/tr><tr><td><code>terraform console<\/code><\/td><td>Test expressions\/functions<\/td><td>Great for debugging locals\/CIDR logic<\/td><\/tr><tr><td><code>terraform output<\/code><\/td><td>Read root outputs<\/td><td>Avoid exposing sensitive values<\/td><\/tr><tr><td><code>terraform show<\/code><\/td><td>Inspect state or saved plan<\/td><td>Plan\/state can contain secrets<\/td><\/tr><tr><td><code>terraform providers<\/code><\/td><td>Inspect provider dependencies<\/td><td>Useful for upgrade debugging<\/td><\/tr><tr><td><code>terraform state<\/code><\/td><td>Controlled state inspection\/moves<\/td><td>High-risk; use change procedure<\/td><\/tr><tr><td><code>terraform import<\/code><\/td><td>Bring existing resources under management<\/td><td>Prefer reviewable import workflows<\/td><\/tr><tr><td><code>terraform test<\/code><\/td><td>Execute&nbsp;<code>.tftest.hcl<\/code>&nbsp;tests<\/td><td>Default first test framework<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Practical workflow<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">terraform version\nterraform init\nterraform fmt -recursive\nterraform validate\nterraform test\nterraform plan -out=tfplan\nterraform show tfplan\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># apply only after review\/approval<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\nterraform apply tfplan\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\"><code>terraform init<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">What it does:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Initializes backend.<\/li>\n\n\n\n<li>Downloads providers.<\/li>\n\n\n\n<li>Downloads child modules.<\/li>\n\n\n\n<li>Creates\/updates\u00a0<code>.terraform.lock.hcl<\/code>.<\/li>\n\n\n\n<li>Prepares the working directory.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Useful patterns:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">terraform init\nterraform init -upgrade\nterraform init -backend=false   <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># useful for validation jobs that do not need state<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\nterraform init -reconfigure\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Use&nbsp;<code>-upgrade<\/code>&nbsp;deliberately, not on every CI run, because it tells Terraform to re-evaluate dependency selections.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>terraform fmt<\/code><\/h3>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">terraform fmt\nterraform fmt -recursive\nterraform fmt -check -recursive\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Local development: run write mode.<br>CI: run&nbsp;<code>-check<\/code>&nbsp;so CI detects unformatted code without modifying the branch.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>terraform validate<\/code><\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">terraform init -backend=<span class=\"hljs-literal\">false<\/span>\nterraform validate\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Validates Terraform configuration consistency and syntax in an initialized directory. It does&nbsp;<strong>not<\/strong>&nbsp;prove:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>credentials are valid,<\/li>\n\n\n\n<li>resource values are allowed by the cloud API,<\/li>\n\n\n\n<li>IAM permissions are sufficient,<\/li>\n\n\n\n<li>deployment will succeed,<\/li>\n\n\n\n<li>architecture is secure,<\/li>\n\n\n\n<li>cost is acceptable.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Those require lint\/security\/plan\/test\/policy\/runtime checks.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>terraform plan<\/code><\/h3>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">terraform plan\nterraform plan -out=tfplan\nterraform show -no-color tfplan\nterraform show -json tfplan &gt; tfplan.json\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Review especially:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>-\/+<\/code>\u00a0replacements,<\/li>\n\n\n\n<li>deletions,<\/li>\n\n\n\n<li>IAM changes,<\/li>\n\n\n\n<li>network exposure,<\/li>\n\n\n\n<li>encryption changes,<\/li>\n\n\n\n<li>database\/storage replacement,<\/li>\n\n\n\n<li>identity-provider changes,<\/li>\n\n\n\n<li>state moves\/imports,<\/li>\n\n\n\n<li>count\/for_each key churn.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Do not treat &#8220;Plan: 1 to add, 0 to change, 0 to destroy&#8221; as sufficient review. The&nbsp;<em>kind<\/em>&nbsp;of resource matters.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>terraform apply<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Recommended hierarchy:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>HCP Terraform\/TFE or controlled CI runner.<\/li>\n\n\n\n<li>Dedicated deployment runner with OIDC\/workload identity.<\/li>\n\n\n\n<li>Laptop apply only for sandbox\/learning or documented break-glass workflows.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Applying a saved reviewed plan is safer than regenerating a plan immediately before apply when your workflow supports preserving the plan artifact securely.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>terraform destroy<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Treat as a destructive privileged operation. Prefer environment protection, explicit approval, and resource protection where appropriate.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>terraform show<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Saved plan JSON is excellent input for policy engines and scanners:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">terraform plan -out=tfplan\nterraform show -json tfplan &gt; tfplan.json\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>Security:<\/strong>&nbsp;plan output can contain sensitive information. Store it only in trusted, short-lived CI artifacts.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>terraform state<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Useful commands:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">terraform<\/span> <span class=\"hljs-selector-tag\">state<\/span> <span class=\"hljs-selector-tag\">list<\/span>\n<span class=\"hljs-selector-tag\">terraform<\/span> <span class=\"hljs-selector-tag\">state<\/span> <span class=\"hljs-selector-tag\">show<\/span> <span class=\"hljs-selector-tag\">aws_instance<\/span><span class=\"hljs-selector-class\">.web<\/span>\n<span class=\"hljs-selector-tag\">terraform<\/span> <span class=\"hljs-selector-tag\">state<\/span> <span class=\"hljs-selector-tag\">mv<\/span> <span class=\"hljs-selector-tag\">OLD_ADDRESS<\/span> <span class=\"hljs-selector-tag\">NEW_ADDRESS<\/span>\n<span class=\"hljs-selector-tag\">terraform<\/span> <span class=\"hljs-selector-tag\">state<\/span> <span class=\"hljs-selector-tag\">rm<\/span> <span class=\"hljs-selector-tag\">ADDRESS<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Policy:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Never directly edit\u00a0<code>terraform.tfstate<\/code>.<\/li>\n\n\n\n<li>Back up\/retain remote state versions.<\/li>\n\n\n\n<li>Require peer review\/change ticket for production state surgery.<\/li>\n\n\n\n<li>Prefer Terraform language mechanisms such as\u00a0<code>moved<\/code>\u00a0blocks where they solve the problem cleanly.<\/li>\n\n\n\n<li><code>state rm<\/code>\u00a0means &#8220;stop managing this object&#8221;; it does not delete the real object.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">3. VS Code + HashiCorp Terraform Extension<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Why<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The editor should catch low-cost mistakes before you reach a terminal or CI.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The official HashiCorp extension currently provides language-server-backed:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>syntax highlighting,<\/li>\n\n\n\n<li>IntelliSense\/completion,<\/li>\n\n\n\n<li>inline diagnostics,<\/li>\n\n\n\n<li>formatting,<\/li>\n\n\n\n<li>go-to-definition and symbols,<\/li>\n\n\n\n<li>module\/project navigation,<\/li>\n\n\n\n<li>snippets,<\/li>\n\n\n\n<li>HCP Terraform workspace\/run integration,<\/li>\n\n\n\n<li>support for newer Terraform language surfaces.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Installation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">VS Code Marketplace extension:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">HashiCorp Terraform\npublisher: HashiCorp\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The extension bundles a compatible&nbsp;<code>terraform-ls<\/code>, so most VS Code users should&nbsp;<strong>not<\/strong>&nbsp;separately install the language server.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Recommended settings<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Example&nbsp;<code>.vscode\/settings.json<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\">{\n  <span class=\"hljs-attr\">\"&#91;terraform]\"<\/span>: {\n    <span class=\"hljs-attr\">\"editor.defaultFormatter\"<\/span>: <span class=\"hljs-string\">\"HashiCorp.terraform\"<\/span>,\n    <span class=\"hljs-attr\">\"editor.formatOnSave\"<\/span>: <span class=\"hljs-literal\">true<\/span>,\n    <span class=\"hljs-attr\">\"editor.formatOnPaste\"<\/span>: <span class=\"hljs-literal\">false<\/span>,\n    <span class=\"hljs-attr\">\"editor.codeActionsOnSave\"<\/span>: {\n      <span class=\"hljs-attr\">\"source.organizeImports\"<\/span>: <span class=\"hljs-string\">\"never\"<\/span>\n    }\n  },\n  <span class=\"hljs-attr\">\"&#91;terraform-vars]\"<\/span>: {\n    <span class=\"hljs-attr\">\"editor.defaultFormatter\"<\/span>: <span class=\"hljs-string\">\"HashiCorp.terraform\"<\/span>,\n    <span class=\"hljs-attr\">\"editor.formatOnSave\"<\/span>: <span class=\"hljs-literal\">true<\/span>\n  },\n  <span class=\"hljs-attr\">\"files.trimTrailingWhitespace\"<\/span>: <span class=\"hljs-literal\">true<\/span>,\n  <span class=\"hljs-attr\">\"files.insertFinalNewline\"<\/span>: <span class=\"hljs-literal\">true<\/span>,\n  <span class=\"hljs-attr\">\"editor.rulers\"<\/span>: &#91;<span class=\"hljs-number\">100<\/span>, <span class=\"hljs-number\">120<\/span>]\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The HashiCorp extension should be the authority for Terraform formatting; avoid competing formatters for&nbsp;<code>.tf<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Useful companion extensions<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Keep the list small:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>GitLens or your organization&#8217;s Git review extension.<\/li>\n\n\n\n<li>YAML extension for CI\/config files.<\/li>\n\n\n\n<li>Markdown lint\/preview for module docs.<\/li>\n\n\n\n<li>EditorConfig if your organization uses it.<\/li>\n\n\n\n<li>AI assistant only if approved by your security\/data policy.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Avoid installing multiple Terraform format\/language extensions at the same time.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">4.&nbsp;<code>terraform-ls<\/code><\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">What<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><code>terraform-ls<\/code>&nbsp;is HashiCorp&#8217;s official Terraform language server. It exposes IDE features over the Language Server Protocol.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Provides<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>completions,<\/li>\n\n\n\n<li>diagnostics,<\/li>\n\n\n\n<li>navigation,<\/li>\n\n\n\n<li>reference awareness,<\/li>\n\n\n\n<li>provider\/schema intelligence,<\/li>\n\n\n\n<li>module awareness,<\/li>\n\n\n\n<li>workspace indexing.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Installation decision<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">VS Code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Do&nbsp;<strong>not<\/strong>&nbsp;normally install manually. The HashiCorp Terraform extension bundles the compatible server.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Other LSP-compatible editors<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Manual installation can be appropriate.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">macOS\/Linux with Homebrew:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">brew install hashicorp\/tap\/terraform-ls\nterraform-ls version\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Best practice<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Open the project&nbsp;<strong>folder\/workspace<\/strong>, not only one&nbsp;<code>.tf<\/code>&nbsp;file. Whole-project indexing enables better module\/ref navigation.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">5. Terraform MCP Server<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">What<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Terraform MCP Server connects an MCP-capable AI client to current Terraform provider\/module\/policy information and, when configured, HCP Terraform\/TFE context.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Problem it solves<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Without external grounding, an AI model can produce:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>provider arguments removed two versions ago,<\/li>\n\n\n\n<li>fictional resources,<\/li>\n\n\n\n<li>invalid nesting,<\/li>\n\n\n\n<li>outdated module interfaces,<\/li>\n\n\n\n<li>obsolete HCP Terraform behavior.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">MCP reduces that risk by giving the agent current source context.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Architecture<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">Developer request\n      \u2193\nAI host \/ coding agent\n      \u2193\nMCP client\n      \u2193\nTerraform MCP Server\n      \u2193\nTerraform Registry \/ HCP Terraform\n      \u2193\nCurrent provider, <span class=\"hljs-built_in\">module<\/span>, policy, workspace context\n      \u2193\nAI-generated suggestion\n      \u2193\nfmt \u2192 validate \u2192 lint \u2192 security \u2192 test \u2192 plan\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Use cases<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>&#8220;Show the current schema for this AWS resource before generating it.&#8221;<\/li>\n\n\n\n<li>&#8220;Find an approved module in our private registry.&#8221;<\/li>\n\n\n\n<li>&#8220;Explain this provider argument using the current provider docs.&#8221;<\/li>\n\n\n\n<li>&#8220;Generate a module that is compatible with our pinned provider.&#8221;<\/li>\n\n\n\n<li>&#8220;Find the HCP Terraform workspace for this stack and summarize its run context.&#8221;<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Example prompts<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">Using the Terraform MCP Server, verify the current AWS provider schema <span class=\"hljs-keyword\">for<\/span>\naws_s3_bucket and generate the smallest secure example compatible <span class=\"hljs-keyword\">with<\/span> our\nrequired provider constraint. Do not invent attributes.\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">Find the current Registry documentation <span class=\"hljs-keyword\">for<\/span> the <span class=\"hljs-built_in\">module<\/span> used <span class=\"hljs-keyword\">in<\/span> .\/network.\nExplain which inputs are required and identify any code <span class=\"hljs-keyword\">in<\/span> our wrapper that is\nusing removed\/deprecated inputs. Do not change state addresses.\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">Before modifying <span class=\"hljs-keyword\">this<\/span> Terraform, list the resources that would be security\nsensitive (IAM, KMS, network ingress, state backend). Generate code only after\nchecking current provider documentation. Then run fmt\/validate\/test and explain\nremaining assumptions.\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Security rules<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Treat the MCP server as a privileged integration if it can access private HCP Terraform information.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Scope HCP tokens minimally.<\/li>\n\n\n\n<li>Do not expose sensitive state to an AI tool by default.<\/li>\n\n\n\n<li>Separate read\/documentation use from write\/run-management permissions.<\/li>\n\n\n\n<li>Review generated commands before execution.<\/li>\n\n\n\n<li>Never authorize autonomous production apply merely because the generated HCL validates.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Recommendation:<\/strong>&nbsp;Strongly recommended for teams already using AI-assisted Terraform, optional otherwise.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 3 \u2014 Terraform Version Management<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">6.&nbsp;<code>tenv<\/code><\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">What<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><code>tenv<\/code>&nbsp;is a Go-based version manager for Terraform, OpenTofu, Terragrunt, Terramate, and Atmos. It is the successor to tfenv\/tofuenv.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">One globally installed Terraform version creates avoidable problems:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>project A needs a new language feature,<\/li>\n\n\n\n<li>project B is still validated on an older minor version,<\/li>\n\n\n\n<li>CI and laptop silently differ,<\/li>\n\n\n\n<li>developers accidentally upgrade providers\/state behavior by upgrading the CLI.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Installation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">macOS:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">brew install tofuutils\/tap\/tenv\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Verify:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">tenv --version\ntenv tf --help\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Common usage<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">tenv<\/span> <span class=\"hljs-selector-tag\">tf<\/span> <span class=\"hljs-selector-tag\">install<\/span> 1<span class=\"hljs-selector-class\">.16<\/span><span class=\"hljs-selector-class\">.0<\/span>\n<span class=\"hljs-selector-tag\">tenv<\/span> <span class=\"hljs-selector-tag\">tf<\/span> <span class=\"hljs-selector-tag\">use<\/span> 1<span class=\"hljs-selector-class\">.16<\/span><span class=\"hljs-selector-class\">.0<\/span>\n<span class=\"hljs-selector-tag\">terraform<\/span> <span class=\"hljs-selector-tag\">version<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Project file:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"># <span class=\"hljs-selector-class\">.terraform-version<\/span>\n1<span class=\"hljs-selector-class\">.16<\/span><span class=\"hljs-selector-class\">.0<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><code>tenv<\/code>&nbsp;can also resolve compatible Terraform versions from&nbsp;<code>required_version<\/code>&nbsp;constraints.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Team policy<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For root configurations:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">terraform {\n  required_version = <span class=\"hljs-string\">\"~&gt; 1.16.0\"<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">and use the same minor\/patch policy in CI.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For reusable modules, avoid unnecessarily over-constraining callers. If the module only needs a feature introduced in 1.7:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">terraform {\n  required_version = <span class=\"hljs-string\">\"&gt;= 1.7.0\"<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Comparison<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Tool<\/th><th class=\"has-text-align-left\" data-align=\"left\">Best fit<\/th><th class=\"has-text-align-left\" data-align=\"left\">Strength<\/th><th class=\"has-text-align-left\" data-align=\"left\">Limitation<\/th><\/tr><\/thead><tbody><tr><td><code>tenv<\/code><\/td><td>Terraform\/OpenTofu\/Terragrunt-centric teams<\/td><td>Purpose-built, multi-IaC binary support<\/td><td>Another tool to standardize<\/td><\/tr><tr><td><code>tfenv<\/code><\/td><td>Existing Terraform-only estates<\/td><td>Familiar and simple<\/td><td><code>tenv<\/code>&nbsp;is its modern successor<\/td><\/tr><tr><td><code>asdf<\/code><\/td><td>Polyglot toolchains<\/td><td>One manager for many ecosystems<\/td><td>Plugin lifecycle\/UX<\/td><\/tr><tr><td><code>mise<\/code><\/td><td>Modern polyglot developer environments<\/td><td>Fast, broad tooling and task support<\/td><td>Broader than Terraform-specific need<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Default recommendation:<\/strong>&nbsp;<code>tenv<\/code>&nbsp;for Terraform-centric teams;&nbsp;<code>mise<\/code>&nbsp;is an excellent choice if the organization already standardizes all developer tools through it.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 4 \u2014 Terraform Code Quality<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">7.&nbsp;<code>terraform fmt<\/code><\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Formatting is non-negotiable because it removes style arguments from review.<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">terraform fmt -recursive\nterraform fmt -check -recursive\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>Local:<\/strong>&nbsp;write\/fix.<br><strong>CI:<\/strong>&nbsp;<code>-check<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Do not build an internal HCL formatting standard that conflicts with&nbsp;<code>terraform fmt<\/code>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">8.&nbsp;<code>terraform validate<\/code><\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Use it as a structural gate, not as a linter or security test.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">CI-friendly:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">terraform init -backend=<span class=\"hljs-literal\">false<\/span> -input=<span class=\"hljs-literal\">false<\/span>\nterraform validate -no-color\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Use a real plan later when values\/backend\/provider runtime context matters.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">9. TFLint<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">What<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">TFLint is a pluggable Terraform linter. Its built-in Terraform rules catch language-level issues, and cloud provider rulesets add checks that Terraform&#8217;s core validation cannot know.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Problems it solves<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>deprecated HCL patterns,<\/li>\n\n\n\n<li>unused declarations,<\/li>\n\n\n\n<li>missing required provider\/version constraints,<\/li>\n\n\n\n<li>unpinned Registry modules,<\/li>\n\n\n\n<li>invalid or discouraged provider-specific values,<\/li>\n\n\n\n<li>invalid instance types,<\/li>\n\n\n\n<li>organization naming conventions through configured\/custom rules.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Installation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">macOS:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">brew install terraform-linters\/tap\/tflint\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Windows:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">winget<\/span> <span class=\"hljs-selector-tag\">install<\/span> <span class=\"hljs-selector-tag\">-e<\/span> <span class=\"hljs-selector-tag\">--id<\/span> <span class=\"hljs-selector-tag\">TerraformLinters<\/span><span class=\"hljs-selector-class\">.tflint<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Linux: use the signed\/attested release artifacts or the project&#8217;s documented installer.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Production-oriented&nbsp;<code>.tflint.hcl<\/code><\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">tflint {\n  required_version = <span class=\"hljs-string\">\"&gt;= 0.64.0\"<\/span>\n}\n\nconfig {\n  call_module_type    = <span class=\"hljs-string\">\"all\"<\/span>\n  disabled_by_default = <span class=\"hljs-keyword\">false<\/span>\n}\n\nplugin <span class=\"hljs-string\">\"terraform\"<\/span> {\n  enabled = <span class=\"hljs-keyword\">true<\/span>\n  preset  = <span class=\"hljs-string\">\"recommended\"<\/span>\n}\n\nplugin <span class=\"hljs-string\">\"aws\"<\/span> {\n  enabled = <span class=\"hljs-keyword\">true<\/span>\n  version = <span class=\"hljs-string\">\"0.48.0\"<\/span>\n  source  = <span class=\"hljs-string\">\"github.com\/terraform-linters\/tflint-ruleset-aws\"<\/span>\n}\n\n<span class=\"hljs-comment\"># Useful team-level additions beyond the recommended preset.<\/span>\nrule <span class=\"hljs-string\">\"terraform_documented_variables\"<\/span> {\n  enabled = <span class=\"hljs-keyword\">true<\/span>\n}\n\nrule <span class=\"hljs-string\">\"terraform_documented_outputs\"<\/span> {\n  enabled = <span class=\"hljs-keyword\">true<\/span>\n}\n\nrule <span class=\"hljs-string\">\"terraform_naming_convention\"<\/span> {\n  enabled = <span class=\"hljs-keyword\">true<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Initialize and run:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">tflint --init\ntflint --recursive --format=compact\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">For CI systems repeatedly running&nbsp;<code>tflint --init<\/code>, provide an authenticated GitHub token to avoid anonymous GitHub API rate limits.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">TFLint vs validate<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\"><code>terraform validate<\/code><\/th><th class=\"has-text-align-left\" data-align=\"left\">TFLint<\/th><\/tr><\/thead><tbody><tr><td>Terraform-native structural validity<\/td><td>Static lint\/best-practice checks<\/td><\/tr><tr><td>Understands Terraform language\/module configuration<\/td><td>Extensible provider-specific rules<\/td><\/tr><tr><td>Not a style\/security scanner<\/td><td>Can catch deprecated\/invalid provider patterns<\/td><\/tr><tr><td>Mandatory baseline<\/td><td>Strongly recommended complement<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 5 \u2014 Terraform Security<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">10. Trivy<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">What<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Trivy scans Terraform\/IaC for misconfiguration and can also scan repositories\/filesystems for secrets and other security problems.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Common findings<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>public exposure,<\/li>\n\n\n\n<li>weak encryption,<\/li>\n\n\n\n<li>risky IAM,<\/li>\n\n\n\n<li>permissive security groups,<\/li>\n\n\n\n<li>weak storage policies,<\/li>\n\n\n\n<li>Kubernetes misconfiguration,<\/li>\n\n\n\n<li>plaintext secret patterns.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Commands<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">IaC misconfiguration:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">trivy config .\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Block high\/critical findings:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">trivy config --severity HIGH,CRITICAL --<span class=\"hljs-keyword\">exit<\/span>-code <span class=\"hljs-number\">1<\/span> .\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Scan repository\/filesystem for both misconfiguration and secrets:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">trivy fs --scanners misconfig,secret --severity HIGH,CRITICAL .\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Use tfvars when appropriate:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">trivy config --tf-vars environments\/staging\/staging.tfvars .\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Policy<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Run a fast high\/critical gate locally.<\/li>\n\n\n\n<li>Run the authoritative scan in CI.<\/li>\n\n\n\n<li>Exceptions need a reason, owner, and preferably expiration date.<\/li>\n\n\n\n<li>Treat scanner output as a risk signal, not proof of exploitability.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">11. Checkov<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">What<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Checkov is an IaC static analysis and compliance\/policy scanner with Terraform source and Terraform plan scanning.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Install in an isolated Python environment:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">pipx install checkov\ncheckov --version\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Source scan:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">checkov -d .\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Plan scan:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">terraform plan -out=tfplan.binary\nterraform show -json tfplan.binary &gt; tfplan.json\ncheckov -f tfplan.json\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>Security:<\/strong>&nbsp;plan JSON may contain injected values and secrets. Create and scan it only in trusted CI and avoid publishing it as a broadly readable artifact.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Trivy vs Checkov<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Situation<\/th><th class=\"has-text-align-left\" data-align=\"left\">Recommendation<\/th><\/tr><\/thead><tbody><tr><td>Small\/medium team wants one broad security tool<\/td><td><strong>Trivy only<\/strong><\/td><\/tr><tr><td>Organization already standardizes Prisma\/Checkov policies\/compliance<\/td><td><strong>Checkov only<\/strong>&nbsp;can be enough<\/td><\/tr><tr><td>Regulated org has distinct, non-overlapping policy packs and accepts runtime cost<\/td><td><strong>Both<\/strong>, but define ownership<\/td><\/tr><tr><td>Same findings duplicated in both with no governance benefit<\/td><td>Remove one<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Do not add both simply to create the appearance of more security.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 6 \u2014 Terraform Testing<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">12. Native&nbsp;<code>terraform test<\/code><\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">What<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Terraform&#8217;s native test framework executes&nbsp;<code>.tftest.hcl<\/code>&nbsp;files with&nbsp;<code>run<\/code>, variables, assertions, plan\/apply modes, helper modules, and provider\/resource mocking.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Minimal example<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><code>main.tf<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">variable <span class=\"hljs-string\">\"environment\"<\/span> {\n  type = string\n\n  validation {\n    condition     = contains(&#91;<span class=\"hljs-string\">\"dev\"<\/span>, <span class=\"hljs-string\">\"staging\"<\/span>, <span class=\"hljs-string\">\"prod\"<\/span>], <span class=\"hljs-keyword\">var<\/span>.environment)\n    error_message = <span class=\"hljs-string\">\"environment must be dev, staging, or prod\"<\/span>\n  }\n}\n\nlocals {\n  name = <span class=\"hljs-string\">\"payments-${var.environment}\"<\/span>\n}\n\noutput <span class=\"hljs-string\">\"name\"<\/span> {\n  description = <span class=\"hljs-string\">\"Generated workload name.\"<\/span>\n  value       = local.name\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><code>tests\/name.tftest.hcl<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">run <span class=\"hljs-string\">\"staging_name\"<\/span> {\n  command = plan\n\n  variables {\n    environment = <span class=\"hljs-string\">\"staging\"<\/span>\n  }\n\n  assert {\n    condition     = output.name == <span class=\"hljs-string\">\"payments-staging\"<\/span>\n    error_message = <span class=\"hljs-string\">\"Unexpected generated name.\"<\/span>\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">terraform test\nterraform test -filter=tests\/name.tftest.hcl\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Provider mocking<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For modules using real providers, Terraform 1.7+ can mock providers:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">mock_provider <span class=\"hljs-string\">\"aws\"<\/span> {}\n\nrun <span class=\"hljs-string\">\"secure_bucket_configuration\"<\/span> {\n  command = plan\n\n  assert {\n    condition     = aws_s3_bucket.example.bucket == <span class=\"hljs-keyword\">var<\/span>.bucket_name\n    error_message = <span class=\"hljs-string\">\"Bucket name wiring is incorrect.\"<\/span>\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This is excellent for logic\/interface tests without cloud credentials or real resources.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Test pyramid<\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\"> <code>           Live E2E \/ integration\n          Terratest \/ custom tests\n        -----------------------------\n             terraform test\n        plan\/apply + mocks\/assertions\n      ---------------------------------\n      validate + TFLint + security scan\n    -------------------------------------\n                  fmt\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">What each layer proves<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Tool<\/th><th class=\"has-text-align-left\" data-align=\"left\">Proves<\/th><\/tr><\/thead><tbody><tr><td><code>validate<\/code><\/td><td>Terraform configuration can be parsed\/validated<\/td><\/tr><tr><td>TFLint<\/td><td>Lint\/provider conventions<\/td><\/tr><tr><td><code>terraform test<\/code><\/td><td>Declared module behavior\/assertions<\/td><\/tr><tr><td>Real&nbsp;<code>plan<\/code><\/td><td>Concrete proposed change in target context<\/td><\/tr><tr><td>Terratest<\/td><td>Deployed infrastructure actually behaves as expected<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Terratest<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Use when you need:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>actual AWS\/Azure\/GCP API behavior,<\/li>\n\n\n\n<li>HTTP\/DNS\/TLS checks,<\/li>\n\n\n\n<li>retries\/eventual consistency,<\/li>\n\n\n\n<li>cross-component E2E validation,<\/li>\n\n\n\n<li>deploy \u2192 validate \u2192 destroy workflows.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Costs:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>slower,<\/li>\n\n\n\n<li>cloud cost,<\/li>\n\n\n\n<li>credential requirements,<\/li>\n\n\n\n<li>cleanup complexity,<\/li>\n\n\n\n<li>flaky external dependencies.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Default:<\/strong>&nbsp;native&nbsp;<code>terraform test<\/code>&nbsp;first; Terratest only for gaps.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Kitchen-Terraform<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Still useful in teams already invested in Test Kitchen\/Ruby-based infrastructure testing. It is rarely the first recommendation for a new Terraform-only test platform in 2026.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 7 \u2014 Terraform Documentation<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">13.&nbsp;<code>terraform-docs<\/code><\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">What<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Generates Terraform module documentation from source:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>requirements,<\/li>\n\n\n\n<li>providers,<\/li>\n\n\n\n<li>modules,<\/li>\n\n\n\n<li>resources,<\/li>\n\n\n\n<li>inputs,<\/li>\n\n\n\n<li>outputs.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Installation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">macOS:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">brew install terraform-docs\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Usage<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">terraform-docs<\/span> <span class=\"hljs-selector-tag\">markdown<\/span> <span class=\"hljs-selector-tag\">table<\/span> .\n<span class=\"hljs-selector-tag\">terraform-docs<\/span> <span class=\"hljs-selector-tag\">markdown<\/span> <span class=\"hljs-selector-tag\">table<\/span> <span class=\"hljs-selector-tag\">--output-file<\/span> <span class=\"hljs-selector-tag\">README<\/span><span class=\"hljs-selector-class\">.md<\/span> <span class=\"hljs-selector-tag\">--output-mode<\/span> <span class=\"hljs-selector-tag\">inject<\/span> .\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">README before<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\"># VPC module<\/span>\n\nCreates a VPC.\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">README with markers<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-24\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"># VPC module\n\nCreates a VPC.\n\n<span class=\"hljs-comment\">&lt;!-- BEGIN&lt;em&gt;_TF_&lt;\/em&gt;DOCS --&gt;<\/span>\n<span class=\"hljs-comment\">&lt;!-- END&lt;em&gt;_TF_&lt;\/em&gt;DOCS --&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">After&nbsp;<code>terraform-docs<\/code>, the marker section contains generated Requirements, Providers, Inputs, Outputs, etc.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><code>.terraform-docs.yml<\/code><\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-25\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">formatter: \"markdown table\"\n\noutput:\n  file: \"README.md\"\n  mode: \"inject\"\n  template: |-\n    <span class=\"hljs-comment\">&lt;!-- BEGIN_TF_DOCS --&gt;<\/span>\n    {{ .Content }}\n    <span class=\"hljs-comment\">&lt;!-- END_TF_DOCS --&gt;<\/span>\n\nsort:\n  enabled: true\n  by: \"required\"\n\nsettings:\n  anchor: true\n  default: true\n  description: true\n  escape: true\n  hide-empty: false\n  html: true\n  lockfile: true\n  read-comments: true\n  required: true\n  sensitive: true\n  type: true\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-25\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Best practice<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Generated interface reference is not a replacement for authored documentation. A gold-standard module README has:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>purpose,<\/li>\n\n\n\n<li>architecture\/behavior,<\/li>\n\n\n\n<li>security assumptions,<\/li>\n\n\n\n<li>usage example,<\/li>\n\n\n\n<li>generated inputs\/outputs,<\/li>\n\n\n\n<li>upgrade notes,<\/li>\n\n\n\n<li>operational caveats.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 8 \u2014 Terraform Cost Management<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">14. Infracost<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">What<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Infracost estimates cloud cost and turns Terraform changes into a cost delta visible during review.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Workflow<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Terraform change\n      \u2193\nPlan\/HCL analysis\n      \u2193\nInfracost\n      \u2193\nMonthly cost estimate + delta\n      \u2193\nPR reviewer sees cost impact\n      \u2193\nBudget \/ FinOps decision\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">CLI<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-26\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">infracost<\/span> <span class=\"hljs-selector-tag\">breakdown<\/span> <span class=\"hljs-selector-tag\">--path<\/span> .\n<span class=\"hljs-selector-tag\">infracost<\/span> <span class=\"hljs-selector-tag\">breakdown<\/span> <span class=\"hljs-selector-tag\">--path<\/span> . <span class=\"hljs-selector-tag\">--format<\/span> <span class=\"hljs-selector-tag\">json<\/span> <span class=\"hljs-selector-tag\">--out-file<\/span> <span class=\"hljs-selector-tag\">infracost-base<\/span><span class=\"hljs-selector-class\">.json<\/span>\n<span class=\"hljs-selector-tag\">infracost<\/span> <span class=\"hljs-selector-tag\">diff<\/span> <span class=\"hljs-selector-tag\">--path<\/span> . <span class=\"hljs-selector-tag\">--compare-to<\/span> <span class=\"hljs-selector-tag\">infracost-base<\/span><span class=\"hljs-selector-class\">.json<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-26\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">It can also consume Terraform plan JSON when source parsing is not sufficient.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">CI recommendation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For GitHub, prefer the vendor&#8217;s current GitHub App or modern&nbsp;<code>diff<\/code>\/<code>scan<\/code>&nbsp;actions where allowed. Avoid building new integrations around legacy action patterns unless you need the lower-level CLI.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Policy<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Cost estimation is advisory unless your organization has explicitly defined budgets\/thresholds. A small cost increase may be correct; a zero-cost plan may still be dangerously destructive.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 9 \u2014 Git Automation<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">15.&nbsp;<code>pre-commit-terraform<\/code><\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Why high value<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">It moves deterministic checks left:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">git commit\n    \u2193\nfmt\n    \u2193\nvalidate\n    \u2193\nTFLint\n    \u2193\nTrivy\n    \u2193\nterraform-docs\n    \u2193\ncommit accepted\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Installation<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-27\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">brew install pre-commit\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># or<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\npipx install pre-commit\n\npre-commit --version\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-27\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Production-ready&nbsp;<code>.pre-commit-config.yaml<\/code><\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-28\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">repos:\n  - repo: https:<span class=\"hljs-comment\">\/\/github.com\/antonbabenko\/pre-commit-terraform<\/span>\n    rev: v1<span class=\"hljs-number\">.108<\/span><span class=\"hljs-number\">.1<\/span>\n    <span class=\"hljs-attr\">hooks<\/span>:\n      - id: terraform_fmt\n\n      - id: terraform_validate\n        <span class=\"hljs-attr\">args<\/span>:\n          - --hook-config=--retry-once-<span class=\"hljs-keyword\">with<\/span>-cleanup=<span class=\"hljs-literal\">true<\/span>\n\n      - id: terraform_tflint\n\n      - id: terraform_trivy\n        <span class=\"hljs-attr\">args<\/span>:\n          - --args=--severity=HIGH,CRITICAL\n          - --args=--exit-code=<span class=\"hljs-number\">1<\/span>\n\n      - id: terraform_docs\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-28\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Enable:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">pre-commit install\npre-commit run --all-files\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Local vs CI split<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Local<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Run checks that are fast and deterministic:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>fmt,<\/li>\n\n\n\n<li>validate,<\/li>\n\n\n\n<li>TFLint,<\/li>\n\n\n\n<li>focused Trivy,<\/li>\n\n\n\n<li>docs.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">CI<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Re-run them authoritatively and add:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>full security policy,<\/li>\n\n\n\n<li><code>terraform test<\/code>,<\/li>\n\n\n\n<li>target-context plan,<\/li>\n\n\n\n<li>Infracost,<\/li>\n\n\n\n<li>plan-policy checks,<\/li>\n\n\n\n<li>approval\/apply rules.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Never rely solely on local hooks; users can skip them and environments differ.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 10 \u2014 Terraform Project Structure<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">16. Recommended repository layout<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">For a moderate monorepo:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">terraform\/\n\u251c\u2500\u2500 modules\/\n\u2502   \u251c\u2500\u2500 vpc\/\n\u2502   \u2502   \u251c\u2500\u2500 main.tf\n\u2502   \u2502   \u251c\u2500\u2500 variables.tf\n\u2502   \u2502   \u251c\u2500\u2500 outputs.tf\n\u2502   \u2502   \u251c\u2500\u2500 versions.tf\n\u2502   \u2502   \u251c\u2500\u2500 tests\/\n\u2502   \u2502   \u2514\u2500\u2500 README.md\n\u2502   \u251c\u2500\u2500 eks\/\n\u2502   \u251c\u2500\u2500 rds\/\n\u2502   \u2514\u2500\u2500 iam\/\n\u2502\n\u251c\u2500\u2500 environments\/\n\u2502   \u251c\u2500\u2500 development\/\n\u2502   \u251c\u2500\u2500 staging\/\n\u2502   \u2514\u2500\u2500 production\/\n\u2502\n\u251c\u2500\u2500 .github\/\n\u2502   \u2514\u2500\u2500 workflows\/\n\u251c\u2500\u2500 .pre-commit-config.yaml\n\u251c\u2500\u2500 .tflint.hcl\n\u251c\u2500\u2500 renovate.json\n\u2514\u2500\u2500 README.md\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Root module vs child module<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Root module:<\/strong>&nbsp;deployment unit. Owns backend, environment composition, concrete provider constraints, and state.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Child\/reusable module:<\/strong>&nbsp;abstraction with documented inputs\/outputs. It should not normally own a backend or assume one environment\/account.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Files<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Recommended per module:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-29\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">main.tf        <span class=\"hljs-comment\"># primary resources\/module calls<\/span>\nvariables.tf   <span class=\"hljs-comment\"># inputs<\/span>\noutputs.tf     <span class=\"hljs-comment\"># outputs<\/span>\nversions.tf    <span class=\"hljs-comment\"># Terraform\/provider requirements<\/span>\nlocals.tf      <span class=\"hljs-comment\"># optional, when locals warrant separation<\/span>\ndata.tf        <span class=\"hljs-comment\"># optional<\/span>\ntests\/         <span class=\"hljs-comment\"># native tests<\/span>\nREADME.md\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-29\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Split further by domain only when it improves readability; Terraform loads all&nbsp;<code>.tf<\/code>&nbsp;files in the directory together.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Environment separation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Prefer separate state per meaningful failure\/ownership boundary:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">production\/network\nproduction\/cluster\nproduction\/database\nproduction\/app-platform\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">rather than one giant production state.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Reasons:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>smaller blast radius,<\/li>\n\n\n\n<li>simpler permissions,<\/li>\n\n\n\n<li>faster plan,<\/li>\n\n\n\n<li>clearer ownership,<\/li>\n\n\n\n<li>fewer unrelated lock conflicts.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Repository strategy<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Strategy<\/th><th class=\"has-text-align-left\" data-align=\"left\">Pros<\/th><th class=\"has-text-align-left\" data-align=\"left\">Cons<\/th><th class=\"has-text-align-left\" data-align=\"left\">Best fit<\/th><\/tr><\/thead><tbody><tr><td>Monorepo<\/td><td>Shared standards, atomic changes, discovery<\/td><td>CI\/path complexity, repo scale<\/td><td>Central platform teams<\/td><\/tr><tr><td>Multi-repo by system<\/td><td>Strong ownership boundaries<\/td><td>Cross-repo upgrades harder<\/td><td>Independent teams<\/td><\/tr><tr><td>One repo per environment<\/td><td>Strong isolation<\/td><td>Duplication\/drift risk<\/td><td>Strict org separation<\/td><\/tr><tr><td>One repo per reusable module<\/td><td>Independent versioning\/release<\/td><td>Many repos<\/td><td>Mature internal module ecosystem<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">There is no universal &#8220;one repo per environment&#8221; rule. Choose boundaries from ownership, release cadence, permissions, and blast radius.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 11 \u2014 Terragrunt<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">17. Terragrunt<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Why it exists<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Terragrunt wraps Terraform\/OpenTofu to reduce repetition and orchestrate many related root modules.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Common value:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>shared remote-state configuration,<\/li>\n\n\n\n<li>generated provider configuration,<\/li>\n\n\n\n<li>reusable environment\/account configuration,<\/li>\n\n\n\n<li>dependency outputs,<\/li>\n\n\n\n<li>multi-account\/multi-region layouts,<\/li>\n\n\n\n<li>ordered execution across units.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Current CLI model<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Modern Terragrunt 1.x:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">terragrunt plan\nterragrunt apply\nterragrunt run --all plan\nterragrunt run --all apply\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Do not teach new teams to standardize on legacy&nbsp;<code>run-all<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Example<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">live\/\n\u251c\u2500\u2500 root.hcl\n\u251c\u2500\u2500 dev\/\n\u2502   \u251c\u2500\u2500 vpc\/terragrunt.hcl\n\u2502   \u2514\u2500\u2500 app\/terragrunt.hcl\n\u2514\u2500\u2500 prod\/\n    \u251c\u2500\u2500 vpc\/terragrunt.hcl\n    \u2514\u2500\u2500 app\/terragrunt.hcl\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><code>root.hcl<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-30\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">remote_state {\n  backend = <span class=\"hljs-string\">\"s3\"<\/span>\n\n  config = {\n    bucket       = <span class=\"hljs-string\">\"example-terraform-state\"<\/span>\n    key          = <span class=\"hljs-string\">\"${path_relative_to_include()}\/terraform.tfstate\"<\/span>\n    region       = <span class=\"hljs-string\">\"ap-northeast-1\"<\/span>\n    encrypt      = <span class=\"hljs-literal\">true<\/span>\n    use_lockfile = <span class=\"hljs-literal\">true<\/span>\n  }\n}\n\ngenerate <span class=\"hljs-string\">\"provider\"<\/span> {\n  path      = <span class=\"hljs-string\">\"provider.generated.tf\"<\/span>\n  if_exists = <span class=\"hljs-string\">\"overwrite_terragrunt\"<\/span>\n\n  contents = &lt;&lt;EOF\nprovider <span class=\"hljs-string\">\"aws\"<\/span> {\n  region = <span class=\"hljs-string\">\"ap-northeast-1\"<\/span>\n}\nEOF\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-30\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><code>prod\/app\/terragrunt.hcl<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-31\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-keyword\">include<\/span> <span class=\"hljs-string\">\"root\"<\/span> {\n  path = find_in_parent_folders(<span class=\"hljs-string\">\"root.hcl\"<\/span>)\n}\n\nterraform {\n  source = <span class=\"hljs-string\">\"..\/..\/..\/modules\/app\"<\/span>\n}\n\ndependency <span class=\"hljs-string\">\"vpc\"<\/span> {\n  config_path = <span class=\"hljs-string\">\"..\/vpc\"<\/span>\n}\n\ninputs = {\n  vpc_id = dependency.vpc.outputs.vpc_id\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-31\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">When Terragrunt is unnecessary<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Do not add it when:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>you have a few simple root modules,<\/li>\n\n\n\n<li>HCP Terraform already removes the orchestration pain you were trying to solve,<\/li>\n\n\n\n<li>duplication is small and explicit,<\/li>\n\n\n\n<li>the team does not need multi-unit orchestration,<\/li>\n\n\n\n<li>introducing a second language\/workflow would cost more than it saves.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Terraform alone is a valid production architecture.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 12 \u2014 CI\/CD<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">18. GitHub Actions<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Pipeline architecture<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-32\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">Pull request\n   \u2193\ncheckout\n   \u2193\nsetup pinned Terraform\n   \u2193\nfmt -check\n   \u2193\ninit -backend=<span class=\"hljs-literal\">false<\/span> (<span class=\"hljs-keyword\">static<\/span>\/test jobs)\n   \u2193\nvalidate\n   \u2193\nTFLint\n   \u2193\nTrivy\n   \u2193\nterraform test\n   \u2193\nOIDC \u2192 target read\/plan role\n   \u2193\nterraform init\n   \u2193\nterraform plan\n   \u2193\nInfracost \/ policy \/ review\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-32\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Apply should be a&nbsp;<strong>separate controlled phase<\/strong>, not an automatic side effect of every PR plan.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Example CI workflow<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-33\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">name: terraform-ci\n\non:\n  pull_request:\n    paths:\n      - <span class=\"hljs-string\">\"**\/*.tf\"<\/span>\n      - <span class=\"hljs-string\">\"**\/*.tfvars\"<\/span>\n      - <span class=\"hljs-string\">\"**\/*.tftest.hcl\"<\/span>\n      - <span class=\"hljs-string\">\".github\/workflows\/terraform-ci.yml\"<\/span>\n\npermissions:\n  contents: read\n\njobs:\n  quality:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions\/checkout@v4\n\n      - name: Setup Terraform\n        uses: hashicorp\/setup-terraform@v3\n        with:\n          terraform_version: <span class=\"hljs-string\">\"1.16.0\"<\/span>\n          terraform_wrapper: <span class=\"hljs-keyword\">false<\/span>\n\n      - name: Format\n        run: terraform fmt -check -recursive\n\n      - name: Init without backend\n        run: terraform init -backend=<span class=\"hljs-keyword\">false<\/span> -input=<span class=\"hljs-keyword\">false<\/span>\n\n      - name: Validate\n        run: terraform validate -no-color\n\n      - name: Setup TFLint\n        uses: terraform-linters\/setup-tflint@v6\n        with:\n          tflint_version: <span class=\"hljs-string\">\"v0.64.0\"<\/span>\n          cache: <span class=\"hljs-keyword\">true<\/span>\n\n      - name: Init TFLint plugins\n        run: tflint --init\n        env:\n          GITHUB_TOKEN: ${{ github.token }}\n\n      - name: TFLint\n        run: tflint --recursive --format=compact\n\n      - name: Trivy IaC\n        uses: aquasecurity\/trivy-action@master\n        with:\n          scan-type: config\n          scan-ref: .\n          severity: HIGH,CRITICAL\n          <span class=\"hljs-keyword\">exit<\/span>-code: <span class=\"hljs-string\">\"1\"<\/span>\n\n      - name: Terraform tests\n        run: terraform test\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-33\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">Supply-chain hardening note<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">For a true enterprise workflow, pin reusable GitHub Actions to reviewed immutable commit SHAs or enforce an organization action allowlist. Major tags are easier to read in a handbook but are mutable references.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Plan job with AWS OIDC<\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\"> <code> plan:\n    needs: quality\n    runs-on: ubuntu-latest\n\n    permissions:\n      contents: read\n      id-token: write\n\n    environment: staging\n\n    steps:\n      - uses: actions\/checkout@v4\n\n      - uses: hashicorp\/setup-terraform@v3\n        with:\n          terraform_version: \"1.16.0\"\n          terraform_wrapper: false\n\n      - name: Authenticate to AWS using OIDC\n        uses: aws-actions\/configure-aws-credentials@v6\n        with:\n          role-to-assume: arn:aws:iam::123456789012:role\/terraform-staging-plan\n          aws-region: ap-northeast-1\n\n      - name: Init\n        working-directory: environments\/staging\n        run: terraform init -input=false\n\n      - name: Plan\n        working-directory: environments\/staging\n        run: terraform plan -input=false -no-color\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">OIDC design<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Trust should be constrained by claims such as:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>organization\/repository identity,<\/li>\n\n\n\n<li>environment,<\/li>\n\n\n\n<li>branch\/tag,<\/li>\n\n\n\n<li>immutable repository\/org IDs where supported.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The plan role and apply role should be separate when practical.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Apply model<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Recommended:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-34\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">PR merged to <span class=\"hljs-keyword\">protected<\/span> branch\n       \u2193\n<span class=\"hljs-keyword\">new<\/span> plan in controlled execution context\n       \u2193\npolicy checks\n       \u2193\nenvironment approval\n       \u2193\nassume apply role using OIDC\n       \u2193\nterraform apply\n       \u2193\nverification\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-34\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Why re-plan after merge? The reviewed branch may no longer be identical to the final protected branch if multiple changes merged.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If your system securely preserves a reviewed saved plan and guarantees code\/state identity, applying the preserved plan is also valid.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Secrets<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Never store:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-35\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">AWS_ACCESS_KEY_ID\nAWS_SECRET_ACCESS_KEY\nlong-lived Azure client secrets\nlong-lived GCP service-account <span class=\"hljs-built_in\">JSON<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-35\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">when federation is available.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">19. GitLab CI<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Use the same gates:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">validate \u2192 lint \u2192 security \u2192 test \u2192 plan \u2192 policy \u2192 approval \u2192 apply\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">GitLab supports OIDC ID tokens for cloud federation. Modern GitLab uses&nbsp;<strong>ID tokens<\/strong>, not the removed legacy&nbsp;<code>CI_JOB_JWT_V2<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Illustrative shape:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-36\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">stages:\n  - quality\n  - plan\n  - apply\n\nterraform-quality:\n  stage: quality\n  image: hashicorp\/terraform:<span class=\"hljs-number\">1.16<\/span><span class=\"hljs-number\">.0<\/span>\n  script:\n    - terraform fmt -check -recursive\n    - terraform init -backend=<span class=\"hljs-keyword\">false<\/span> -input=<span class=\"hljs-keyword\">false<\/span>\n    - terraform validate\n    - terraform test\n\nterraform-plan:\n  stage: plan\n  id_tokens:\n    GITLAB_OIDC_TOKEN:\n      aud: sts.amazonaws.com\n  script:\n    - <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Exchange GitLab OIDC token for short-lived cloud credentials\"<\/span>\n    - terraform init -input=<span class=\"hljs-keyword\">false<\/span>\n    - terraform plan -input=<span class=\"hljs-keyword\">false<\/span>\n\nterraform-apply:\n  stage: apply\n  when: manual\n  rules:\n    - <span class=\"hljs-keyword\">if<\/span>: <span class=\"hljs-string\">'$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'<\/span>\n  script:\n    - terraform apply -input=<span class=\"hljs-keyword\">false<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-36\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Exact AWS\/Azure\/GCP token-exchange steps should follow the current cloud\/GitLab federation documentation and your organization&#8217;s role design.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 13 \u2014 Atlantis<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">20. Atlantis<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">What<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Atlantis is a server that reacts to pull-request comments\/events and runs Terraform plans\/applies in a PR-driven workflow.<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">PR opened\n   \u2193\nAtlantis autoplan\n   \u2193\nreview plan\n   \u2193\napproval\n   \u2193\natlantis apply\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Commands<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">atlantis plan\natlantis apply\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Strengths<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>PR-native workflow,<\/li>\n\n\n\n<li>project\/workspace awareness,<\/li>\n\n\n\n<li>locking,<\/li>\n\n\n\n<li>plan\/apply conversation in VCS,<\/li>\n\n\n\n<li>works across multiple Terraform projects.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Atlantis locks a directory\/workspace when a plan is active so competing PRs do not casually race the same deployment unit.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Atlantis vs GitHub Actions vs HCP Terraform<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Need<\/th><th class=\"has-text-align-left\" data-align=\"left\">Better fit<\/th><\/tr><\/thead><tbody><tr><td>Generic build\/test ecosystem<\/td><td>GitHub\/GitLab CI<\/td><\/tr><tr><td>Comment-driven Terraform workflow you operate<\/td><td>Atlantis<\/td><\/tr><tr><td>Managed state, remote runs, RBAC, registry, run tasks, policy, health<\/td><td>HCP Terraform<\/td><\/tr><tr><td>Self-hosted HCP feature set<\/td><td>Terraform Enterprise<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Avoid running Atlantis&nbsp;<em>and<\/em>&nbsp;a separate CI apply system against the same state without a very explicit ownership model.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 14 \u2014 HCP Terraform<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">21. HCP Terraform<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">What it adds beyond CLI<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>remote state and state history,<\/li>\n\n\n\n<li>stronger centralized run coordination,<\/li>\n\n\n\n<li>remote runs,<\/li>\n\n\n\n<li>workspaces,<\/li>\n\n\n\n<li>projects,<\/li>\n\n\n\n<li>variable sets,<\/li>\n\n\n\n<li>self-hosted agents,<\/li>\n\n\n\n<li>private module\/provider registry,<\/li>\n\n\n\n<li>run tasks,<\/li>\n\n\n\n<li>policy enforcement,<\/li>\n\n\n\n<li>VCS-driven speculative plans,<\/li>\n\n\n\n<li>team\/project\/workspace RBAC,<\/li>\n\n\n\n<li>health assessments including drift detection and continuous validation,<\/li>\n\n\n\n<li>API\/audit capabilities depending on edition.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Enterprise workflow<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-37\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">Git branch\n   \u2193\nPull request\n   \u2193\nHCP speculative plan\n   \u2193\nCI\/security\/cost checks\n   \u2193\nreview\n   \u2193\nmerge\n   \u2193\nHCP run\n   \u2193\npolicy checks \/ run tasks\n   \u2193\napproval\n   \u2193\nremote worker <span class=\"hljs-keyword\">or<\/span> <span class=\"hljs-keyword\">private<\/span> agent\n   \u2193\ncloud\n   \u2193\nhealth assessment \/ drift detection\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-37\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Workspaces<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">An HCP Terraform workspace is a deployment\/state boundary, not the same concept as a Terraform CLI workspace.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Use workspaces to separate infrastructure with different:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>state,<\/li>\n\n\n\n<li>permissions,<\/li>\n\n\n\n<li>ownership,<\/li>\n\n\n\n<li>credentials,<\/li>\n\n\n\n<li>run cadence,<\/li>\n\n\n\n<li>blast radius.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Avoid a single giant workspace for an entire company environment.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Projects<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Use projects as access-control\/organizational boundaries grouping workspaces and Stacks.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Good project boundaries:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>platform,<\/li>\n\n\n\n<li>analytics,<\/li>\n\n\n\n<li>security,<\/li>\n\n\n\n<li>business unit,<\/li>\n\n\n\n<li>account\/portfolio.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Variable sets<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Good uses:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>common non-secret environment settings,<\/li>\n\n\n\n<li>dynamic credential configuration,<\/li>\n\n\n\n<li>shared provider settings,<\/li>\n\n\n\n<li>organization\/project-wide standards.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Do not use variable sets as an excuse to hide every important deployment value from code review.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Agents<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Use HCP Terraform Agents when the run must reach:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>private VPC\/VNet resources,<\/li>\n\n\n\n<li>on-premises services,<\/li>\n\n\n\n<li>private APIs,<\/li>\n\n\n\n<li>network-isolated providers.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Agents need only outbound connectivity to HCP Terraform.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Do not deploy agents merely because &#8220;enterprise teams use agents.&#8221; Built-in workers are simpler when private network access\/custom tooling is not required.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Private registry<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Use the private registry for:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>approved reusable modules,<\/li>\n\n\n\n<li>internally developed providers,<\/li>\n\n\n\n<li>versioned organization patterns.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">A mature platform team should make the secure path the easy path:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-38\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">developer need \u2192 approved private <span class=\"hljs-built_in\">module<\/span> \u2192 small <span class=\"hljs-keyword\">set<\/span> of inputs \u2192 tests\/policy \u2192 deploy\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-38\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Run tasks<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Useful for third-party checks at lifecycle stages such as:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>pre-plan,<\/li>\n\n\n\n<li>post-plan,<\/li>\n\n\n\n<li>pre-apply,<\/li>\n\n\n\n<li>post-apply.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Examples:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>security platform,<\/li>\n\n\n\n<li>cost governance,<\/li>\n\n\n\n<li>custom compliance,<\/li>\n\n\n\n<li>image validation.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Only integrate trusted run-task services because run-related information can be sensitive.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">HCP Terraform vs Terraform Enterprise vs CLI<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Capability<\/th><th class=\"has-text-align-left\" data-align=\"left\">Terraform CLI<\/th><th class=\"has-text-align-left\" data-align=\"left\">HCP Terraform<\/th><th class=\"has-text-align-left\" data-align=\"left\">Terraform Enterprise<\/th><\/tr><\/thead><tbody><tr><td>Terraform engine<\/td><td>Yes<\/td><td>Yes<\/td><td>Yes<\/td><\/tr><tr><td>Local execution<\/td><td>Yes<\/td><td>Can integrate<\/td><td>Can integrate<\/td><\/tr><tr><td>Managed SaaS<\/td><td>No<\/td><td>Yes<\/td><td>No<\/td><\/tr><tr><td>Self-hosted platform<\/td><td>No<\/td><td>No<\/td><td>Yes<\/td><\/tr><tr><td>Remote state<\/td><td>Via backend<\/td><td>Built in<\/td><td>Built in<\/td><\/tr><tr><td>Remote runs<\/td><td>No platform by itself<\/td><td>Built in<\/td><td>Built in<\/td><\/tr><tr><td>Projects\/RBAC<\/td><td>No<\/td><td>Yes<\/td><td>Yes<\/td><\/tr><tr><td>Private registry<\/td><td>No<\/td><td>Yes<\/td><td>Yes<\/td><\/tr><tr><td>Agents<\/td><td>N\/A<\/td><td>Yes<\/td><td>Yes<\/td><\/tr><tr><td>Policy\/run tasks<\/td><td>External<\/td><td>Integrated<\/td><td>Integrated<\/td><\/tr><tr><td>Drift\/health<\/td><td>External<\/td><td>Integrated by edition<\/td><td>Integrated<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Terraform Enterprise is the self-hosted distribution of the HCP Terraform application for organizations requiring private hosting\/advanced architecture\/compliance controls.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 15 \u2014 Policy as Code<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">22. Sentinel<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">What<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">HashiCorp&#8217;s policy-as-code framework integrated with HCP Terraform\/TFE.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Enforcement styles<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Advisory<\/strong>\u00a0\u2014 report failure but allow run.<\/li>\n\n\n\n<li><strong>Soft mandatory \/ overridable<\/strong>\u00a0\u2014 block unless authorized override.<\/li>\n\n\n\n<li><strong>Hard mandatory \/ mandatory<\/strong>\u00a0\u2014 block until fixed, subject to platform policy-set semantics.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Use terms carefully because HCP Terraform&#8217;s UI\/framework-specific naming can differ.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Example policy scenarios<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>approved AWS regions,<\/li>\n\n\n\n<li>no public storage,<\/li>\n\n\n\n<li>allowed EC2 families,<\/li>\n\n\n\n<li>mandatory tags,<\/li>\n\n\n\n<li>encryption required,<\/li>\n\n\n\n<li>restrict resource deletion,<\/li>\n\n\n\n<li>only approved modules.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Rollout model<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-39\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">new<\/span> rule\n  \u2193\nadvisory\n  \u2193\nobserve <span class=\"hljs-literal\">false<\/span> positives\n  \u2193\nfix existing estate\n  \u2193\nmandatory <span class=\"hljs-keyword\">with<\/span> controlled exception process\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-39\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Do not deploy dozens of untested hard-blocking policies on day one.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">23. OPA \/ Conftest<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">OPA<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">OPA evaluates Rego policies against structured input. Terraform plan JSON is a common input:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">terraform plan -out=tfplan\nterraform show -json tfplan &gt; tfplan.json\nopa exec --decision terraform\/analysis\/authz --bundle policy\/ tfplan.json\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Caveat: plan-time unknown values can limit what a policy can know.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conftest<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Conftest is a convenient CLI for executing Rego checks against structured files.<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">conftest test tfplan.json --policy policy\/\nconftest verify --policy policy\/\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Typical policy:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-40\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">package main\n\ndeny contains msg <span class=\"hljs-keyword\">if<\/span> {\n  some rc <span class=\"hljs-keyword\">in<\/span> input.resource_changes\n  rc.type == <span class=\"hljs-string\">\"aws_security_group_rule\"<\/span>\n  some cidr <span class=\"hljs-keyword\">in<\/span> rc.change.after.cidr_blocks\n  cidr == <span class=\"hljs-string\">\"0.0.0.0\/0\"<\/span>\n  <span class=\"hljs-attr\">msg<\/span> := sprintf(<span class=\"hljs-string\">\"Public ingress is not allowed: %s\"<\/span>, &#91;rc.address])\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-40\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Production policies need careful handling for null\/unknown\/alternate resource shapes; the snippet is intentionally small.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Decision guide<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Framework<\/th><th class=\"has-text-align-left\" data-align=\"left\">Best fit<\/th><\/tr><\/thead><tbody><tr><td>Terraform Policy (HCL)<\/td><td>Terraform-centric HCP teams wanting native HCL policy; evaluate maturity while beta<\/td><\/tr><tr><td>Sentinel<\/td><td>HCP\/TFE estates with HashiCorp governance investment<\/td><\/tr><tr><td>OPA<\/td><td>Vendor-neutral enterprise policy platform<\/td><\/tr><tr><td>Conftest<\/td><td>Lightweight local\/CI Rego execution<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 16 \u2014 Dependency Management<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">24. Renovate<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">What<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Automates dependency update PRs for:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Terraform\u00a0<code>required_version<\/code>,<\/li>\n\n\n\n<li><code>.terraform-version<\/code>,<\/li>\n\n\n\n<li>providers,<\/li>\n\n\n\n<li>Registry modules,<\/li>\n\n\n\n<li>Git-based modules where supported,<\/li>\n\n\n\n<li>GitHub Actions,<\/li>\n\n\n\n<li>many surrounding build dependencies.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Recommended&nbsp;<code>renovate.json<\/code><\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-41\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\">{\n  <span class=\"hljs-attr\">\"extends\"<\/span>: &#91;<span class=\"hljs-string\">\"config:recommended\"<\/span>],\n  <span class=\"hljs-attr\">\"labels\"<\/span>: &#91;<span class=\"hljs-string\">\"dependencies\"<\/span>],\n  <span class=\"hljs-attr\">\"packageRules\"<\/span>: &#91;\n    {\n      <span class=\"hljs-attr\">\"matchManagers\"<\/span>: &#91;<span class=\"hljs-string\">\"terraform\"<\/span>, <span class=\"hljs-string\">\"terraform-version\"<\/span>],\n      <span class=\"hljs-attr\">\"groupName\"<\/span>: <span class=\"hljs-string\">\"terraform dependencies\"<\/span>,\n      <span class=\"hljs-attr\">\"schedule\"<\/span>: &#91;<span class=\"hljs-string\">\"before 6am on monday\"<\/span>]\n    },\n    {\n      <span class=\"hljs-attr\">\"matchManagers\"<\/span>: &#91;<span class=\"hljs-string\">\"github-actions\"<\/span>],\n      <span class=\"hljs-attr\">\"groupName\"<\/span>: <span class=\"hljs-string\">\"github actions\"<\/span>\n    },\n    {\n      <span class=\"hljs-attr\">\"matchUpdateTypes\"<\/span>: &#91;<span class=\"hljs-string\">\"major\"<\/span>],\n      <span class=\"hljs-attr\">\"dependencyDashboardApproval\"<\/span>: <span class=\"hljs-literal\">true<\/span>\n    }\n  ]\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-41\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Upgrade policy<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Do not auto-merge major Terraform\/provider\/module upgrades into production infrastructure merely because CI is green.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Recommended:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">patch \u2192 may auto-merge after strong test gates\nminor \u2192 normal PR review\nmajor \u2192 explicit upgrade issue, changelog review, staged rollout\n<\/code><\/span><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">25.&nbsp;<code>tfupdate<\/code><\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><code>tfupdate<\/code>&nbsp;is a focused utility for rewriting Terraform\/OpenTofu core\/provider\/module version constraints and lock files.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-42\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">tfupdate<\/span> <span class=\"hljs-selector-tag\">terraform<\/span> <span class=\"hljs-selector-tag\">-v<\/span> 1<span class=\"hljs-selector-class\">.16<\/span><span class=\"hljs-selector-class\">.0<\/span> <span class=\"hljs-selector-tag\">-r<\/span> .\n<span class=\"hljs-selector-tag\">tfupdate<\/span> <span class=\"hljs-selector-tag\">provider<\/span> <span class=\"hljs-selector-tag\">aws<\/span> <span class=\"hljs-selector-tag\">-v<\/span> \"~&gt; 6<span class=\"hljs-selector-class\">.0<\/span>\" <span class=\"hljs-selector-tag\">-r<\/span> .\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-42\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">It is still useful for scripts and specialized bulk updates.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Default recommendation:<\/strong>&nbsp;prefer Renovate for continuous dependency management because it covers Terraform, modules, providers, actions, and many other dependencies with PR workflows. Use&nbsp;<code>tfupdate<\/code>&nbsp;when you specifically need command-line rewriting\/automation.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 17 \u2014 Terraform Console<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">26.&nbsp;<code>terraform console<\/code><\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">The console is one of the fastest ways to debug Terraform expressions without creating resources.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-43\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">terraform <span class=\"hljs-built_in\">console<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-43\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Examples:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-44\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">&gt; upper(<span class=\"hljs-string\">\"prod\"<\/span>)\n<span class=\"hljs-string\">\"PROD\"<\/span>\n\n&gt; length(&#91;<span class=\"hljs-string\">\"a\"<\/span>, <span class=\"hljs-string\">\"b\"<\/span>, <span class=\"hljs-string\">\"c\"<\/span>])\n<span class=\"hljs-number\">3<\/span>\n\n&gt; contains(&#91;<span class=\"hljs-string\">\"dev\"<\/span>, <span class=\"hljs-string\">\"prod\"<\/span>], <span class=\"hljs-string\">\"prod\"<\/span>)\n<span class=\"hljs-literal\">true<\/span>\n\n&gt; merge({a = <span class=\"hljs-number\">1<\/span>}, {b = <span class=\"hljs-number\">2<\/span>})\n{\n  <span class=\"hljs-string\">\"a\"<\/span> = <span class=\"hljs-number\">1<\/span>\n  <span class=\"hljs-string\">\"b\"<\/span> = <span class=\"hljs-number\">2<\/span>\n}\n\n&gt; &#91;<span class=\"hljs-keyword\">for<\/span> x <span class=\"hljs-keyword\">in<\/span> &#91;<span class=\"hljs-string\">\"api\"<\/span>, <span class=\"hljs-string\">\"worker\"<\/span>] : <span class=\"hljs-string\">\"prod-${x}\"<\/span>]\n&#91;\n  <span class=\"hljs-string\">\"prod-api\"<\/span>,\n  <span class=\"hljs-string\">\"prod-worker\"<\/span>,\n]\n\n&gt; { <span class=\"hljs-keyword\">for<\/span> x <span class=\"hljs-keyword\">in<\/span> &#91;<span class=\"hljs-string\">\"api\"<\/span>, <span class=\"hljs-string\">\"worker\"<\/span>] : <span class=\"hljs-function\"><span class=\"hljs-params\">x<\/span> =&gt;<\/span> upper(x) }\n{\n  <span class=\"hljs-string\">\"api\"<\/span> = <span class=\"hljs-string\">\"API\"<\/span>\n  <span class=\"hljs-string\">\"worker\"<\/span> = <span class=\"hljs-string\">\"WORKER\"<\/span>\n}\n\n&gt; cidrsubnet(<span class=\"hljs-string\">\"10.0.0.0\/16\"<\/span>, <span class=\"hljs-number\">8<\/span>, <span class=\"hljs-number\">1<\/span>)\n<span class=\"hljs-string\">\"10.0.1.0\/24\"<\/span>\n\n&gt; <span class=\"hljs-keyword\">try<\/span>({a = <span class=\"hljs-number\">1<\/span>}.b, <span class=\"hljs-string\">\"fallback\"<\/span>)\n<span class=\"hljs-string\">\"fallback\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-44\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Use it to prototype:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>for<\/code>\u00a0expressions,<\/li>\n\n\n\n<li>map merging,<\/li>\n\n\n\n<li>conditionals,<\/li>\n\n\n\n<li>CIDR subdivision,<\/li>\n\n\n\n<li>string transformations,<\/li>\n\n\n\n<li><code>try<\/code>\/<code>can<\/code>,<\/li>\n\n\n\n<li>regex,<\/li>\n\n\n\n<li>collection conversion.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Do not use console output as a substitute for a repeatable test when the expression matters to production behavior.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 18 \u2014 IDE + AI Development Workflow<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">27. Modern AI-assisted workflow<\/h1>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Developer intent\n      \u2193\nAI agent\n      \u2193\nTerraform MCP \/ current provider docs\n      \u2193\nGenerate\/refactor HCL\n      \u2193\nterraform fmt\n      \u2193\nterraform validate\n      \u2193\nTFLint\n      \u2193\nTrivy\n      \u2193\nterraform test\n      \u2193\nhuman review\n      \u2193\nreal plan\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Good AI tasks<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>generate boilerplate,<\/li>\n\n\n\n<li>explain resources,<\/li>\n\n\n\n<li>draft variables\/outputs,<\/li>\n\n\n\n<li>create native tests,<\/li>\n\n\n\n<li>refactor repeated HCL,<\/li>\n\n\n\n<li>produce module documentation,<\/li>\n\n\n\n<li>identify likely missing validation,<\/li>\n\n\n\n<li>compare plan output,<\/li>\n\n\n\n<li>search current provider docs through MCP.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Never blindly trust AI for<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>IAM,<\/li>\n\n\n\n<li>security-group\/network exposure,<\/li>\n\n\n\n<li>KMS\/key policy,<\/li>\n\n\n\n<li>destructive lifecycle changes,<\/li>\n\n\n\n<li>provider upgrade compatibility,<\/li>\n\n\n\n<li><code>terraform state<\/code>\u00a0operations,<\/li>\n\n\n\n<li>imports\/moves without address review,<\/li>\n\n\n\n<li>production apply,<\/li>\n\n\n\n<li>secret handling,<\/li>\n\n\n\n<li>cost assumptions.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">AI prompt template<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-45\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">Act as a senior Terraform reviewer.\n\nContext:\n- Terraform: <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">constraint<\/span>&gt;<\/span>\n- Provider: <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">source<\/span> + <span class=\"hljs-attr\">constraint<\/span>&gt;<\/span>\n- Execution: <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">HCP<\/span> <span class=\"hljs-attr\">Terraform<\/span> \/ <span class=\"hljs-attr\">CI<\/span> \/ <span class=\"hljs-attr\">local<\/span>&gt;<\/span>\n- Environment: <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dev<\/span>\/<span class=\"hljs-attr\">stage<\/span>\/<span class=\"hljs-attr\">prod<\/span>&gt;<\/span>\n- State boundary: <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">description<\/span>&gt;<\/span>\n\nTask:\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">requested<\/span> <span class=\"hljs-attr\">change<\/span>&gt;<\/span>\n\nBefore writing code:\n1. Use current Terraform\/provider documentation or Terraform MCP.\n2. Identify security, state, replacement, and cost risks.\n3. Preserve resource addresses unless a move is explicitly required.\n4. Do not hardcode credentials\/secrets.\n5. Prefer OIDC\/workload identity.\n\nAfter writing code:\n1. terraform fmt\n2. terraform validate\n3. TFLint\n4. Trivy\n5. terraform test\n6. explain expected plan and anything requiring human verification.\n\nNever run or recommend an unreviewed production apply.\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-45\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 19 \u2014 Terraform Developer Daily Workflow<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">28. Daily workflow<\/h1>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Pull latest code<\/strong><code>git switch main git pull --ff-only<\/code><\/li>\n\n\n\n<li><strong>Select project tool version<\/strong><code>tenv tf install terraform version<\/code><\/li>\n\n\n\n<li><strong>Create a branch<\/strong><code>git switch -c feat\/add-private-endpoint<\/code><\/li>\n\n\n\n<li><strong>Initialize<\/strong><code>terraform init<\/code><\/li>\n\n\n\n<li><strong>Write\/change Terraform<\/strong><\/li>\n\n\n\n<li><strong>Format<\/strong><code>terraform fmt -recursive<\/code><\/li>\n\n\n\n<li><strong>Validate<\/strong><code>terraform validate<\/code><\/li>\n\n\n\n<li><strong>Lint<\/strong><code>tflint --init tflint --recursive<\/code><\/li>\n\n\n\n<li><strong>Security scan<\/strong><code>trivy config --severity HIGH,CRITICAL --exit-code 1 .<\/code><\/li>\n\n\n\n<li><strong>Run tests<\/strong><code>terraform test<\/code><\/li>\n\n\n\n<li><strong>Generate docs<\/strong><code>terraform-docs markdown table --output-file README.md --output-mode inject .<\/code><\/li>\n\n\n\n<li><strong>Plan in a safe target context<\/strong><code>terraform plan<\/code><\/li>\n\n\n\n<li><strong>Inspect cost delta<\/strong><code>infracost breakdown --path .<\/code><\/li>\n\n\n\n<li><strong>Review your own diff<\/strong><code>git diff git status<\/code><\/li>\n\n\n\n<li><strong>Commit<\/strong><code>git add . git commit -m \"feat: add private endpoint\"<\/code><\/li>\n\n\n\n<li><strong>Pre-commit hooks execute<\/strong><\/li>\n\n\n\n<li><strong>Push<\/strong><code>git push -u origin HEAD<\/code><\/li>\n\n\n\n<li><strong>CI reproduces checks and plan<\/strong><\/li>\n\n\n\n<li><strong>PR review<\/strong>\u00a0Reviewer checks security, replacement, blast radius, cost, test quality.<\/li>\n\n\n\n<li><strong>Policy validation<\/strong><\/li>\n\n\n\n<li><strong>Controlled apply<\/strong><\/li>\n\n\n\n<li><strong>Post-deployment verification<\/strong>\u00a0Check service health, metrics, logs, connectivity, and expected outputs.<\/li>\n\n\n\n<li><strong>Drift\/health feedback<\/strong>\u00a0Operational system closes the loop.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 20 \u2014 Complete Developer Toolchain Reference<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">29. Master reference<\/h1>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Tool<\/th><th class=\"has-text-align-left\" data-align=\"left\">Problem solved<\/th><th class=\"has-text-align-left\" data-align=\"left\">Basic usage<\/th><th class=\"has-text-align-left\" data-align=\"left\">Value<\/th><th class=\"has-text-align-left\" data-align=\"left\">Limitation<\/th><th class=\"has-text-align-left\" data-align=\"left\">Classification<\/th><\/tr><\/thead><tbody><tr><td>Terraform CLI<\/td><td>IaC lifecycle<\/td><td><code>terraform plan<\/code><\/td><td>Core engine<\/td><td>Needs surrounding controls<\/td><td>MUST<\/td><\/tr><tr><td>VS Code<\/td><td>Authoring<\/td><td>open project<\/td><td>Productivity<\/td><td>Editor only<\/td><td>Recommended<\/td><\/tr><tr><td>HashiCorp extension<\/td><td>Terraform IDE<\/td><td>install extension<\/td><td>Completion\/diagnostics<\/td><td>VS Code focused<\/td><td>Recommended<\/td><\/tr><tr><td><code>terraform-ls<\/code><\/td><td>LSP intelligence<\/td><td>normally bundled<\/td><td>Schema\/navigation<\/td><td>Not security\/policy<\/td><td>Recommended<\/td><\/tr><tr><td>Terraform MCP<\/td><td>AI current context<\/td><td>configure MCP host<\/td><td>Reduces stale AI output<\/td><td>Trust\/access design<\/td><td>Recommended with AI<\/td><\/tr><tr><td><code>tenv<\/code><\/td><td>Binary versions<\/td><td><code>tenv tf use<\/code><\/td><td>Reproducibility<\/td><td>Extra dependency<\/td><td>Recommended<\/td><\/tr><tr><td><code>terraform fmt<\/code><\/td><td>Style<\/td><td><code>fmt -recursive<\/code><\/td><td>Deterministic format<\/td><td>Not validation<\/td><td>MUST<\/td><\/tr><tr><td><code>terraform validate<\/code><\/td><td>Config validity<\/td><td><code>validate<\/code><\/td><td>Cheap errors<\/td><td>Not provider runtime security<\/td><td>MUST<\/td><\/tr><tr><td>TFLint<\/td><td>Lint\/provider checks<\/td><td><code>tflint --recursive<\/code><\/td><td>Higher code quality<\/td><td>Not full security<\/td><td>Recommended<\/td><\/tr><tr><td>Trivy<\/td><td>Misconfig\/secrets<\/td><td><code>trivy config .<\/code><\/td><td>Broad security<\/td><td>Policy noise possible<\/td><td>Recommended<\/td><\/tr><tr><td>Checkov<\/td><td>Compliance\/IaC policy<\/td><td><code>checkov -d .<\/code><\/td><td>Broad policy library<\/td><td>Overlap<\/td><td>Optional<\/td><\/tr><tr><td><code>terraform test<\/code><\/td><td>Native behavior tests<\/td><td><code>terraform test<\/code><\/td><td>Fast\/native<\/td><td>Live behavior may need more<\/td><td>Recommended<\/td><\/tr><tr><td>Terratest<\/td><td>Real integration tests<\/td><td><code>go test<\/code><\/td><td>Strong E2E<\/td><td>Slow\/cost\/flakiness<\/td><td>Optional<\/td><\/tr><tr><td>terraform-docs<\/td><td>Interface docs<\/td><td><code>terraform-docs ...<\/code><\/td><td>No stale input\/output tables<\/td><td>Doesn&#8217;t write architecture narrative<\/td><td>Recommended<\/td><\/tr><tr><td>Infracost<\/td><td>Cost delta<\/td><td><code>infracost diff<\/code><\/td><td>FinOps in review<\/td><td>Estimate not invoice<\/td><td>Recommended<\/td><\/tr><tr><td>pre-commit-terraform<\/td><td>Local gates<\/td><td><code>pre-commit run<\/code><\/td><td>Fast feedback<\/td><td>Can be bypassed<\/td><td>Recommended<\/td><\/tr><tr><td>Git<\/td><td>Version\/review<\/td><td><code>git diff<\/code><\/td><td>Audit\/review<\/td><td>Needs policy<\/td><td>MUST<\/td><\/tr><tr><td>GitHub\/GitLab<\/td><td>Collaboration<\/td><td>PR\/MR<\/td><td>Review controls<\/td><td>Platform dependency<\/td><td>MUST for teams<\/td><\/tr><tr><td>GitHub Actions\/GitLab CI<\/td><td>Automation<\/td><td>pipeline<\/td><td>Reproducible checks<\/td><td>Runner\/security design needed<\/td><td>MUST for prod teams<\/td><\/tr><tr><td>Terragrunt<\/td><td>Multi-unit DRY\/orchestration<\/td><td><code>run --all plan<\/code><\/td><td>Scale multi-env layouts<\/td><td>Added abstraction<\/td><td>Optional<\/td><\/tr><tr><td>Atlantis<\/td><td>PR-driven Terraform<\/td><td>PR commands<\/td><td>Terraform-focused UX<\/td><td>Operate server\/security<\/td><td>Optional<\/td><\/tr><tr><td>HCP Terraform<\/td><td>Remote Terraform platform<\/td><td>workspace runs<\/td><td>State\/runs\/RBAC\/policy<\/td><td>Commercial\/platform choice<\/td><td>Recommended\/Enterprise<\/td><\/tr><tr><td>Terraform Enterprise<\/td><td>Self-hosted HCP<\/td><td>self-host<\/td><td>Compliance\/private hosting<\/td><td>Operational burden<\/td><td>Enterprise<\/td><\/tr><tr><td>Sentinel<\/td><td>HCP\/TFE policy<\/td><td>policy sets<\/td><td>Integrated governance<\/td><td>HashiCorp-specific<\/td><td>Enterprise<\/td><\/tr><tr><td>OPA<\/td><td>Vendor-neutral policy<\/td><td><code>opa exec<\/code><\/td><td>Reusable policy platform<\/td><td>Rego learning curve<\/td><td>Enterprise\/Optional<\/td><\/tr><tr><td>Conftest<\/td><td>Rego CLI<\/td><td><code>conftest test<\/code><\/td><td>Easy CI integration<\/td><td>Not Terraform orchestrator<\/td><td>Optional<\/td><\/tr><tr><td>Renovate<\/td><td>Dependency PRs<\/td><td>bot\/app<\/td><td>Sustainable upgrades<\/td><td>Needs grouping\/rules<\/td><td>Recommended<\/td><\/tr><tr><td><code>tfupdate<\/code><\/td><td>Constraint rewrites<\/td><td><code>tfupdate provider<\/code><\/td><td>Focused automation<\/td><td>Narrower than Renovate<\/td><td>Optional<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 21 \u2014 Recommended Stacks by Maturity<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">30. Beginner<\/h1>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Terraform CLI\nVS Code\nHashiCorp Terraform Extension\nterraform fmt\nterraform validate\nGit\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Add a remote backend once working with others.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">31. Professional developer<\/h1>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Terraform CLI\nVS Code + terraform-ls\ntenv\nTFLint\nTrivy\nterraform test\nterraform-docs\npre-commit-terraform\nGitHub\/GitLab CI\nremote state + locking\nOIDC\n<\/code><\/span><\/pre>\n\n\n<h1 class=\"wp-block-heading\">32. Senior \/ Platform engineer<\/h1>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-46\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">Everything above\nTerraform MCP Server\nInfracost\nreusable <span class=\"hljs-built_in\">module<\/span> standards\nRenovate\nstructured state boundaries\npolicy tests\ncontrolled runners\nHCP Terraform where appropriate\nTerragrunt only <span class=\"hljs-keyword\">if<\/span> complexity justifies it\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-46\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h1 class=\"wp-block-heading\">33. Enterprise platform<\/h1>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-47\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">approved IDE\/toolchain\n<span class=\"hljs-keyword\">private<\/span> module\/provider registry\ncentral CI templates\nOIDC\/workload identity\nremote execution\nHCP Terraform\/TFE\n<span class=\"hljs-keyword\">private<\/span> agents where needed\nRBAC\nrun tasks\npolicy-<span class=\"hljs-keyword\">as<\/span>-code\ncost governance\naudit logs\ndrift\/health assessments\ndependency automation\nAI\/MCP governance\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-47\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 22 \u2014 Gold Standard Terraform Pipeline<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">34. Pipeline<\/h1>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Developer\n   \u2193\nIDE\n   \u2193\nAI\/MCP assistance\n   \u2193\nTerraform code\n   \u2193\nFormat\n   \u2193\nValidate\n   \u2193\nLint\n   \u2193\nSecurity scan\n   \u2193\nNative tests\n   \u2193\nDocumentation\n   \u2193\nPre-commit\n   \u2193\nPush\n   \u2193\nCI reproduces gates\n   \u2193\nOIDC authentication\n   \u2193\nTerraform plan\n   \u2193\nCost analysis\n   \u2193\nPolicy validation\n   \u2193\nCode review\n   \u2193\nApproval\n   \u2193\nControlled apply\n   \u2193\nPost-deploy verification\n   \u2193\nMonitoring \/ drift \/ continuous validation\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Quality gates<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Gate<\/th><th class=\"has-text-align-left\" data-align=\"left\">Failure means<\/th><\/tr><\/thead><tbody><tr><td>Format<\/td><td>Code not canonical<\/td><\/tr><tr><td>Validate<\/td><td>Terraform config invalid<\/td><\/tr><tr><td>TFLint<\/td><td>Quality\/provider rule violation<\/td><\/tr><tr><td>Security<\/td><td>Risk exceeds accepted threshold<\/td><\/tr><tr><td>Test<\/td><td>Contract\/behavior regression<\/td><\/tr><tr><td>Docs<\/td><td>Module API reference stale<\/td><\/tr><tr><td>Plan<\/td><td>Change differs from developer intent<\/td><\/tr><tr><td>Cost<\/td><td>Material unexpected spend<\/td><\/tr><tr><td>Policy<\/td><td>Organization rule violated<\/td><\/tr><tr><td>Review<\/td><td>Human risk\/architecture concerns<\/td><\/tr><tr><td>Approval<\/td><td>Production change not authorized<\/td><\/tr><tr><td>Verify<\/td><td>Deployment outcome not healthy<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 23 \u2014 Local Development Setup<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">35. macOS<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Homebrew-centered setup:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">brew tap hashicorp\/tap\n\nbrew install hashicorp\/tap\/terraform\nbrew install hashicorp\/tap\/terraform-ls\nbrew install tofuutils\/tap\/tenv\nbrew install terraform-linters\/tap\/tflint\nbrew install trivy\nbrew install terraform-docs\nbrew install infracost\nbrew install pre-commit\nbrew install opa\nbrew install conftest\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">For Checkov:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">brew install pipx\npipx ensurepath\npipx install checkov\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">You may omit manual&nbsp;<code>terraform-ls<\/code>&nbsp;if VS Code&#8217;s HashiCorp extension is your only LSP client.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Verify:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">terraform version\ntenv --version\ntflint --version\ntrivy --version\nterraform-docs --version\ninfracost --version\npre-commit --version\nopa version\nconftest --version\ncheckov --version\n<\/code><\/span><\/pre>\n\n\n<h1 class=\"wp-block-heading\">36. Linux<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Prefer official\/vendor repositories or verified release artifacts rather than random curl-pipe-shell snippets.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example base packages:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-48\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">sudo apt-<span class=\"hljs-keyword\">get<\/span> update\nsudo apt-<span class=\"hljs-keyword\">get<\/span> install -y git curl unzip jq pipx\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-48\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Then install:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Terraform from HashiCorp&#8217;s official package repository,<\/li>\n\n\n\n<li>TFLint from verified GitHub release artifacts,<\/li>\n\n\n\n<li>Trivy from Aqua&#8217;s official package repository,<\/li>\n\n\n\n<li><code>terraform-docs<\/code>,\u00a0<code>tenv<\/code>, OPA, Conftest from official releases,<\/li>\n\n\n\n<li>Checkov with\u00a0<code>pipx install checkov<\/code>,<\/li>\n\n\n\n<li>pre-commit with OS package manager or pipx.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Keep installation logic in a reproducible developer bootstrap script or dev-container image.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">37. Windows<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Good choices:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>winget<\/code>\u00a0where the project publishes a verified package,<\/li>\n\n\n\n<li>Chocolatey in organizations already managing it,<\/li>\n\n\n\n<li>official signed release archives.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Known example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-49\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">winget<\/span> <span class=\"hljs-selector-tag\">install<\/span> <span class=\"hljs-selector-tag\">-e<\/span> <span class=\"hljs-selector-tag\">--id<\/span> <span class=\"hljs-selector-tag\">TerraformLinters<\/span><span class=\"hljs-selector-class\">.tflint<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-49\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Terraform itself can be installed via your organization&#8217;s package-management standard or HashiCorp&#8217;s official release archive.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Prefer WSL only if your team&#8217;s Terraform workflow intentionally standardizes on Linux semantics; do not require it just because Terraform can run there.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Setup checklist<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">&#91; ] Git\n&#91; ] Terraform version manager\n&#91; ] Pinned Terraform\n&#91; ] Editor + HashiCorp Terraform extension\n&#91; ] TFLint\n&#91; ] Trivy\n&#91; ] terraform-docs\n&#91; ] pre-commit\n&#91; ] Infracost if used\n&#91; ] Checkov if selected\n&#91; ] OPA\/Conftest if selected\n&#91; ] Cloud CLI\n&#91; ] SSO\/OIDC-compatible developer authentication\n&#91; ] No long-lived cloud key in shell profile\n&#91; ] Project hooks installed\n<\/code><\/span><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 24 \u2014 Sample Real AWS Project<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">38. Directory<\/h1>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">aws-web\/\n\u251c\u2500\u2500 backend.tf\n\u251c\u2500\u2500 versions.tf\n\u251c\u2500\u2500 provider.tf\n\u251c\u2500\u2500 variables.tf\n\u251c\u2500\u2500 main.tf\n\u251c\u2500\u2500 outputs.tf\n\u251c\u2500\u2500 terraform.tfvars.example\n\u251c\u2500\u2500 backend.hcl.example\n\u251c\u2500\u2500 tests\/\n\u2502   \u2514\u2500\u2500 basic.tftest.hcl\n\u251c\u2500\u2500 .tflint.hcl\n\u251c\u2500\u2500 .terraform-docs.yml\n\u251c\u2500\u2500 .pre-commit-config.yaml\n\u251c\u2500\u2500 .gitignore\n\u2514\u2500\u2500 README.md\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\"><code>versions.tf<\/code><\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-50\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">terraform {\n  required_version = <span class=\"hljs-string\">\"~&gt; 1.16.0\"<\/span>\n\n  required_providers {\n    aws = {\n      source  = <span class=\"hljs-string\">\"hashicorp\/aws\"<\/span>\n      version = <span class=\"hljs-string\">\"~&gt; 6.0\"<\/span>\n    }\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-50\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\"><code>backend.tf<\/code><\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-51\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">terraform {\n  backend <span class=\"hljs-string\">\"s3\"<\/span> {}\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-51\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\"><code>backend.hcl.example<\/code><\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-52\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">bucket       = <span class=\"hljs-string\">\"REPLACE-ME-terraform-state\"<\/span>\nkey          = <span class=\"hljs-string\">\"examples\/aws-web\/terraform.tfstate\"<\/span>\nregion       = <span class=\"hljs-string\">\"ap-northeast-1\"<\/span>\nencrypt      = <span class=\"hljs-literal\">true<\/span>\nuse_lockfile = <span class=\"hljs-literal\">true<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-52\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Initialize:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-53\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">cp backend.hcl.example backend.hcl\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># edit backend.hcl for your non-secret account settings<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\nterraform init -backend-config=backend.hcl\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-53\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Do&nbsp;<strong>not<\/strong>&nbsp;put access keys in backend config.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><code>provider.tf<\/code><\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-54\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">provider <span class=\"hljs-string\">\"aws\"<\/span> {\n  region = <span class=\"hljs-keyword\">var<\/span>.aws_region\n\n  default_tags {\n    tags = {\n      ManagedBy   = <span class=\"hljs-string\">\"Terraform\"<\/span>\n      Project     = <span class=\"hljs-keyword\">var<\/span>.project_name\n      Environment = <span class=\"hljs-keyword\">var<\/span>.environment\n    }\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-54\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Authentication comes from the AWS SDK credential chain: SSO\/profile locally, OIDC\/workload identity in CI.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><code>variables.tf<\/code><\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-55\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">variable <span class=\"hljs-string\">\"aws_region\"<\/span> {\n  description = <span class=\"hljs-string\">\"AWS region for this deployment.\"<\/span>\n  type        = string\n  <span class=\"hljs-keyword\">default<\/span>     = <span class=\"hljs-string\">\"ap-northeast-1\"<\/span>\n}\n\nvariable <span class=\"hljs-string\">\"project_name\"<\/span> {\n  description = <span class=\"hljs-string\">\"Short project identifier.\"<\/span>\n  type        = string\n  <span class=\"hljs-keyword\">default<\/span>     = <span class=\"hljs-string\">\"tf-web\"<\/span>\n}\n\nvariable <span class=\"hljs-string\">\"environment\"<\/span> {\n  description = <span class=\"hljs-string\">\"Deployment environment.\"<\/span>\n  type        = string\n\n  validation {\n    condition     = contains(&#91;<span class=\"hljs-string\">\"dev\"<\/span>, <span class=\"hljs-string\">\"staging\"<\/span>, <span class=\"hljs-string\">\"prod\"<\/span>], <span class=\"hljs-keyword\">var<\/span>.environment)\n    error_message = <span class=\"hljs-string\">\"environment must be dev, staging, or prod.\"<\/span>\n  }\n}\n\nvariable <span class=\"hljs-string\">\"vpc_cidr\"<\/span> {\n  description = <span class=\"hljs-string\">\"CIDR for the VPC.\"<\/span>\n  type        = string\n  <span class=\"hljs-keyword\">default<\/span>     = <span class=\"hljs-string\">\"10.42.0.0\/16\"<\/span>\n}\n\nvariable <span class=\"hljs-string\">\"azs\"<\/span> {\n  description = <span class=\"hljs-string\">\"Availability Zones for public subnets.\"<\/span>\n  type        = <span class=\"hljs-keyword\">list<\/span>(string)\n  <span class=\"hljs-keyword\">default<\/span>     = &#91;<span class=\"hljs-string\">\"ap-northeast-1a\"<\/span>, <span class=\"hljs-string\">\"ap-northeast-1c\"<\/span>]\n\n  validation {\n    condition     = length(<span class=\"hljs-keyword\">var<\/span>.azs) == <span class=\"hljs-number\">2<\/span>\n    error_message = <span class=\"hljs-string\">\"This example expects exactly two AZs.\"<\/span>\n  }\n}\n\nvariable <span class=\"hljs-string\">\"public_subnet_cidrs\"<\/span> {\n  description = <span class=\"hljs-string\">\"CIDRs for public subnets, one per AZ.\"<\/span>\n  type        = <span class=\"hljs-keyword\">list<\/span>(string)\n  <span class=\"hljs-keyword\">default<\/span>     = &#91;<span class=\"hljs-string\">\"10.42.1.0\/24\"<\/span>, <span class=\"hljs-string\">\"10.42.2.0\/24\"<\/span>]\n\n  validation {\n    condition     = length(<span class=\"hljs-keyword\">var<\/span>.public_subnet_cidrs) == <span class=\"hljs-number\">2<\/span>\n    error_message = <span class=\"hljs-string\">\"This example expects exactly two public subnet CIDRs.\"<\/span>\n  }\n}\n\nvariable <span class=\"hljs-string\">\"ami_id\"<\/span> {\n  description = <span class=\"hljs-string\">\"Approved AMI ID for the EC2 instance.\"<\/span>\n  type        = string\n}\n\nvariable <span class=\"hljs-string\">\"instance_type\"<\/span> {\n  description = <span class=\"hljs-string\">\"EC2 instance type.\"<\/span>\n  type        = string\n  <span class=\"hljs-keyword\">default<\/span>     = <span class=\"hljs-string\">\"t3.micro\"<\/span>\n}\n\nvariable <span class=\"hljs-string\">\"allowed_http_cidrs\"<\/span> {\n  description = <span class=\"hljs-string\">\"CIDRs permitted to access TCP\/80. Do not use 0.0.0.0\/0 unless public exposure is intended and approved.\"<\/span>\n  type        = <span class=\"hljs-keyword\">list<\/span>(string)\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-55\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\"><code>main.tf<\/code><\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-56\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">locals {\n  subnet_map = {\n    <span class=\"hljs-keyword\">for<\/span> idx, az in <span class=\"hljs-keyword\">var<\/span>.azs :\n    az =&gt; <span class=\"hljs-keyword\">var<\/span>.public_subnet_cidrs&#91;idx]\n  }\n}\n\nresource <span class=\"hljs-string\">\"aws_vpc\"<\/span> <span class=\"hljs-string\">\"this\"<\/span> {\n  cidr_block           = <span class=\"hljs-keyword\">var<\/span>.vpc_cidr\n  enable_dns_support   = <span class=\"hljs-keyword\">true<\/span>\n  enable_dns_hostnames = <span class=\"hljs-keyword\">true<\/span>\n\n  tags = {\n    Name = <span class=\"hljs-string\">\"${var.project_name}-${var.environment}\"<\/span>\n  }\n}\n\nresource <span class=\"hljs-string\">\"aws_internet_gateway\"<\/span> <span class=\"hljs-string\">\"this\"<\/span> {\n  vpc_id = aws_vpc.this.id\n\n  tags = {\n    Name = <span class=\"hljs-string\">\"${var.project_name}-${var.environment}\"<\/span>\n  }\n}\n\nresource <span class=\"hljs-string\">\"aws_subnet\"<\/span> <span class=\"hljs-string\">\"public\"<\/span> {\n  for_each = local.subnet_map\n\n  vpc_id                  = aws_vpc.this.id\n  availability_zone       = each.key\n  cidr_block              = each.value\n  map_public_ip_on_launch = <span class=\"hljs-keyword\">true<\/span>\n\n  tags = {\n    Name = <span class=\"hljs-string\">\"${var.project_name}-${var.environment}-${each.key}\"<\/span>\n  }\n}\n\nresource <span class=\"hljs-string\">\"aws_route_table\"<\/span> <span class=\"hljs-string\">\"public\"<\/span> {\n  vpc_id = aws_vpc.this.id\n\n  route {\n    cidr_block = <span class=\"hljs-string\">\"0.0.0.0\/0\"<\/span>\n    gateway_id = aws_internet_gateway.this.id\n  }\n\n  tags = {\n    Name = <span class=\"hljs-string\">\"${var.project_name}-${var.environment}-public\"<\/span>\n  }\n}\n\nresource <span class=\"hljs-string\">\"aws_route_table_association\"<\/span> <span class=\"hljs-string\">\"public\"<\/span> {\n  for_each = aws_subnet.<span class=\"hljs-keyword\">public<\/span>\n\n  subnet_id      = each.value.id\n  route_table_id = aws_route_table.<span class=\"hljs-keyword\">public<\/span>.id\n}\n\nresource <span class=\"hljs-string\">\"aws_security_group\"<\/span> <span class=\"hljs-string\">\"web\"<\/span> {\n  name_prefix = <span class=\"hljs-string\">\"${var.project_name}-${var.environment}-web-\"<\/span>\n  description = <span class=\"hljs-string\">\"HTTP access for example web instance\"<\/span>\n  vpc_id      = aws_vpc.this.id\n\n  ingress {\n    description = <span class=\"hljs-string\">\"Approved HTTP clients\"<\/span>\n    from_port   = <span class=\"hljs-number\">80<\/span>\n    to_port     = <span class=\"hljs-number\">80<\/span>\n    protocol    = <span class=\"hljs-string\">\"tcp\"<\/span>\n    cidr_blocks = <span class=\"hljs-keyword\">var<\/span>.allowed_http_cidrs\n  }\n\n  egress {\n    description = <span class=\"hljs-string\">\"Outbound access\"<\/span>\n    from_port   = <span class=\"hljs-number\">0<\/span>\n    to_port     = <span class=\"hljs-number\">0<\/span>\n    protocol    = <span class=\"hljs-string\">\"-1\"<\/span>\n    cidr_blocks = &#91;<span class=\"hljs-string\">\"0.0.0.0\/0\"<\/span>]\n  }\n}\n\nresource <span class=\"hljs-string\">\"aws_instance\"<\/span> <span class=\"hljs-string\">\"web\"<\/span> {\n  ami                         = <span class=\"hljs-keyword\">var<\/span>.ami_id\n  instance_type               = <span class=\"hljs-keyword\">var<\/span>.instance_type\n  subnet_id                   = values(aws_subnet.<span class=\"hljs-keyword\">public<\/span>)&#91;<span class=\"hljs-number\">0<\/span>].id\n  vpc_security_group_ids      = &#91;aws_security_group.web.id]\n  associate_public_ip_address = <span class=\"hljs-keyword\">true<\/span>\n\n  metadata_options {\n    http_endpoint = <span class=\"hljs-string\">\"enabled\"<\/span>\n    http_tokens   = <span class=\"hljs-string\">\"required\"<\/span>\n  }\n\n  root_block_device {\n    encrypted = <span class=\"hljs-keyword\">true<\/span>\n  }\n\n  tags = {\n    Name = <span class=\"hljs-string\">\"${var.project_name}-${var.environment}-web\"<\/span>\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-56\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This deliberately exposes no SSH port. In a production design, use SSM\/session tooling or another approved administration path rather than opening port 22 globally.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><code>outputs.tf<\/code><\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-57\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">output <span class=\"hljs-string\">\"vpc_id\"<\/span> {\n  description = <span class=\"hljs-string\">\"VPC ID.\"<\/span>\n  value       = aws_vpc.this.id\n}\n\noutput <span class=\"hljs-string\">\"public_subnet_ids\"<\/span> {\n  description = <span class=\"hljs-string\">\"Public subnet IDs keyed by availability zone.\"<\/span>\n  value       = { <span class=\"hljs-keyword\">for<\/span> az, subnet <span class=\"hljs-keyword\">in<\/span> aws_subnet.public : <span class=\"hljs-function\"><span class=\"hljs-params\">az<\/span> =&gt;<\/span> subnet.id }\n}\n\noutput <span class=\"hljs-string\">\"instance_id\"<\/span> {\n  description = <span class=\"hljs-string\">\"EC2 instance ID.\"<\/span>\n  value       = aws_instance.web.id\n}\n\noutput <span class=\"hljs-string\">\"public_ip\"<\/span> {\n  description = <span class=\"hljs-string\">\"Public IPv4 address of the example instance.\"<\/span>\n  value       = aws_instance.web.public_ip\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-57\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\"><code>terraform.tfvars.example<\/code><\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-58\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">environment        = <span class=\"hljs-string\">\"dev\"<\/span>\nami_id             = <span class=\"hljs-string\">\"ami-REPLACE-WITH-AN-APPROVED-AMI\"<\/span>\nallowed_http_cidrs = &#91;<span class=\"hljs-string\">\"203.0.113.0\/24\"<\/span>]\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-58\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\"><code>tests\/basic.tftest.hcl<\/code><\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-59\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">mock_provider <span class=\"hljs-string\">\"aws\"<\/span> {}\n\nrun <span class=\"hljs-string\">\"basic_plan\"<\/span> {\n  command = plan\n\n  variables {\n    environment        = <span class=\"hljs-string\">\"dev\"<\/span>\n    ami_id             = <span class=\"hljs-string\">\"ami-0123456789abcdef0\"<\/span>\n    allowed_http_cidrs = &#91;<span class=\"hljs-string\">\"203.0.113.0\/24\"<\/span>]\n  }\n\n  assert {\n    condition     = aws_vpc.this.cidr_block == <span class=\"hljs-string\">\"10.42.0.0\/16\"<\/span>\n    error_message = <span class=\"hljs-string\">\"Unexpected VPC CIDR.\"<\/span>\n  }\n\n  assert {\n    condition     = length(aws_subnet.<span class=\"hljs-keyword\">public<\/span>) == <span class=\"hljs-number\">2<\/span>\n    error_message = <span class=\"hljs-string\">\"Expected two public subnets.\"<\/span>\n  }\n\n  assert {\n    condition     = aws_instance.web.metadata_options&#91;<span class=\"hljs-number\">0<\/span>].http_tokens == <span class=\"hljs-string\">\"required\"<\/span>\n    error_message = <span class=\"hljs-string\">\"IMDSv2 must be required.\"<\/span>\n  }\n\n  assert {\n    condition     = aws_instance.web.root_block_device&#91;<span class=\"hljs-number\">0<\/span>].encrypted == <span class=\"hljs-keyword\">true<\/span>\n    error_message = <span class=\"hljs-string\">\"Root volume must be encrypted.\"<\/span>\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-59\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\"><code>.gitignore<\/code><\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">.terraform\/\n*.tfstate\n*.tfstate.*\n*.tfplan\ntfplan*\ncrash.log\ncrash.*.log\noverride.tf\noverride.tf.json\n*_override.tf\n*_override.tf.json\nbackend.hcl\n*.auto.tfvars\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>Do commit:<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-60\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.terraform<\/span><span class=\"hljs-selector-class\">.lock<\/span><span class=\"hljs-selector-class\">.hcl<\/span>\n<span class=\"hljs-selector-tag\">Terraform<\/span> <span class=\"hljs-selector-tag\">source<\/span>\n<span class=\"hljs-selector-tag\">non-secret<\/span> <span class=\"hljs-selector-tag\">examples<\/span>\n<span class=\"hljs-selector-tag\">tests<\/span>\n<span class=\"hljs-selector-tag\">tool<\/span> <span class=\"hljs-selector-tag\">configuration<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-60\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Run locally<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-61\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">terraform init -backend=<span class=\"hljs-literal\">false<\/span>\nterraform fmt -recursive\nterraform validate\ntflint --init\ntflint --recursive\ntrivy config --severity HIGH,CRITICAL .\nterraform test\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-61\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">For a real plan, authenticate through AWS SSO\/profile and initialize the real backend:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-62\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">terraform init -backend-config=backend.hcl\nterraform plan -<span class=\"hljs-keyword\">var<\/span>-file=terraform.tfvars\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-62\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 25 \u2014 Common Developer Mistakes<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">39. Mistakes and correct alternatives<\/h1>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Mistake<\/th><th class=\"has-text-align-left\" data-align=\"left\">Why dangerous<\/th><th class=\"has-text-align-left\" data-align=\"left\">Correct alternative<\/th><\/tr><\/thead><tbody><tr><td>Hardcoded cloud keys<\/td><td>Credential leak\/rotation burden<\/td><td>OIDC, SSO, workload identity<\/td><\/tr><tr><td>Hardcoded secrets<\/td><td>HCL\/plan\/state leak<\/td><td>Secret manager + ephemeral\/write-only features where provider supports<\/td><\/tr><tr><td>Commit&nbsp;<code>.tfstate<\/code><\/td><td>Secrets + no safe state locking<\/td><td>Remote state<\/td><\/tr><tr><td>Commit&nbsp;<code>.terraform\/<\/code><\/td><td>Large, platform-specific, unreviewable<\/td><td>Re-run init<\/td><\/tr><tr><td>Ignore&nbsp;<code>.terraform.lock.hcl<\/code><\/td><td>Provider drift<\/td><td>Commit root lock file<\/td><\/tr><tr><td>No provider constraints<\/td><td>Unexpected upgrades<\/td><td>Declare constraints<\/td><\/tr><tr><td>No module versions<\/td><td>Module drift<\/td><td>Pin Registry module versions<\/td><\/tr><tr><td>Apply from laptops<\/td><td>Weak audit\/reproducibility<\/td><td>Controlled runner<\/td><\/tr><tr><td>No review<\/td><td>High blast radius<\/td><td>Protected PR flow<\/td><\/tr><tr><td>No lint\/security tests<\/td><td>Preventable defects<\/td><td>Automated gates<\/td><\/tr><tr><td>One giant state<\/td><td>Huge blast radius\/lock contention<\/td><td>Decompose by ownership\/failure boundary<\/td><\/tr><tr><td>Overuse CLI workspaces<\/td><td>Hidden environment coupling<\/td><td>Separate roots\/states\/workspaces where environments differ<\/td><\/tr><tr><td>Huge root module<\/td><td>Slow\/unowned\/unreviewable<\/td><td>Compose smaller deployment units<\/td><\/tr><tr><td>Copy\/paste infrastructure<\/td><td>Drift<\/td><td>Reusable modules\/templates where repetition is stable<\/td><\/tr><tr><td>Weak variable design<\/td><td>Invalid combinations<\/td><td>Types, validation, object schemas<\/td><\/tr><tr><td>Excessive&nbsp;<code>-target<\/code><\/td><td>Partial graph surprises<\/td><td>Normal full plan\/apply; use target for exceptional recovery<\/td><\/tr><tr><td>Direct state edits<\/td><td>Corruption<\/td><td>Terraform state\/moved\/import mechanisms<\/td><\/tr><tr><td>Casual&nbsp;<code>state rm<\/code><\/td><td>Terraform loses management<\/td><td>Controlled state procedure<\/td><\/tr><tr><td>Blind AI-generated code<\/td><td>Hallucination\/security bugs<\/td><td>MCP\/docs + automated gates + review<\/td><\/tr><tr><td>Ignore cost<\/td><td>Surprise spend<\/td><td>Infracost\/FinOps review<\/td><\/tr><tr><td>Auto-apply PRs to prod<\/td><td>Unreviewed change<\/td><td>Merge + protected approval<\/td><\/tr><tr><td>Long-lived CI credentials<\/td><td>High-value secret<\/td><td>OIDC federation<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 26 \u2014 Security Best Practices<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">40. Security checklist<\/h1>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-63\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">Identity\n&#91; ] No hardcoded cloud credentials\n&#91; ] CI uses OIDC\/workload identity\n&#91; ] Developer auth uses SSO\/short-lived credentials\n&#91; ] Plan <span class=\"hljs-keyword\">and<\/span> apply roles follow least privilege\n&#91; ] Production permissions separated from non-production\n\nState\n&#91; ] State is remote\n&#91; ] State is encrypted\n&#91; ] State access is restricted\n&#91; ] State locking enabled\n&#91; ] State versioning\/history enabled where supported\n&#91; ] State access\/audit is monitored\n&#91; ] State never committed to Git\n\nCode\/dependencies\n&#91; ] Terraform version constrained\n&#91; ] Providers constrained\n&#91; ] .terraform.lock.hcl committed <span class=\"hljs-keyword\">for<\/span> root configurations\n&#91; ] Modules explicitly versioned\n&#91; ] Dependency updates automated but reviewed\n&#91; ] No secrets in tfvars\/source\n\nReview\n&#91; ] Branch protection enabled\n&#91; ] Required reviewers defined\n&#91; ] Security scan required\n&#91; ] Tests required\n&#91; ] Plan required\n&#91; ] Destruction\/replacement explicitly reviewed\n&#91; ] Production approval <span class=\"hljs-keyword\">protected<\/span>\n\nRuntime\n&#91; ] Policy-<span class=\"hljs-keyword\">as<\/span>-code <span class=\"hljs-keyword\">for<\/span> critical invariants where justified\n&#91; ] Audit logs retained\n&#91; ] Drift\/health monitoring enabled\n&#91; ] <span class=\"hljs-keyword\">Break<\/span>-glass process documented\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-63\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">State is a security asset<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Terraform state may contain:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>database passwords,<\/li>\n\n\n\n<li>generated tokens,<\/li>\n\n\n\n<li>private endpoint metadata,<\/li>\n\n\n\n<li>identifiers,<\/li>\n\n\n\n<li>provider-returned secrets.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Marking an output&nbsp;<code>sensitive = true<\/code>&nbsp;primarily controls display; it does not magically remove the value from all state.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Prefer Terraform\/provider features that avoid persistence when possible, including ephemeral values and write-only arguments where supported.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 27 \u2014 Terraform Code Review Checklist<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">41. PR checklist<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Intent<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">&#91; ] Ticket\/goal is clear\n&#91; ] Change is minimal for the intended outcome\n&#91; ] Correct environment\/account\/region is targeted\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Code quality<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">&#91; ] terraform fmt passes\n&#91; ] terraform validate passes\n&#91; ] TFLint passes\n&#91; ] Naming is consistent\n&#91; ] Variables are typed and validated\n&#91; ] Inputs\/outputs are documented\n&#91; ] No unnecessary abstraction\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Dependencies<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-64\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-attr\">&#91; ]<\/span> <span class=\"hljs-selector-tag\">Terraform<\/span> <span class=\"hljs-selector-tag\">constraint<\/span> <span class=\"hljs-selector-tag\">appropriate<\/span>\n<span class=\"hljs-selector-attr\">&#91; ]<\/span> <span class=\"hljs-selector-tag\">Provider<\/span> <span class=\"hljs-selector-tag\">constraints<\/span> <span class=\"hljs-selector-tag\">appropriate<\/span>\n<span class=\"hljs-selector-attr\">&#91; ]<\/span> <span class=\"hljs-selector-tag\">Lock-file<\/span> <span class=\"hljs-selector-tag\">changes<\/span> <span class=\"hljs-selector-tag\">expected<\/span>\n<span class=\"hljs-selector-attr\">&#91; ]<\/span> <span class=\"hljs-selector-tag\">Reusable<\/span> <span class=\"hljs-selector-tag\">module<\/span> <span class=\"hljs-selector-tag\">versions<\/span> <span class=\"hljs-selector-tag\">pinned<\/span>\n<span class=\"hljs-selector-attr\">&#91; ]<\/span> <span class=\"hljs-selector-tag\">Major<\/span> <span class=\"hljs-selector-tag\">dependency<\/span> <span class=\"hljs-selector-tag\">upgrades<\/span> <span class=\"hljs-selector-tag\">reviewed<\/span> <span class=\"hljs-selector-tag\">separately<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-64\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Security<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">&#91; ] No credentials\/secrets\n&#91; ] IAM is least privilege\n&#91; ] No accidental 0.0.0.0\/0 or ::\/0\n&#91; ] Encryption settings correct\n&#91; ] Security scanner result reviewed\n&#91; ] State\/backend permissions unaffected or intentionally changed\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Plan\/blast radius<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-65\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">&#91; ] Adds\/changes\/destroys match intent\n&#91; ] Every replacement (<span class=\"hljs-string\">`-\/+`<\/span>) understood\n&#91; ] Database\/storage replacement scrutinized\n&#91; ] IAM\/KMS\/networking changes scrutinized\n&#91; ] for_each\/count key changes understood\n&#91; ] Moved\/imported resource addresses correct\n&#91; ] No unexpected state churn\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-65\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Reliability\/cost<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">&#91; ] HA assumptions still valid\n&#91; ] Capacity\/quotas considered\n&#91; ] Cost delta acceptable\n&#91; ] Tests cover important behavior\n&#91; ] Rollback\/recovery approach understood\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Documentation\/operations<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">&#91; ] terraform-docs output current\n&#91; ] README\/runbook updated if behavior changed\n&#91; ] Monitoring\/alerts adjusted if needed\n&#91; ] Post-deploy verification defined\n<\/code><\/span><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 28 \u2014 Terraform Productivity Best Practices<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">42. Reduce developer time without lowering safety<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">1. Standard repository template<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Pre-create:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">versions.tf\nvariables.tf\noutputs.tf\ntests\/\n.tflint.hcl\n.terraform-docs.yml\n.pre-commit-config.yaml\nCI workflow\nRenovate config\nREADME markers\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">2. Reusable modules, but not &#8220;module everything&#8221;<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Create a module when you have:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>repeated stable pattern,<\/li>\n\n\n\n<li>meaningful abstraction,<\/li>\n\n\n\n<li>security defaults worth centralizing,<\/li>\n\n\n\n<li>clear consumer interface.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Do not wrap one resource in a module merely to claim standardization.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3. Fast local path<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Target:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">fmt + validate + lint + focused security + mock tests\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">in a developer-friendly feedback loop.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Put slow live integration tests in CI\/nightly pipelines.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">4. Pre-approved module catalog<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Make secure defaults easy:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-66\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">Need private S3 bucket\n  \u2193\napproved <span class=\"hljs-built_in\">module<\/span>\n  \u2193\n<span class=\"hljs-number\">3<\/span>\u2013<span class=\"hljs-number\">5<\/span> meaningful inputs\n  \u2193\nencryption\/logging\/policy defaults already included\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-66\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">5. Central CI templates<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Do not copy 100 slightly different Terraform workflows. Provide versioned reusable CI workflows\/components.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">6. Automated documentation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Generated docs prevent &#8220;variable exists in code but not README.&#8221;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">7. Automated dependency updates<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Small continuous upgrades are usually safer than a two-year provider jump.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">8. Cost feedback in PRs<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Shift cost discussion before apply.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">9. Remote execution<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A consistent execution environment eliminates &#8220;worked on my laptop&#8221; differences and improves auditability.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">10. Use AI for mechanical work<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">AI is excellent at:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>repetitive HCL,<\/li>\n\n\n\n<li>tests,<\/li>\n\n\n\n<li>docs,<\/li>\n\n\n\n<li>refactors.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Humans remain accountable for:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>intent,<\/li>\n\n\n\n<li>architecture,<\/li>\n\n\n\n<li>security,<\/li>\n\n\n\n<li>blast radius,<\/li>\n\n\n\n<li>production approval.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 29 \u2014 Tool Overlap and Decision Guide<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">43. Overlap matrix<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\"><code>terraform validate<\/code>&nbsp;vs TFLint<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Use&nbsp;<strong>both<\/strong>. They answer different questions.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Trivy vs Checkov<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Start with&nbsp;<strong>one<\/strong>. Add the second only for distinct policies\/compliance.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Sentinel vs OPA<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>HCP-first, integrated governance: Sentinel is natural.<\/li>\n\n\n\n<li>Cross-platform policy strategy: OPA is natural.<\/li>\n\n\n\n<li>HCP Terraform can support OPA too.<\/li>\n\n\n\n<li>Consider Terraform Policy as it matures if HCL-native policy is attractive.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">tfenv vs tenv<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For new Terraform-centric standardization, choose&nbsp;<strong>tenv<\/strong>&nbsp;unless existing tfenv investment outweighs migration benefit.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Terraform vs Terragrunt<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Start with&nbsp;<strong>Terraform<\/strong>. Add Terragrunt to solve actual repetition\/orchestration pain.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">GitHub Actions vs Atlantis<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>CI ecosystem + custom workflows: GitHub Actions.<\/li>\n\n\n\n<li>Terraform-specific PR command workflow: Atlantis.<\/li>\n\n\n\n<li>You can combine them if responsibilities are unambiguous: CI validates, Atlantis owns plan\/apply.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Atlantis vs HCP Terraform<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">HCP Terraform is a broader platform: state, runs, RBAC, registry, agents, policy, health. Atlantis is a focused PR automation server.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><code>terraform test<\/code>&nbsp;vs Terratest<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Start native. Add Terratest for real behavioral integration.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">terraform-docs vs manual README<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Use both:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-67\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">Manual sections \u2192 why\/how\/architecture\nGenerated section \u2192 exact <span class=\"hljs-class\"><span class=\"hljs-keyword\">interface<\/span> <span class=\"hljs-title\">reference<\/span>\n<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-67\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">pre-commit vs CI<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Use both:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">pre-commit = developer speed\nCI         = enforcement\n<\/code><\/span><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">PART 30 \u2014 Final Gold Standard Recommendation<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">44. Final stack<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">MUST HAVE<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Terraform CLI\nGit\nterraform fmt\nterraform validate\nversion constraints\n.terraform.lock.hcl for root configs\nremote state + locking for teams\ncode review\nCI for production\nOIDC\/workload identity\nplan review\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">STRONGLY RECOMMENDED<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">VS Code + HashiCorp Terraform Extension\nterraform-ls\ntenv\nTFLint\nTrivy\nterraform test\nterraform-docs\npre-commit-terraform\nInfracost\nRenovate\nstandard repository templates\nreusable CI templates\npost-deployment verification\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">OPTIONAL<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Terraform MCP Server (recommended if AI is used)\nCheckov\nTerratest\nTerragrunt\nAtlantis\nConftest\ntfupdate\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">ENTERPRISE \/ PLATFORM<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-68\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">HCP Terraform\nTerraform Enterprise\n<span class=\"hljs-keyword\">private<\/span> module\/provider registry\n<span class=\"hljs-keyword\">private<\/span> agents\nRBAC\nrun tasks\npolicy sets\nSentinel \/ OPA \/ Terraform Policy\naudit logging\ndrift detection \/ continuous validation\ncentral cost\/security governance\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-68\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Terraform Developer GOLD Standard Checklist Before Merge<\/h1>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-69\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">SCOPE\n&#91; ] I can explain exactly what <span class=\"hljs-keyword\">this<\/span> PR changes and why.\n&#91; ] I changed only the intended state boundary\/environment.\n\nCODE\n&#91; ] terraform fmt -check passes.\n&#91; ] terraform validate passes.\n&#91; ] TFLint passes.\n&#91; ] No duplicate\/copy-paste architecture was introduced unnecessarily.\n&#91; ] Variables are typed, described, and validated where useful.\n&#91; ] Outputs are intentional and documented.\n\nVERSIONS\n&#91; ] Terraform version policy is satisfied.\n&#91; ] Provider constraints are explicit.\n&#91; ] .terraform.lock.hcl changes are expected and reviewed.\n&#91; ] Registry <span class=\"hljs-built_in\">module<\/span> versions are explicit.\n\nSECURITY\n&#91; ] No cloud credentials, passwords, API keys, tokens, or private keys are committed.\n&#91; ] CI uses OIDC\/workload identity.\n&#91; ] Trivy\/selected IaC security scan passes or exceptions are approved.\n&#91; ] IAM follows least privilege.\n&#91; ] Public ingress\/egress is intentional.\n&#91; ] Encryption is enabled where required.\n&#91; ] State remains protected.\n\nTESTING\n&#91; ] terraform test passes.\n&#91; ] Important <span class=\"hljs-built_in\">module<\/span> behavior is asserted.\n&#91; ] Live integration testing is added when native tests cannot prove the requirement.\n\nPLAN\n&#91; ] A plan exists <span class=\"hljs-keyword\">for<\/span> the correct target.\n&#91; ] I reviewed additions, changes, deletions, and replacements.\n&#91; ] I understand every destructive\/replacement action.\n&#91; ] Resource-address changes are intentional.\n&#91; ] No unexpected IAM\/KMS\/network\/database changes exist.\n&#91; ] Blast radius is acceptable.\n\nCOST\n&#91; ] Cost delta has been reviewed when material.\n&#91; ] New always-on\/large resources are intentional.\n\nDOCS\n&#91; ] terraform-docs output is current.\n&#91; ] Architecture\/runbooks are updated <span class=\"hljs-keyword\">if<\/span> behavior changed.\n\nDEPLOYMENT\n&#91; ] Apply will run <span class=\"hljs-keyword\">from<\/span> the approved controlled environment.\n&#91; ] Production approval requirements are met.\n&#91; ] Rollback\/recovery is understood.\n&#91; ] Post-deployment verification is defined.\n\nAFTER\n&#91; ] Monitoring\/alerts will detect regressions.\n&#91; ] Drift\/continuous validation is enabled where appropriate.\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-69\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Opinionated reference architecture<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">For most modern platform teams, the cleanest production model is:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-70\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">Developer laptop\n  \u251c\u2500 SSO \/ short-lived local cloud auth\n  \u251c\u2500 VS Code + HashiCorp extension\n  \u251c\u2500 tenv\n  \u251c\u2500 fmt \/ validate \/ TFLint \/ Trivy \/ terraform test\n  \u2514\u2500 pre-commit\n            \u2193\n<span class=\"hljs-keyword\">Protected<\/span> Git repository\n            \u2193\nCI\n  \u251c\u2500 reproduce local checks\n  \u251c\u2500 dependency\/supply-chain controls\n  \u251c\u2500 OIDC\n  \u251c\u2500 speculative\/real plan\n  \u2514\u2500 cost\/security feedback\n            \u2193\nHCP Terraform \/ TFE <span class=\"hljs-keyword\">or<\/span> dedicated controlled execution\n  \u251c\u2500 remote state\n  \u251c\u2500 locking\n  \u251c\u2500 RBAC\n  \u251c\u2500 <span class=\"hljs-keyword\">private<\/span> agents only when network access requires them\n  \u251c\u2500 run tasks\n  \u251c\u2500 policy\n  \u2514\u2500 approvals\n            \u2193\nCloud\n            \u2193\nVerification + observability + drift\/health\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-70\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This is intentionally boring. Boring infrastructure delivery is good: deterministic, reviewable, repeatable, auditable, and difficult to bypass accidentally.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Source and currency notes<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">This handbook was checked against current vendor\/project documentation available on&nbsp;<strong>5 September 2026<\/strong>, including:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>HashiCorp Terraform CLI, testing, state, backend, policy, MCP Server, HCP Terraform, and Terraform Enterprise documentation.<\/li>\n\n\n\n<li>HashiCorp Terraform VS Code extension and\u00a0<code>terraform-ls<\/code>.<\/li>\n\n\n\n<li>tofuutils\u00a0<code>tenv<\/code>.<\/li>\n\n\n\n<li>terraform-linters TFLint and AWS ruleset.<\/li>\n\n\n\n<li>Aqua Security Trivy.<\/li>\n\n\n\n<li>Checkov.<\/li>\n\n\n\n<li>terraform-docs.<\/li>\n\n\n\n<li>Infracost.<\/li>\n\n\n\n<li>antonbabenko\/pre-commit-terraform.<\/li>\n\n\n\n<li>Gruntwork Terragrunt and Terratest.<\/li>\n\n\n\n<li>Atlantis.<\/li>\n\n\n\n<li>Open Policy Agent and Conftest.<\/li>\n\n\n\n<li>Renovate.<\/li>\n\n\n\n<li><code>tfupdate<\/code>.<\/li>\n\n\n\n<li>GitHub Actions \/ AWS OIDC and GitLab OIDC documentation.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Selected official\/reference URLs:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-71\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">https:<span class=\"hljs-comment\">\/\/developer.hashicorp.com\/terraform\/<\/span>\nhttps:<span class=\"hljs-comment\">\/\/developer.hashicorp.com\/terraform\/mcp-server<\/span>\nhttps:<span class=\"hljs-comment\">\/\/developer.hashicorp.com\/terraform\/cloud-docs<\/span>\nhttps:<span class=\"hljs-comment\">\/\/developer.hashicorp.com\/terraform\/enterprise<\/span>\nhttps:<span class=\"hljs-comment\">\/\/marketplace.visualstudio.com\/items?itemName=HashiCorp.terraform<\/span>\nhttps:<span class=\"hljs-comment\">\/\/github.com\/hashicorp\/terraform-ls<\/span>\nhttps:<span class=\"hljs-comment\">\/\/github.com\/tofuutils\/tenv<\/span>\nhttps:<span class=\"hljs-comment\">\/\/github.com\/terraform-linters\/tflint<\/span>\nhttps:<span class=\"hljs-comment\">\/\/trivy.dev\/<\/span>\nhttps:<span class=\"hljs-comment\">\/\/www.checkov.io\/<\/span>\nhttps:<span class=\"hljs-comment\">\/\/terraform-docs.io\/<\/span>\nhttps:<span class=\"hljs-comment\">\/\/www.infracost.io\/docs\/<\/span>\nhttps:<span class=\"hljs-comment\">\/\/github.com\/antonbabenko\/pre-commit-terraform<\/span>\nhttps:<span class=\"hljs-comment\">\/\/terragrunt.gruntwork.io\/<\/span>\nhttps:<span class=\"hljs-comment\">\/\/terratest.gruntwork.io\/<\/span>\nhttps:<span class=\"hljs-comment\">\/\/www.runatlantis.io\/<\/span>\nhttps:<span class=\"hljs-comment\">\/\/www.openpolicyagent.org\/<\/span>\nhttps:<span class=\"hljs-comment\">\/\/www.conftest.dev\/<\/span>\nhttps:<span class=\"hljs-comment\">\/\/docs.renovatebot.com\/<\/span>\nhttps:<span class=\"hljs-comment\">\/\/github.com\/minamijoyo\/tfupdate<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-71\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Final operating principle<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Terraform quality is not produced by Terraform alone.<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A production Terraform system combines:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-72\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">Correct code\n+ reproducible versions\n+ secure identities\n+ <span class=\"hljs-keyword\">protected<\/span> state\n+ automated lint\/security\/test gates\n+ visible cost\n+ reviewable plan\n+ policy where it adds real value\n+ controlled execution\n+ post-deployment verification\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-72\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">That combination turns Terraform from a command-line tool into an engineering platform.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Tools, Workflow, Security, Testing, CI\/CD, Automation, AI Assistance, Governance, and Production Best Practices Verified baseline:&nbsp;5 September 2026Audience:&nbsp;Terraform developers, DevOps\/SRE, platform\/cloud engineers, module authors, DevSecOps teams, and engineering&#8230; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_joinchat":[],"footnotes":""},"categories":[11138],"tags":[],"class_list":["post-78465","post","type-post","status-publish","format-standard","hentry","category-best-tools"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/78465","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/comments?post=78465"}],"version-history":[{"count":1,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/78465\/revisions"}],"predecessor-version":[{"id":78466,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/78465\/revisions\/78466"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media?parent=78465"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/categories?post=78465"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/tags?post=78465"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}