{"id":1158,"date":"2026-09-05T01:38:47","date_gmt":"2026-09-05T01:38:47","guid":{"rendered":"https:\/\/www.devopsschool.com\/tutorials\/?p=1158"},"modified":"2026-09-05T01:41:12","modified_gmt":"2026-09-05T01:41:12","slug":"terraform-developer-toolchain-hands-on-lab-manual","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/tutorials\/terraform-developer-toolchain-hands-on-lab-manual\/","title":{"rendered":"Terraform Developer Toolchain &#8211; Hands-On Lab Manual"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">DOWNLOAD &#8211; HERE<\/p>\n\n\n\n<div data-wp-interactive=\"core\/file\" class=\"wp-block-file\"><object data-wp-bind--hidden=\"!state.hasPdfPreview\" hidden class=\"wp-block-file__embed\" data=\"https:\/\/www.devopsschool.com\/tutorials\/wp-content\/uploads\/2026\/09\/Terraform_Developer_Toolchain_5W1H_Student_Lab_Manual.pdf\" type=\"application\/pdf\" style=\"width:100%;height:600px\" aria-label=\"Embed of Terraform_Developer_Toolchain_5W1H_Student_Lab_Manual.\"><\/object><a id=\"wp-block-file--media-8d1e9fd6-19b7-405f-afaf-f8d07f78ae91\" href=\"https:\/\/www.devopsschool.com\/tutorials\/wp-content\/uploads\/2026\/09\/Terraform_Developer_Toolchain_5W1H_Student_Lab_Manual.pdf\">Terraform_Developer_Toolchain_5W1H_Student_Lab_Manual<\/a><a href=\"https:\/\/www.devopsschool.com\/tutorials\/wp-content\/uploads\/2026\/09\/Terraform_Developer_Toolchain_5W1H_Student_Lab_Manual.pdf\" class=\"wp-block-file__button wp-element-button\" download aria-describedby=\"wp-block-file--media-8d1e9fd6-19b7-405f-afaf-f8d07f78ae91\">Download<\/a><\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Write -&gt; Validate -&gt; Test -&gt; Lint -&gt; Secure -&gt; Document -&gt; Cost -&gt; Automate -&gt; Govern<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Framework<\/strong><\/td><td>What | Why | When | How to Install | How to Use | Top 5 Commands<\/td><\/tr><tr><td><strong>Scope<\/strong><\/td><td>20 Terraform developer tools + Terraform CLI\/Git foundation<\/td><\/tr><tr><td><strong>Primary goal<\/strong><\/td><td>Learn the workflow by running small, safe exercises<\/td><\/tr><tr><td><strong>Cloud safety<\/strong><\/td><td>Core labs require no cloud credentials; risky AWS examples are scan-only<\/td><\/tr><tr><td><strong>Validated baseline<\/strong><\/td><td>5 September 2026<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Rule for this manual: understand the tool, run the smallest useful exercise, verify the result, then move on.<\/em><\/p>\n\n\n\n<h1 class=\"wp-block-heading\">How to Use This Lab Manual<\/h1>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>GOAL<\/strong><\/td><td>Each lab is deliberately small. Students should be able to explain the tool in one minute and demonstrate its core value with one exercise.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Recommended learning path<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td>VS Code \/ Terraform Extension -&gt; terraform-ls -&gt; Terraform MCP -&gt; tenv -&gt; Terraform CLI<br>-&gt; terraform test -&gt; TFLint -&gt; Trivy -&gt; terraform-docs -&gt; Infracost<br>-&gt; pre-commit -&gt; Git -&gt; GitHub Actions \/ GitLab CI -&gt; HCP Terraform<br>-&gt; optional scale\/governance tools: Terragrunt, Checkov, OPA\/Conftest, Sentinel, Atlantis, Renovate, tfupdate<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Lab rules<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Run commands from the lab workspace unless the lab says otherwise.<\/li>\n\n\n\n<li>Do not paste cloud access keys into Terraform files, shell history, Git, CI variables, or screenshots.<\/li>\n\n\n\n<li>Never run terraform apply inside security-demo\/ or cost-demo\/. Those folders exist only for scanning and cost exercises.<\/li>\n\n\n\n<li>For hosted tools, use a training\/sandbox organization and follow instructor-provided access.<\/li>\n\n\n\n<li>If a command is unavailable, run &lt;tool&gt; &#8211;help and verify your installed version before assuming the lab is wrong.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Requirement<\/strong><\/td><td><strong>Minimum<\/strong><\/td><td><strong>Check<\/strong><\/td><\/tr><tr><td>Terminal + shell<\/td><td>bash\/zsh\/PowerShell<\/td><td>echo $SHELL<\/td><\/tr><tr><td>Git<\/td><td>Recent stable<\/td><td>git &#8211;version<\/td><\/tr><tr><td>VS Code<\/td><td>Recent stable<\/td><td>code &#8211;version<\/td><\/tr><tr><td>Homebrew<\/td><td>Recommended for this manual<\/td><td>brew &#8211;version<\/td><\/tr><tr><td>Docker<\/td><td>Optional: Terraform MCP lab<\/td><td>docker &#8211;version<\/td><\/tr><tr><td>GitHub\/GitLab account<\/td><td>Optional: CI lab<\/td><td>Sign in<\/td><\/tr><tr><td>HCP Terraform account<\/td><td>Optional: HCP lab<\/td><td>Sign in<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">Foundation Lab &#8211; Build the Safe Student Workspace<\/h1>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>WHY<\/strong><\/td><td>Every later lab reuses this tiny project. It creates only local resources, so students can practice Terraform without an AWS\/Azure\/GCP account.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">1. Create the folders<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td>mkdir -p terraform-toolchain-lab\/tests<br>mkdir -p terraform-toolchain-lab\/security-demo<br>mkdir -p terraform-toolchain-lab\/cost-demo<br>cd terraform-toolchain-lab<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">2. Create versions.tf<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td>terraform {<br>&nbsp; required_version = &#8220;&gt;= 1.7.0, &lt; 2.0.0&#8221;<br><br>&nbsp; required_providers {<br>&nbsp;&nbsp;&nbsp; random = {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; source&nbsp; = &#8220;hashicorp\/random&#8221;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; version = &#8220;~&gt; 3.7&#8221;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; local = {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; source&nbsp; = &#8220;hashicorp\/local&#8221;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; version = &#8220;~&gt; 2.5&#8221;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp; }<br>}<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">3. Create main.tf<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td>variable &#8220;environment&#8221; {<br>&nbsp; type&nbsp;&nbsp;&nbsp; = string<br>&nbsp; default = &#8220;dev&#8221;<br><br>&nbsp; validation {<br>&nbsp;&nbsp;&nbsp; condition&nbsp;&nbsp;&nbsp;&nbsp; = contains([&#8220;dev&#8221;, &#8220;staging&#8221;, &#8220;prod&#8221;], var.environment)<br>&nbsp;&nbsp;&nbsp; error_message = &#8220;environment must be dev, staging, or prod&#8221;<br>&nbsp; }<br>}<br><br>locals {<br>&nbsp; app_name = &#8220;training-${var.environment}&#8221;<br>}<br><br>resource &#8220;random_pet&#8221; &#8220;suffix&#8221; {<br>&nbsp; length = 2<br>}<br><br>resource &#8220;local_file&#8221; &#8220;summary&#8221; {<br>&nbsp; filename = &#8220;${path.module}\/generated.txt&#8221;<br>&nbsp; content&nbsp; = &#8220;app=${local.app_name}\\nsuffix=${random_pet.suffix.id}\\n&#8221;<br>}<br><br>output &#8220;app_name&#8221; {<br>&nbsp; value = local.app_name<br>}<br><br>output &#8220;generated_file&#8221; {<br>&nbsp; value = local_file.summary.filename<br>}<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">4. Create tests\/basic.tftest.hcl<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td colspan=\"2\">mock_provider &#8220;random&#8221; {}<br>mock_provider &#8220;local&#8221; {}<br><br>run &#8220;naming_is_correct&#8221; {<br>&nbsp; command = plan<br><br>&nbsp; variables {<br>&nbsp;&nbsp;&nbsp; environment = &#8220;staging&#8221;<br>&nbsp; }<br><br>&nbsp; assert {<br>&nbsp;&nbsp;&nbsp; condition&nbsp;&nbsp;&nbsp;&nbsp; = output.app_name == &#8220;training-staging&#8221;<br>&nbsp;&nbsp;&nbsp; error_message = &#8220;Unexpected application name&#8221;<br>&nbsp; }<br>}<\/td><td>&nbsp;<\/td><\/tr><tr><td><strong>PASS<\/strong><\/td><td colspan=\"2\">The folder contains versions.tf, main.tf, and tests\/basic.tftest.hcl. No cloud credentials are required.<\/td><\/tr><tr><td><\/td><td><\/td><td><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">Foundation &#8211; Terraform CLI + Git<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">5W1H Quick View<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>What?<\/strong><\/td><td>Terraform CLI is the IaC execution engine; Git records and reviews code changes.<\/td><\/tr><tr><td><strong>Why?<\/strong><\/td><td>Every tool in this manual either improves Terraform authoring, checks Terraform, or automates its Git workflow.<\/td><\/tr><tr><td><strong>When?<\/strong><\/td><td>Use Terraform CLI throughout development; use Git for every team change.<\/td><\/tr><tr><td><strong>Install?<\/strong><\/td><td>Terraform is installed through tenv in Lab 08. Git is normally preinstalled or available through your OS package manager.<\/td><\/tr><tr><td><strong>How?<\/strong><\/td><td>Initialize, format, validate, plan, then apply only in the approved context. Commit source and .terraform.lock.hcl &#8211; never state files.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Terraform &#8211; Top 5 Commands<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Command \/ Action<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td>terraform init<\/td><td>Download providers\/modules and initialize the working directory.<\/td><\/tr><tr><td>terraform fmt -recursive<\/td><td>Apply canonical Terraform formatting.<\/td><\/tr><tr><td>terraform validate<\/td><td>Check configuration validity.<\/td><\/tr><tr><td>terraform plan<\/td><td>Preview proposed changes.<\/td><\/tr><tr><td>terraform apply<\/td><td>Execute an approved plan; in this manual only the local training project.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Git &#8211; Top 5 Commands<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td colspan=\"2\"><strong>Command \/ Action<\/strong><\/td><td colspan=\"2\"><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td colspan=\"2\">git status<\/td><td colspan=\"2\">See changed\/untracked files.<\/td><\/tr><tr><td colspan=\"2\">git diff<\/td><td colspan=\"2\">Review your changes before commit.<\/td><\/tr><tr><td colspan=\"2\">git add .<\/td><td colspan=\"2\">Stage intended changes.<\/td><\/tr><tr><td colspan=\"2\">git commit -m &#8220;&#8230;&#8221;<\/td><td colspan=\"2\">Create a reviewable change.<\/td><\/tr><tr><td colspan=\"2\">git push<\/td><td colspan=\"2\">Send the branch to the remote repository.<\/td><\/tr><tr><td><strong>SECURITY<\/strong><\/td><td colspan=\"2\">Never commit *.tfstate, .terraform\/, saved plan files, credentials, tokens, or sensitive *.tfvars.<\/td><td>&nbsp;<\/td><\/tr><tr><td><\/td><td><\/td><td><\/td><td><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">Lab 01 &#8211; VS Code + HashiCorp Terraform Extension<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Terraform-aware editing: syntax, completion, formatting, navigation and diagnostics.<\/em><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Priority:<\/strong> 5\/5<\/td><td><strong>Lab mode:<\/strong> Hands-on<\/td><td><strong>Outcome:<\/strong> Run + verify<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">5W1H Quick View<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>What?<\/strong><\/td><td>The official Terraform extension turns VS Code into a Terraform-aware editor and uses the Terraform language server.<\/td><\/tr><tr><td><strong>Why?<\/strong><\/td><td>It catches simple mistakes while you type and makes provider\/module code easier to navigate.<\/td><\/tr><tr><td><strong>When?<\/strong><\/td><td>Use it whenever authoring or reviewing .tf, .tfvars, .tftest.hcl, Terraform Stacks\/Search\/Policy files.<\/td><\/tr><tr><td><strong>Install?<\/strong><\/td><td>VS Code Extensions -&gt; search &#8220;HashiCorp Terraform&#8221;, or run: code &#8211;install-extension HashiCorp.terraform<\/td><\/tr><tr><td><strong>How?<\/strong><\/td><td>Open the project folder, edit main.tf, use autocomplete, Format Document, Go to Definition, and the Problems panel.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Top 5 Commands \/ Actions<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Command \/ Action<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td>code &#8211;install-extension HashiCorp.terraform<\/td><td>Install the official extension.<\/td><\/tr><tr><td>code .<\/td><td>Open the current Terraform project.<\/td><\/tr><tr><td>Format Document<\/td><td>Run terraform-aware formatting from the editor.<\/td><\/tr><tr><td>F12 &#8211; Go to Definition<\/td><td>Jump to variables, modules, resources, outputs.<\/td><\/tr><tr><td>Problems panel<\/td><td>Review syntax\/diagnostic issues from the language server.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Student Exercise<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">1.&nbsp; Install the extension and open terraform-toolchain-lab as a folder.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2.&nbsp; Open main.tf and type &#8220;var.&#8221; inside a temporary local expression; confirm autocomplete suggests environment.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3.&nbsp; Intentionally misalign an attribute, run Format Document, and confirm formatting changes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">4.&nbsp; Use F12 on var.environment or local.app_name.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">5.&nbsp; Remove the temporary edit and save the file.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PASS &#8211;<\/strong> Expected: Terraform files have syntax highlighting and autocomplete; formatting and navigation work from the editor. Pass when: You can demonstrate autocomplete + Format Document + Go to Definition without using a terminal command.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Lab 02 &#8211; terraform-ls<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Language Server Protocol backend behind Terraform IDE intelligence.<\/em><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Priority:<\/strong> 5\/5<\/td><td><strong>Lab mode:<\/strong> Hands-on \/ background service<\/td><td><strong>Outcome:<\/strong> Run + verify<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">5W1H Quick View<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>What?<\/strong><\/td><td>HashiCorp&#8217;s Terraform language server provides completion, diagnostics, hover and navigation through LSP.<\/td><\/tr><tr><td><strong>Why?<\/strong><\/td><td>Editors need a project-aware process that understands Terraform configuration and schemas.<\/td><\/tr><tr><td><strong>When?<\/strong><\/td><td>Normally every time the Terraform editor extension is active.<\/td><\/tr><tr><td><strong>Install?<\/strong><\/td><td>VS Code normally bundles a compatible terraform-ls. Manual install is mainly for other LSP-capable editors: brew install hashicorp\/tap\/terraform-ls<\/td><\/tr><tr><td><strong>How?<\/strong><\/td><td>Usually let the editor start it automatically. Manual users run terraform-ls serve.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Top 5 Commands \/ Actions<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Command \/ Action<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td>terraform-ls version<\/td><td>Show the installed language-server version.<\/td><\/tr><tr><td>terraform-ls serve<\/td><td>Start the LSP server manually.<\/td><\/tr><tr><td>which terraform-ls<\/td><td>Find the binary on macOS\/Linux.<\/td><\/tr><tr><td>code .<\/td><td>Open a project so the extension can start\/index the server.<\/td><\/tr><tr><td>View -&gt; Output -&gt; HashiCorp Terraform<\/td><td>Inspect language-server startup and diagnostics.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Student Exercise<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">1.&nbsp; In VS Code, open View -&gt; Output and select HashiCorp Terraform.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2.&nbsp; Confirm the output shows a language server starting for the workspace.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3.&nbsp; If you installed terraform-ls separately, run terraform-ls version.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">4.&nbsp; Open main.tf and confirm hover\/completion\/navigation still work.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PASS &#8211;<\/strong> Expected: The editor launches terraform-ls automatically; manual installation is not required for normal VS Code usage. Pass when: You can explain that terraform-ls is a background service, not a replacement for terraform CLI.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Lab 03 &#8211; TFLint<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Lint Terraform and detect provider-specific mistakes, deprecated patterns and quality issues.<\/em><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Priority:<\/strong> 5\/5<\/td><td><strong>Lab mode:<\/strong> Hands-on<\/td><td><strong>Outcome:<\/strong> Run + verify<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">5W1H Quick View<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>What?<\/strong><\/td><td>TFLint is a pluggable Terraform linter with Terraform and cloud-provider rulesets.<\/td><\/tr><tr><td><strong>Why?<\/strong><\/td><td>terraform validate checks validity; TFLint catches additional suspicious or non-standard configuration.<\/td><\/tr><tr><td><strong>When?<\/strong><\/td><td>Run after validate, before commit, and again in CI.<\/td><\/tr><tr><td><strong>Install?<\/strong><\/td><td>macOS\/Homebrew: brew install terraform-linters\/tap\/tflint<\/td><\/tr><tr><td><strong>How?<\/strong><\/td><td>Create .tflint.hcl, initialize plugins, then lint the project.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Top 5 Commands \/ Actions<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Command \/ Action<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td>tflint &#8211;init<\/td><td>Download\/initialize configured ruleset plugins.<\/td><\/tr><tr><td>tflint<\/td><td>Lint the current directory.<\/td><\/tr><tr><td>tflint &#8211;recursive<\/td><td>Lint Terraform directories recursively.<\/td><\/tr><tr><td>tflint &#8211;format=compact<\/td><td>Use concise CI-friendly output.<\/td><\/tr><tr><td>tflint &#8211;fix<\/td><td>Apply fixes for supported fixable findings; always review the diff.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Student Exercise<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td># .tflint.hcl<br>plugin &#8220;terraform&#8221; {<br>&nbsp; enabled = true<br>&nbsp; preset&nbsp; = &#8220;recommended&#8221;<br>}<br><br>rule &#8220;terraform_documented_variables&#8221; {<br>&nbsp; enabled = true<br>}<br><br>rule &#8220;terraform_documented_outputs&#8221; {<br>&nbsp; enabled = true<br>}<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">1.&nbsp; Install TFLint and create the configuration shown below in the lab root.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2.&nbsp; Run tflint &#8211;init.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3.&nbsp; Run tflint.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">4.&nbsp; Read every finding; do not blindly suppress warnings.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">5.&nbsp; Run git diff after any automatic fix.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PASS &#8211;<\/strong> Expected: TFLint completes and reports either no findings or clear lint findings tied to Terraform source. Pass when: tflint &#8211;init succeeds and tflint runs against the lab directory.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Lab 04 &#8211; Trivy<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Scan Terraform for IaC security misconfiguration and optionally repository secrets.<\/em><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Priority:<\/strong> 5\/5<\/td><td><strong>Lab mode:<\/strong> Hands-on &#8211; scan only<\/td><td><strong>Outcome:<\/strong> Run + verify<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">5W1H Quick View<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>What?<\/strong><\/td><td>Trivy is a unified security scanner; trivy config scans Terraform\/IaC misconfiguration.<\/td><\/tr><tr><td><strong>Why?<\/strong><\/td><td>Valid Terraform can still create publicly exposed, unencrypted or overly permissive infrastructure.<\/td><\/tr><tr><td><strong>When?<\/strong><\/td><td>Run locally before commit and enforce selected severities in CI.<\/td><\/tr><tr><td><strong>Install?<\/strong><\/td><td>macOS\/Homebrew: brew install trivy<\/td><\/tr><tr><td><strong>How?<\/strong><\/td><td>Scan source with trivy config. Use trivy fs when you also want secret scanning.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Top 5 Commands \/ Actions<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Command \/ Action<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td>trivy config .<\/td><td>Scan IaC configuration recursively.<\/td><\/tr><tr><td>trivy config &#8211;severity HIGH,CRITICAL .<\/td><td>Focus on serious findings.<\/td><\/tr><tr><td>trivy config &#8211;exit-code 1 .<\/td><td>Return failure for detected findings &#8211; useful in CI.<\/td><\/tr><tr><td>trivy config &#8211;tf-vars values.tfvars .<\/td><td>Evaluate source using supplied Terraform variables.<\/td><\/tr><tr><td>trivy fs &#8211;scanners misconfig,secret .<\/td><td>Scan repository files for misconfigurations and secrets.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Student Exercise<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>CAUTION<\/strong><\/td><td colspan=\"2\">This file is intentionally insecure. It exists only to produce scanner findings. Never run terraform apply in security-demo\/.<\/td><\/tr><tr><td colspan=\"2\"># security-demo\/main.tf &#8211; TRAINING ONLY, DO NOT APPLY<br>resource &#8220;aws_security_group&#8221; &#8220;insecure_demo&#8221; {<br>&nbsp; name = &#8220;training-insecure-demo&#8221;<br><br>&nbsp; ingress {<br>&nbsp;&nbsp;&nbsp; from_port&nbsp;&nbsp; = 22<br>&nbsp;&nbsp;&nbsp; to_port&nbsp;&nbsp;&nbsp;&nbsp; = 22<br>&nbsp;&nbsp;&nbsp; protocol&nbsp;&nbsp;&nbsp; = &#8220;tcp&#8221;<br>&nbsp;&nbsp;&nbsp; cidr_blocks = [&#8220;0.0.0.0\/0&#8221;]<br>&nbsp; }<br>}<\/td><td>&nbsp;<\/td><\/tr><tr><td><\/td><td><\/td><td><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">1.&nbsp; Create security-demo\/main.tf from the sample below.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2.&nbsp; From the lab root run: trivy config security-demo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3.&nbsp; Run again with &#8211;severity HIGH,CRITICAL.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">4.&nbsp; Explain why an open SSH ingress rule is risky.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">5.&nbsp; Do not apply the security-demo configuration.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PASS &#8211;<\/strong> Expected: Trivy flags the intentionally risky Terraform configuration or reports the security checks it evaluated. Pass when: You can identify the insecure setting and explain the remediation: restrict the CIDR\/remove public SSH.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Lab 05 &#8211; terraform-docs<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Generate accurate Terraform module input\/output\/provider documentation.<\/em><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Priority:<\/strong> 5\/5<\/td><td><strong>Lab mode:<\/strong> Hands-on<\/td><td><strong>Outcome:<\/strong> Run + verify<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">5W1H Quick View<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>What?<\/strong><\/td><td>terraform-docs generates documentation directly from Terraform module source.<\/td><\/tr><tr><td><strong>Why?<\/strong><\/td><td>Manually maintained input\/output tables drift quickly as modules change.<\/td><\/tr><tr><td><strong>When?<\/strong><\/td><td>Use for reusable modules and run automatically before commit\/CI.<\/td><\/tr><tr><td><strong>Install?<\/strong><\/td><td>macOS\/Homebrew: brew install terraform-docs<\/td><\/tr><tr><td><strong>How?<\/strong><\/td><td>Add BEGIN_TF_DOCS \/ END_TF_DOCS markers to README.md and inject generated Markdown.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Top 5 Commands \/ Actions<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Command \/ Action<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td>terraform-docs markdown table .<\/td><td>Print Markdown table documentation.<\/td><\/tr><tr><td>terraform-docs markdown table &#8211;output-file README.md &#8211;output-mode inject .<\/td><td>Inject generated docs into README.<\/td><\/tr><tr><td>terraform-docs markdown document .<\/td><td>Generate Markdown document format.<\/td><\/tr><tr><td>terraform-docs json .<\/td><td>Generate JSON metadata.<\/td><\/tr><tr><td>terraform-docs &#8211;output-check .<\/td><td>Check whether configured generated output is current.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Student Exercise<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td># Terraform Toolchain Lab<br><br>&lt;!&#8211; BEGIN_TF_DOCS &#8211;&gt;<br>&lt;!&#8211; END_TF_DOCS &#8211;&gt;<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">1.&nbsp; Create README.md with the marker block shown below.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2.&nbsp; Run the inject command.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3.&nbsp; Open README.md and locate Requirements, Providers, Inputs and Outputs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">4.&nbsp; Change the environment variable description in main.tf and regenerate docs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">5.&nbsp; Use git diff to see exactly what changed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PASS &#8211;<\/strong> Expected: README.md contains an automatically generated Terraform reference between the markers. Pass when: Changing Terraform input\/output metadata and rerunning terraform-docs updates README without manual table editing.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Lab 06 &#8211; pre-commit-terraform<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Run Terraform quality\/security\/documentation checks automatically before Git commits.<\/em><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Priority:<\/strong> 5\/5<\/td><td><strong>Lab mode:<\/strong> Hands-on<\/td><td><strong>Outcome:<\/strong> Run + verify<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">5W1H Quick View<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>What?<\/strong><\/td><td>A collection of pre-commit hooks specialized for Terraform and related tools.<\/td><\/tr><tr><td><strong>Why?<\/strong><\/td><td>Developers get fast feedback before pushing broken or unformatted Terraform.<\/td><\/tr><tr><td><strong>When?<\/strong><\/td><td>Use on every Terraform repository; CI must still re-run authoritative checks.<\/td><\/tr><tr><td><strong>Install?<\/strong><\/td><td>Install pre-commit first: brew install pre-commit. Hooks are fetched from the repository config.<\/td><\/tr><tr><td><strong>How?<\/strong><\/td><td>Create .pre-commit-config.yaml, install the Git hook, run all files once.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Top 5 Commands \/ Actions<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Command \/ Action<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td>pre-commit install<\/td><td>Install the Git pre-commit hook.<\/td><\/tr><tr><td>pre-commit run &#8211;all-files<\/td><td>Run every configured hook now.<\/td><\/tr><tr><td>pre-commit run terraform_fmt &#8211;all-files<\/td><td>Run one specific hook.<\/td><\/tr><tr><td>pre-commit autoupdate<\/td><td>Update hook revisions; review resulting changes.<\/td><\/tr><tr><td>pre-commit clean<\/td><td>Clear cached hook environments when troubleshooting.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Student Exercise<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td>repos:<br>&nbsp; &#8211; repo: https:\/\/github.com\/antonbabenko\/pre-commit-terraform<br>&nbsp;&nbsp;&nbsp; rev: REV<br>&nbsp;&nbsp;&nbsp; hooks:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; id: terraform_fmt<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; id: terraform_validate<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; id: terraform_tflint<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; id: terraform_trivy<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; id: terraform_docs<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">1.&nbsp; Create the minimal config below.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2.&nbsp; Replace REV with the instructor-approved current pre-commit-terraform release.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3.&nbsp; Run pre-commit install.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">4.&nbsp; Run pre-commit run &#8211;all-files.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">5.&nbsp; Fix any failure and re-run until all hooks pass.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PASS &#8211;<\/strong> Expected: One command runs formatting, validation, TFLint, Trivy and terraform-docs in a repeatable local workflow. Pass when: pre-commit run &#8211;all-files finishes successfully after you resolve findings.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Lab 07 &#8211; Infracost<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Show cost impact before Terraform changes are applied.<\/em><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Priority:<\/strong> 4\/5<\/td><td><strong>Lab mode:<\/strong> Hands-on \/ login may be required<\/td><td><strong>Outcome:<\/strong> Run + verify<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">5W1H Quick View<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>What?<\/strong><\/td><td>Infracost analyzes IaC and estimates cloud cost plus FinOps\/policy findings.<\/td><\/tr><tr><td><strong>Why?<\/strong><\/td><td>A technically correct plan can still introduce unexpected monthly spend.<\/td><\/tr><tr><td><strong>When?<\/strong><\/td><td>Use during development and PR review for cost-bearing resources.<\/td><\/tr><tr><td><strong>Install?<\/strong><\/td><td>macOS\/Homebrew: brew install infracost<\/td><\/tr><tr><td><strong>How?<\/strong><\/td><td>Use the current CLI workflow: setup\/auth -&gt; scan -&gt; inspect; price is useful for one-off snippets.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Top 5 Commands \/ Actions<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Command \/ Action<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td>infracost setup<\/td><td>Interactive first-time configuration.<\/td><\/tr><tr><td>infracost auth login<\/td><td>Authenticate the CLI.<\/td><\/tr><tr><td>infracost scan .\/cost-demo<\/td><td>Scan IaC and calculate costs\/policy results.<\/td><\/tr><tr><td>infracost inspect &#8211;summary<\/td><td>Summarize results from the latest scan.<\/td><\/tr><tr><td>infracost price &lt; cost-demo\/main.tf<\/td><td>Quickly price Terraform piped on stdin.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Student Exercise<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>CAUTION<\/strong><\/td><td colspan=\"2\">Do not apply cost-demo\/. The AMI is only a placeholder and the example exists to demonstrate cost feedback.<\/td><\/tr><tr><td colspan=\"2\"># cost-demo\/main.tf &#8211; COST ANALYSIS ONLY, DO NOT APPLY<br>resource &#8220;aws_instance&#8221; &#8220;training_cost&#8221; {<br>&nbsp; ami&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = &#8220;ami-00000000000000000&#8221;<br>&nbsp; instance_type = &#8220;m5.4xlarge&#8221;<br>}<\/td><td>&nbsp;<\/td><\/tr><tr><td><\/td><td><\/td><td><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">1.&nbsp; Create cost-demo\/main.tf from the sample below.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2.&nbsp; Authenticate\/configure Infracost if your training environment requires it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3.&nbsp; Run infracost scan .\/cost-demo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">4.&nbsp; Run infracost inspect &#8211;summary and identify monthly cost.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">5.&nbsp; Change instance_type to a smaller type and scan again. Compare the result.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PASS &#8211;<\/strong> Expected: Students can see a price\/cost estimate before any infrastructure is created. Pass when: You can explain the cost difference between two instance sizes without running terraform apply.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Lab 08 &#8211; tenv<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Manage Terraform, OpenTofu and Terragrunt versions per project.<\/em><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Priority:<\/strong> 4\/5<\/td><td><strong>Lab mode:<\/strong> Hands-on<\/td><td><strong>Outcome:<\/strong> Run + verify<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">5W1H Quick View<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>What?<\/strong><\/td><td>tenv is a version manager for Terraform\/OpenTofu\/Terragrunt and related IaC tools.<\/td><\/tr><tr><td><strong>Why?<\/strong><\/td><td>Different projects may require different Terraform versions. One global binary causes version drift.<\/td><\/tr><tr><td><strong>When?<\/strong><\/td><td>Use whenever teams maintain multiple Terraform repositories or CI must reproduce local versions.<\/td><\/tr><tr><td><strong>Install?<\/strong><\/td><td>macOS\/Homebrew: brew install tofuutils\/tap\/tenv<\/td><\/tr><tr><td><strong>How?<\/strong><\/td><td>Install a compatible Terraform version, select it, and let tenv detect project requirements.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Top 5 Commands \/ Actions<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Command \/ Action<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td>tenv tf install latest-allowed<\/td><td>Install the newest Terraform allowed by required_version.<\/td><\/tr><tr><td>tenv tf use latest-allowed<\/td><td>Select an allowed installed version.<\/td><\/tr><tr><td>tenv tf detect<\/td><td>Show which Terraform version the project resolves to.<\/td><\/tr><tr><td>tenv tf list<\/td><td>List installed Terraform versions.<\/td><\/tr><tr><td>tenv tf list-remote &#8211;stable<\/td><td>List stable remote versions available to install.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Student Exercise<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">1.&nbsp; Install tenv.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2.&nbsp; From the lab root run tenv tf install latest-allowed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3.&nbsp; Run tenv tf use latest-allowed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">4.&nbsp; Run terraform version.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">5.&nbsp; Run tenv tf detect and compare the resolved version with versions.tf.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PASS &#8211;<\/strong> Expected: Terraform runs with a version compatible with the project required_version constraint. Pass when: tenv tf detect and terraform version agree on an allowed Terraform release.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Lab 09 &#8211; Terragrunt<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Reduce repeated configuration and orchestrate larger multi-environment Terraform layouts.<\/em><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Priority:<\/strong> 4\/5<\/td><td><strong>Lab mode:<\/strong> Hands-on &#8211; optional architecture tool<\/td><td><strong>Outcome:<\/strong> Run + verify<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">5W1H Quick View<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>What?<\/strong><\/td><td>Terragrunt is a thin orchestration\/configuration layer around Terraform\/OpenTofu.<\/td><\/tr><tr><td><strong>Why?<\/strong><\/td><td>It can reduce repeated backend\/provider\/environment configuration and coordinate multiple units.<\/td><\/tr><tr><td><strong>When?<\/strong><\/td><td>Use when repetition\/orchestration is a real problem across many accounts, regions or environments.<\/td><\/tr><tr><td><strong>Install?<\/strong><\/td><td>macOS\/Homebrew: brew install terragrunt<\/td><\/tr><tr><td><strong>How?<\/strong><\/td><td>Create terragrunt.hcl that points at a Terraform module\/source and supplies environment-specific inputs.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Top 5 Commands \/ Actions<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Command \/ Action<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td>terragrunt plan<\/td><td>Plan one Terragrunt unit.<\/td><\/tr><tr><td>terragrunt apply<\/td><td>Apply one unit; use only in approved environments.<\/td><\/tr><tr><td>terragrunt run &#8211;all plan<\/td><td>Plan all discovered units using current Terragrunt 1.x style.<\/td><\/tr><tr><td>terragrunt run &#8211;all validate<\/td><td>Validate all units.<\/td><\/tr><tr><td>terragrunt hcl fmt<\/td><td>Format Terragrunt HCL.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Student Exercise<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td># terragrunt-demo\/dev\/terragrunt.hcl<br>terraform {<br>&nbsp; source = &#8220;..\/..&#8221;<br>}<br><br>inputs = {<br>&nbsp; environment = &#8220;dev&#8221;<br>}<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">1.&nbsp; Create terragrunt-demo\/dev\/terragrunt.hcl as shown below.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2.&nbsp; Run terragrunt hcl fmt inside terragrunt-demo\/dev.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3.&nbsp; Run terragrunt plan.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">4.&nbsp; Identify the environment input passed to the Terraform root module.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">5.&nbsp; Do not add Terragrunt to a real repository merely because this lab demonstrates it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PASS &#8211;<\/strong> Expected: Terragrunt invokes the Terraform source and passes environment=&#8221;dev&#8221;. Pass when: You can explain one real problem Terragrunt solves and one reason a simple Terraform-only repo may not need it.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Lab 10 &#8211; terraform test<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Native Terraform tests using .tftest.hcl, assertions and mocked providers.<\/em><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Priority:<\/strong> 4\/5<\/td><td><strong>Lab mode:<\/strong> Hands-on<\/td><td><strong>Outcome:<\/strong> Run + verify<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">5W1H Quick View<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>What?<\/strong><\/td><td>terraform test is Terraform&#8217;s native test runner for modules and root configurations.<\/td><\/tr><tr><td><strong>Why?<\/strong><\/td><td>validate proves configuration validity; tests prove intended behavior through assertions.<\/td><\/tr><tr><td><strong>When?<\/strong><\/td><td>Use for module contracts, input validation, outputs and planned configuration behavior.<\/td><\/tr><tr><td><strong>Install?<\/strong><\/td><td>No separate install &#8211; it is part of modern Terraform CLI.<\/td><\/tr><tr><td><strong>How?<\/strong><\/td><td>Put *.tftest.hcl in tests\/, define run blocks\/assertions, then run terraform test.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Top 5 Commands \/ Actions<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Command \/ Action<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td>terraform test<\/td><td>Run all Terraform tests.<\/td><\/tr><tr><td>terraform test -verbose<\/td><td>Show plan\/state details for each run.<\/td><\/tr><tr><td>terraform test -filter=tests\/basic.tftest.hcl<\/td><td>Run one test file.<\/td><\/tr><tr><td>terraform test -json<\/td><td>Emit machine-readable test output.<\/td><\/tr><tr><td>terraform test -junit-xml=test-results.xml<\/td><td>Write a JUnit XML report for CI systems.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Student Exercise<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">1.&nbsp; Confirm tests\/basic.tftest.hcl exists from the foundation setup.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2.&nbsp; Run terraform init.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3.&nbsp; Run terraform test.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">4.&nbsp; Change the expected value in the assertion so the test fails; read the failure.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">5.&nbsp; Restore the correct assertion and rerun until PASS.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PASS &#8211;<\/strong> Expected: The naming_is_correct test passes with environment=staging and expected output training-staging. Pass when: You can intentionally make the test fail, explain why, fix it, and return to a passing test suite.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Lab 11 &#8211; terraform console<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Interactively test Terraform expressions, collections, functions and CIDR calculations.<\/em><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Priority:<\/strong> 4\/5<\/td><td><strong>Lab mode:<\/strong> Hands-on<\/td><td><strong>Outcome:<\/strong> Run + verify<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">5W1H Quick View<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>What?<\/strong><\/td><td>An interactive expression console using the current Terraform configuration context.<\/td><\/tr><tr><td><strong>Why?<\/strong><\/td><td>It is faster than editing\/applying code just to test an expression or function.<\/td><\/tr><tr><td><strong>When?<\/strong><\/td><td>Use while developing locals, for expressions, maps\/lists, conditionals and CIDR math.<\/td><\/tr><tr><td><strong>Install?<\/strong><\/td><td>No separate install &#8211; included with Terraform CLI.<\/td><\/tr><tr><td><strong>How?<\/strong><\/td><td>Run terraform console, evaluate expressions, then type exit or press Ctrl-D.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Top 5 Commands \/ Actions<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Command \/ Action<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td>terraform console<\/td><td>Start the interactive console.<\/td><\/tr><tr><td>upper(&#8220;prod&#8221;)<\/td><td>Test a string function.<\/td><\/tr><tr><td>contains([&#8220;dev&#8221;,&#8221;prod&#8221;], &#8220;prod&#8221;)<\/td><td>Test collection membership.<\/td><\/tr><tr><td>[for x in [&#8220;api&#8221;,&#8221;worker&#8221;] : &#8220;prod-${x}&#8221;]<\/td><td>Test a for-expression.<\/td><\/tr><tr><td>cidrsubnet(&#8220;10.0.0.0\/16&#8221;, 8, 1)<\/td><td>Calculate a subnet CIDR.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Student Exercise<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">1.&nbsp; Run terraform console in the lab root.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2.&nbsp; Evaluate local.app_name.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3.&nbsp; Run each of the four example expressions above.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">4.&nbsp; Predict each result before pressing Enter.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">5.&nbsp; Exit the console.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PASS &#8211;<\/strong> Expected: Students can prototype Terraform language expressions without changing resources. Pass when: You can explain at least one expression result and recreate it in Terraform code if needed.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Lab 12 &#8211; Checkov<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><em>IaC security\/compliance scanning and Terraform plan-policy analysis.<\/em><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Priority:<\/strong> 4\/5<\/td><td><strong>Lab mode:<\/strong> Hands-on &#8211; optional alternative\/complement to Trivy<\/td><td><strong>Outcome:<\/strong> Run + verify<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">5W1H Quick View<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>What?<\/strong><\/td><td>Checkov statically scans Terraform and other IaC against security\/compliance policies.<\/td><\/tr><tr><td><strong>Why?<\/strong><\/td><td>It gives broad policy coverage and can also evaluate Terraform plan JSON.<\/td><\/tr><tr><td><strong>When?<\/strong><\/td><td>Use when your organization standardizes on Checkov\/Prisma policies or needs policy coverage not provided by your primary scanner.<\/td><\/tr><tr><td><strong>Install?<\/strong><\/td><td>Simple Python isolation: pipx install checkov. Homebrew is also available.<\/td><\/tr><tr><td><strong>How?<\/strong><\/td><td>Scan a directory or file, filter frameworks\/checks, and optionally scan plan JSON in trusted CI.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Top 5 Commands \/ Actions<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Command \/ Action<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td>checkov -d security-demo<\/td><td>Scan a directory.<\/td><\/tr><tr><td>checkov -f security-demo\/main.tf<\/td><td>Scan one file.<\/td><\/tr><tr><td>checkov -d . &#8211;framework terraform<\/td><td>Limit scan to Terraform.<\/td><\/tr><tr><td>checkov -l<\/td><td>List available checks.<\/td><\/tr><tr><td>checkov -f tfplan.json<\/td><td>Scan Terraform plan JSON.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Student Exercise<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">1.&nbsp; Install Checkov.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2.&nbsp; Run checkov -d security-demo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3.&nbsp; Compare its findings with Trivy from Lab 04.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">4.&nbsp; Identify overlapping findings and any unique checks.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">5.&nbsp; Decide whether your hypothetical team needs Trivy, Checkov, or both &#8211; and state one reason.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PASS &#8211;<\/strong> Expected: Checkov reports policy results against the intentionally insecure sample. Pass when: You can explain why running two scanners with identical findings may add noise rather than value.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Lab 13 &#8211; Conftest \/ OPA<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Write custom vendor-neutral policy-as-code checks with Rego.<\/em><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Priority:<\/strong> 3\/5<\/td><td><strong>Lab mode:<\/strong> Hands-on &#8211; policy fundamentals<\/td><td><strong>Outcome:<\/strong> Run + verify<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">5W1H Quick View<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>What?<\/strong><\/td><td>OPA is a general policy engine; Conftest is a convenient CLI for applying Rego policies to configuration files.<\/td><\/tr><tr><td><strong>Why?<\/strong><\/td><td>Built-in scanner rules cannot express every organization-specific rule.<\/td><\/tr><tr><td><strong>When?<\/strong><\/td><td>Use for custom rules such as approved environments, regions, tags, resource classes and network policies.<\/td><\/tr><tr><td><strong>Install?<\/strong><\/td><td>macOS\/Homebrew: brew install opa conftest<\/td><\/tr><tr><td><strong>How?<\/strong><\/td><td>Write a Rego deny rule, test an input with conftest, and unit-test policy logic with OPA\/Conftest.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Top 5 Commands \/ Actions<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Command \/ Action<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td>conftest test input.json<\/td><td>Evaluate input against policy\/.<\/td><\/tr><tr><td>conftest test -p policy input.json<\/td><td>Use an explicit policy directory.<\/td><\/tr><tr><td>conftest verify &#8211;policy policy<\/td><td>Run Rego policy unit tests.<\/td><\/tr><tr><td>opa eval -d policy -i input.json &#8220;data.training.deny&#8221;<\/td><td>Evaluate a Rego decision directly.<\/td><\/tr><tr><td>opa test policy<\/td><td>Run OPA test_* rules.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Student Exercise<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td># policy\/input.json<br>{&#8220;environment&#8221;:&#8221;prod&#8221;,&#8221;public_cidr&#8221;:&#8221;0.0.0.0\/0&#8243;}<br><br># policy\/network.rego<br>package training<br><br>deny contains msg if {<br>&nbsp; input.environment == &#8220;prod&#8221;<br>&nbsp; input.public_cidr == &#8220;0.0.0.0\/0&#8221;<br>&nbsp; msg := &#8220;Production must not allow a public CIDR&#8221;<br>}<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">1.&nbsp; Create policy\/input.json and policy\/network.rego using the sample below.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2.&nbsp; Run conftest test -p policy policy\/input.json.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3.&nbsp; Change public_cidr to 10.0.0.0\/8 and rerun.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">4.&nbsp; Explain why the first input should fail and the second should pass.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">5.&nbsp; Relate the rule to a Terraform plan-policy gate.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PASS &#8211;<\/strong> Expected: The policy denies a production configuration with public CIDR 0.0.0.0\/0 and passes the restricted CIDR. Pass when: You can change only input data and observe policy behavior without changing the Rego rule.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Lab 14 &#8211; Sentinel<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><em>HashiCorp policy-as-code for HCP Terraform \/ Terraform Enterprise governance.<\/em><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Priority:<\/strong> 3\/5<\/td><td><strong>Lab mode:<\/strong> Local CLI + guided HCP integration<\/td><td><strong>Outcome:<\/strong> Run + verify<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">5W1H Quick View<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>What?<\/strong><\/td><td>Sentinel is HashiCorp&#8217;s policy-as-code language\/runtime integrated with HCP Terraform\/TFE.<\/td><\/tr><tr><td><strong>Why?<\/strong><\/td><td>It can enforce organization-wide rules between plan and apply.<\/td><\/tr><tr><td><strong>When?<\/strong><\/td><td>Use when your HCP\/TFE governance model standardizes on Sentinel policy sets.<\/td><\/tr><tr><td><strong>Install?<\/strong><\/td><td>Install the Sentinel CLI from HashiCorp releases; verify with sentinel &#8211;help.<\/td><\/tr><tr><td><strong>How?<\/strong><\/td><td>Develop\/format\/test policy locally, then publish it through the organization policy workflow.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Top 5 Commands \/ Actions<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Command \/ Action<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td>sentinel &#8211;help<\/td><td>List CLI commands.<\/td><\/tr><tr><td>sentinel version<\/td><td>Show runtime version.<\/td><\/tr><tr><td>sentinel fmt policy.sentinel<\/td><td>Format Sentinel source.<\/td><\/tr><tr><td>sentinel apply policy.sentinel<\/td><td>Evaluate a policy locally.<\/td><\/tr><tr><td>sentinel test<\/td><td>Run local Sentinel policy tests.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Student Exercise<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td># policy.sentinel<br>main = rule {<br>&nbsp; true<br>}<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">1.&nbsp; Create policy.sentinel from the minimal example below.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2.&nbsp; Run sentinel fmt policy.sentinel.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3.&nbsp; Run sentinel apply policy.sentinel and confirm success.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">4.&nbsp; Change true to false and confirm the policy fails.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">5.&nbsp; Restore the passing policy. Instructor demonstrates how a real plan policy set is attached in HCP Terraform\/TFE.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PASS &#8211;<\/strong> Expected: Students understand the local Sentinel development loop and that HCP\/TFE policy integration is a separate governance layer. Pass when: You can make a policy pass\/fail locally and explain advisory versus blocking enforcement conceptually.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Lab 15 &#8211; HCP Terraform<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Managed remote state, remote runs, collaboration, registry, policy and governance.<\/em><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Priority:<\/strong> 5\/5<\/td><td><strong>Lab mode:<\/strong> Guided \/ HCP account required<\/td><td><strong>Outcome:<\/strong> Run + verify<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">5W1H Quick View<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>What?<\/strong><\/td><td>HashiCorp&#8217;s managed Terraform platform for state, runs, workspaces\/projects, variables, registry and governance.<\/td><\/tr><tr><td><strong>Why?<\/strong><\/td><td>Teams need centralized state, locking, repeatable execution, RBAC and audit instead of laptop-owned production workflows.<\/td><\/tr><tr><td><strong>When?<\/strong><\/td><td>Use for team\/enterprise Terraform when managed remote execution and governance fit the operating model.<\/td><\/tr><tr><td><strong>Install?<\/strong><\/td><td>No separate HCP client is required; use Terraform CLI and an HCP Terraform account.<\/td><\/tr><tr><td><strong>How?<\/strong><\/td><td>terraform login, add a cloud block, terraform init, then plan\/apply through the configured HCP workspace.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Top 5 Commands \/ Actions<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Command \/ Action<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td>terraform login<\/td><td>Authenticate interactively to app.terraform.io.<\/td><\/tr><tr><td>terraform init<\/td><td>Connect the local configuration to HCP Terraform settings.<\/td><\/tr><tr><td>terraform plan<\/td><td>Start\/view a remote plan in CLI-driven workflow.<\/td><\/tr><tr><td>terraform apply<\/td><td>Start an approved remote apply.<\/td><\/tr><tr><td>HCP UI: Runs \/ States \/ Variables<\/td><td>Inspect centralized run, state and workspace context.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Student Exercise<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>CAUTION<\/strong><\/td><td colspan=\"2\">Use only an instructor-provided sandbox workspace. Do not point this lab at production state or credentials.<\/td><\/tr><tr><td colspan=\"2\">terraform {<br>&nbsp; cloud {<br>&nbsp;&nbsp;&nbsp; organization = &#8220;YOUR_TRAINING_ORG&#8221;<br><br>&nbsp;&nbsp;&nbsp; workspaces {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; name = &#8220;terraform-toolchain-lab-YOURNAME&#8221;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp; }<br>}<\/td><td>&nbsp;<\/td><\/tr><tr><td><\/td><td><\/td><td><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">1.&nbsp; Create a training workspace in the instructor-provided HCP organization.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2.&nbsp; Run terraform login.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3.&nbsp; Add cloud.tf using the example below and your assigned organization\/workspace.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">4.&nbsp; Run terraform init, then terraform plan.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">5.&nbsp; Open the HCP workspace and locate the run, variables and state areas. Apply only if the instructor explicitly enables it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PASS &#8211;<\/strong> Expected: The CLI is connected to an HCP workspace and a plan is visible in both terminal and HCP UI. Pass when: You can explain why HCP Terraform workspaces are deployment\/state\/RBAC boundaries, not merely local CLI workspaces.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Lab 16 &#8211; GitHub Actions \/ GitLab CI<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Automate Terraform validation, testing and security checks on every change.<\/em><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Priority:<\/strong> 5\/5<\/td><td><strong>Lab mode:<\/strong> Hands-on with GitHub; GitLab equivalent<\/td><td><strong>Outcome:<\/strong> Run + verify<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">5W1H Quick View<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>What?<\/strong><\/td><td>Hosted CI engines that run repeatable checks from repository events.<\/td><\/tr><tr><td><strong>Why?<\/strong><\/td><td>Local hooks can be skipped; CI becomes the authoritative, reviewable quality gate.<\/td><\/tr><tr><td><strong>When?<\/strong><\/td><td>Use for every team Terraform repository, especially before plan\/apply.<\/td><\/tr><tr><td><strong>Install?<\/strong><\/td><td>No runner install is needed for basic hosted GitHub\/GitLab CI. Add workflow YAML to the repository.<\/td><\/tr><tr><td><strong>How?<\/strong><\/td><td>Trigger on pull requests, install pinned Terraform, run fmt\/validate\/test\/security, then add OIDC-based plan\/apply jobs when cloud access is needed.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Top 5 Commands \/ Actions<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Command \/ Action<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td>git push<\/td><td>Trigger CI for the pushed branch.<\/td><\/tr><tr><td>gh workflow list<\/td><td>List GitHub Actions workflows.<\/td><\/tr><tr><td>gh workflow run &lt;workflow&gt;<\/td><td>Manually dispatch a supported workflow.<\/td><\/tr><tr><td>gh run list<\/td><td>List workflow runs.<\/td><\/tr><tr><td>gh run view &lt;id&gt; &#8211;log<\/td><td>Read a run log from GitHub CLI.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Student Exercise<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td>on:<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n<p>[pull_request]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">jobs:<br>&nbsp; quality:<br>&nbsp;&nbsp;&nbsp; runs-on: ubuntu-latest<br>&nbsp;&nbsp;&nbsp; steps:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; uses: actions\/checkout@v4<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; uses: hashicorp\/setup-terraform@v3<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; run: terraform fmt -check -recursive<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; run: terraform init -backend=false -input=false<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; run: terraform validate<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; run: terraform test<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">1.&nbsp; Create .github\/workflows\/terraform-ci.yml using the sample below.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2.&nbsp; Commit and push to a training repository.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3.&nbsp; Open a pull request.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">4.&nbsp; Confirm fmt, validate and test execute in CI. For real cloud plans, use OIDC\/workload identity &#8211; never static cloud keys.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PASS &#8211;<\/strong> Expected: The pull request receives an automated Terraform quality result without any cloud secret in the workflow. Pass when: CI fails when formatting\/tests fail and passes after the issue is fixed.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Lab 17 &#8211; Atlantis<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><em>PR-comment-driven Terraform plans\/applies with project locking and approval workflow.<\/em><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Priority:<\/strong> 4\/5<\/td><td><strong>Lab mode:<\/strong> Guided &#8211; shared Atlantis server required<\/td><td><strong>Outcome:<\/strong> Run + verify<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">5W1H Quick View<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>What?<\/strong><\/td><td>Atlantis listens to pull requests and runs Terraform plan\/apply based on comments and repository configuration.<\/td><\/tr><tr><td><strong>Why?<\/strong><\/td><td>It makes Terraform execution visible and conversational inside the pull request.<\/td><\/tr><tr><td><strong>When?<\/strong><\/td><td>Use when your operating model wants PR-driven Terraform and you are prepared to run\/secure Atlantis.<\/td><\/tr><tr><td><strong>Install?<\/strong><\/td><td>Students do not need to install a server for this lab; use an instructor-provided Atlantis sandbox.<\/td><\/tr><tr><td><strong>How?<\/strong><\/td><td>Open\/change a PR, let autoplan run or comment atlantis plan, obtain approval, then atlantis apply if permitted.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Top 5 Commands \/ Actions<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Command \/ Action<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td>atlantis help<\/td><td>Show supported PR commands.<\/td><\/tr><tr><td>atlantis plan<\/td><td>Plan modified projects.<\/td><\/tr><tr><td>atlantis plan -p project1<\/td><td>Plan one configured project.<\/td><\/tr><tr><td>atlantis apply<\/td><td>Apply available approved plans.<\/td><\/tr><tr><td>atlantis unlock<\/td><td>Remove Atlantis locks\/discard plans for the PR.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Student Exercise<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>CAUTION<\/strong><\/td><td>Atlantis apply is a real infrastructure action. In this lab it is allowed only in the instructor-provided sandbox.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">1.&nbsp; Open the instructor-provided training PR.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2.&nbsp; Comment: atlantis plan.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3.&nbsp; Read the returned Terraform plan.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">4.&nbsp; Observe the PR\/project lock behavior.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">5.&nbsp; Only if the instructor has configured a safe sandbox and approval requirement, run atlantis apply.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PASS &#8211;<\/strong> Expected: Plan output is posted back to the PR and is tied to the PR\/project context. Pass when: You can explain how Atlantis differs from generic CI and from HCP Terraform.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Lab 18 &#8211; Renovate<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Automatically create dependency-update PRs for Terraform, providers, modules and CI actions.<\/em><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Priority:<\/strong> 4\/5<\/td><td><strong>Lab mode:<\/strong> Hands-on config + guided bot<\/td><td><strong>Outcome:<\/strong> Run + verify<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">5W1H Quick View<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>What?<\/strong><\/td><td>Renovate is a dependency update automation bot with Terraform-aware managers.<\/td><\/tr><tr><td><strong>Why?<\/strong><\/td><td>Small, continuous version updates are safer than large, infrequent upgrade jumps.<\/td><\/tr><tr><td><strong>When?<\/strong><\/td><td>Use on actively maintained Terraform repositories with CI\/review gates.<\/td><\/tr><tr><td><strong>Install?<\/strong><\/td><td>For local config validation use Node\/npm (npx). For production, install the Renovate GitHub\/GitLab app or self-host the bot.<\/td><\/tr><tr><td><strong>How?<\/strong><\/td><td>Create renovate.json, validate it, enable the bot, then review dependency dashboard\/update PRs.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Top 5 Commands \/ Actions<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Command \/ Action<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td>npx renovate-config-validator<\/td><td>Validate Renovate configuration.<\/td><\/tr><tr><td>npx renovate &#8211;help<\/td><td>Show self-hosted CLI options.<\/td><\/tr><tr><td>Dependency Dashboard<\/td><td>See pending\/blocked dependency updates.<\/td><\/tr><tr><td>Update PR<\/td><td>Review Terraform\/provider\/module\/action changes.<\/td><\/tr><tr><td>Automerge rule<\/td><td>Automatically merge only low-risk updates after required checks.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Student Exercise<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td>{<br>&nbsp; &#8220;extends&#8221;: [&#8220;config:recommended&#8221;],<br>&nbsp; &#8220;labels&#8221;: [&#8220;dependencies&#8221;],<br>&nbsp; &#8220;packageRules&#8221;: [<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8220;matchUpdateTypes&#8221;: [&#8220;major&#8221;],<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8220;dependencyDashboardApproval&#8221;: true<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp; ]<br>}<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">1.&nbsp; Create renovate.json using the sample below.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2.&nbsp; Run npx renovate-config-validator.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3.&nbsp; Fix any validation errors.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">4.&nbsp; In a training repository with Renovate enabled, locate the Dependency Dashboard or an update PR.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">5.&nbsp; Explain why major provider\/Terraform upgrades should usually require explicit review.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PASS &#8211;<\/strong> Expected: The configuration validates and students understand how dependency-update PRs enter the normal CI\/review workflow. Pass when: You can identify which Terraform dependencies Renovate can keep current and state an appropriate major-upgrade policy.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Lab 19 &#8211; tfupdate<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Update Terraform\/provider\/module version constraints from the command line.<\/em><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Priority:<\/strong> 3\/5<\/td><td><strong>Lab mode:<\/strong> Hands-on &#8211; niche helper<\/td><td><strong>Outcome:<\/strong> Run + verify<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">5W1H Quick View<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>What?<\/strong><\/td><td>tfupdate rewrites Terraform\/OpenTofu core, provider and module version constraints and can update lock files.<\/td><\/tr><tr><td><strong>Why?<\/strong><\/td><td>It is useful for scripted bulk version edits across many Terraform directories.<\/td><\/tr><tr><td><strong>When?<\/strong><\/td><td>Use for focused command-line upgrade automation; Renovate is usually broader for continuous dependency PRs.<\/td><\/tr><tr><td><strong>Install?<\/strong><\/td><td>macOS\/Homebrew: brew install minamijoyo\/tfupdate\/tfupdate<\/td><\/tr><tr><td><strong>How?<\/strong><\/td><td>Point tfupdate at a file\/directory and choose the dependency\/version constraint to rewrite.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Top 5 Commands \/ Actions<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Command \/ Action<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td>tfupdate &#8211;help<\/td><td>List commands.<\/td><\/tr><tr><td>tfupdate terraform -v &#8220;~&gt; 1.16.0&#8221; versions.tf<\/td><td>Update Terraform required_version.<\/td><\/tr><tr><td>tfupdate provider random -v &#8220;~&gt; 3.7&#8221; versions.tf<\/td><td>Update a provider constraint.<\/td><\/tr><tr><td>tfupdate module -v &lt;ver&gt; &lt;source&gt; &lt;path&gt;<\/td><td>Update a module constraint.<\/td><\/tr><tr><td>tfupdate lock .<\/td><td>Update dependency lock information where applicable.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Student Exercise<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">1.&nbsp; Copy versions.tf to versions.tf.bak.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2.&nbsp; Run tfupdate terraform with an instructor-selected compatible constraint.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3.&nbsp; Run git diff versions.tf.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">4.&nbsp; Run terraform init -upgrade only if the instructor asks you to test dependency resolution.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">5.&nbsp; Restore versions.tf if the change was only for demonstration.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PASS &#8211;<\/strong> Expected: tfupdate changes only the intended version constraint and the diff is easy to review. Pass when: You can explain when tfupdate is useful and why Renovate is normally the broader long-term dependency workflow.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Lab 20 &#8211; Terraform MCP Server<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Give AI coding agents live Terraform Registry\/provider\/module\/policy context.<\/em><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Priority:<\/strong> 5\/5<\/td><td><strong>Lab mode:<\/strong> Hands-on with Docker + AI client<\/td><td><strong>Outcome:<\/strong> Run + verify<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">5W1H Quick View<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>What?<\/strong><\/td><td>HashiCorp&#8217;s MCP server exposes current Terraform Registry information to MCP-capable AI tools and can integrate with HCP Terraform\/TFE.<\/td><\/tr><tr><td><strong>Why?<\/strong><\/td><td>AI models can hallucinate or use outdated provider\/module attributes; MCP grounds them in current Terraform data.<\/td><\/tr><tr><td><strong>When?<\/strong><\/td><td>Use when approved AI coding assistants generate or explain Terraform.<\/td><\/tr><tr><td><strong>Install?<\/strong><\/td><td>HashiCorp recommends Docker for getting started: hashicorp\/terraform-mcp-server. A precompiled binary or Go install is also available.<\/td><\/tr><tr><td><strong>How?<\/strong><\/td><td>Configure the AI client to start the server over stdio, then ask Terraform questions that require current Registry context.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Top 5 Commands \/ Actions<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Command \/ Action<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td>docker pull hashicorp\/terraform-mcp-server<\/td><td>Fetch the local MCP server image.<\/td><\/tr><tr><td>docker run -i &#8211;rm hashicorp\/terraform-mcp-server<\/td><td>Run the container over stdio for a client.<\/td><\/tr><tr><td>go install github.com\/hashicorp\/terraform-mcp-server\/cmd\/terraform-mcp-server@latest<\/td><td>Alternative source install.<\/td><\/tr><tr><td>which terraform-mcp-server<\/td><td>Find the locally installed binary.<\/td><\/tr><tr><td>terraform-mcp-server stdio<\/td><td>Run the binary in stdio transport mode.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Student Exercise<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td>{<br>&nbsp; &#8220;mcpServers&#8221;: {<br>&nbsp;&nbsp;&nbsp; &#8220;terraform&#8221;: {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8220;command&#8221;: &#8220;docker&#8221;,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8220;args&#8221;: [&#8220;run&#8221;, &#8220;-i&#8221;, &#8220;&#8211;rm&#8221;, &#8220;hashicorp\/terraform-mcp-server&#8221;]<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp; }<br>}<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">1.&nbsp; Confirm Docker is available.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2.&nbsp; Pull the Terraform MCP server image.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">3.&nbsp; Add the minimal MCP configuration below to your instructor-approved MCP client.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">4.&nbsp; Restart\/refresh the client and confirm the terraform MCP server is connected.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">5.&nbsp; Ask: &#8220;Using current Registry information, explain the required arguments for a Terraform provider resource selected by the instructor.&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PASS &#8211;<\/strong> Expected: The AI client can call Terraform MCP tools and ground answers in current Registry information. Pass when: You can explain that MCP improves context but does not replace fmt, validate, lint, security scan, test, plan or human review.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Capstone &#8211; Run the Complete Local Quality Chain<\/h1>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>OBJECTIVE<\/strong><\/td><td>Use the tools as one workflow rather than isolated utilities. This is the key lesson of the course.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Run from the lab root<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td>tenv tf detect<br>terraform fmt -recursive<br>terraform init<br>terraform validate<br>terraform test<br>tflint &#8211;init<br>tflint<br>trivy config .<br>terraform-docs markdown table &#8211;output-file README.md &#8211;output-mode inject .<br>infracost scan .\/cost-demo<br>infracost inspect &#8211;summary<br>pre-commit run &#8211;all-files<br>git status<br>git diff<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Student explanation challenge<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Stage<\/strong><\/td><td><strong>Question<\/strong><\/td><td><strong>Expected idea<\/strong><\/td><\/tr><tr><td>Format<\/td><td>What does fmt prove?<\/td><td>Only canonical formatting.<\/td><\/tr><tr><td>Validate<\/td><td>What does validate prove?<\/td><td>Terraform configuration validity, not security or cloud success.<\/td><\/tr><tr><td>Lint<\/td><td>Why TFLint after validate?<\/td><td>Additional Terraform\/provider quality checks.<\/td><\/tr><tr><td>Security<\/td><td>Why Trivy\/Checkov?<\/td><td>Valid code can still be insecure.<\/td><\/tr><tr><td>Test<\/td><td>Why terraform test?<\/td><td>Assert intended behavior\/contracts.<\/td><\/tr><tr><td>Docs<\/td><td>Why generate docs?<\/td><td>Keep module interface reference synchronized.<\/td><\/tr><tr><td>Cost<\/td><td>Why Infracost?<\/td><td>Review spend before deployment.<\/td><\/tr><tr><td>Automation<\/td><td>Why pre-commit + CI?<\/td><td>Fast local feedback + authoritative central enforcement.<\/td><\/tr><tr><td>Governance<\/td><td>Why HCP\/Policy?<\/td><td>Controlled state\/runs\/RBAC\/policy for teams.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">Student Completion Checklist<\/h1>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>#<\/strong><\/td><td colspan=\"2\"><strong>Tool<\/strong><\/td><td><strong>Evidence<\/strong><\/td><td colspan=\"2\"><strong>Pass<\/strong><\/td><\/tr><\/thead><tbody><tr><td>1<\/td><td colspan=\"2\">VS Code + HashiCorp Terraform Extension<\/td><td>You can demonstrate autocomplete + Format Document + Go to Definition without using a terminal command.<\/td><td colspan=\"2\">\u2610<\/td><\/tr><tr><td>2<\/td><td colspan=\"2\">terraform-ls<\/td><td>You can explain that terraform-ls is a background service, not a replacement for terraform CLI.<\/td><td colspan=\"2\">\u2610<\/td><\/tr><tr><td>3<\/td><td colspan=\"2\">TFLint<\/td><td>tflint &#8211;init succeeds and tflint runs against the lab directory.<\/td><td colspan=\"2\">\u2610<\/td><\/tr><tr><td>4<\/td><td colspan=\"2\">Trivy<\/td><td>You can identify the insecure setting and explain the remediation: restrict the CIDR\/remove public SSH.<\/td><td colspan=\"2\">\u2610<\/td><\/tr><tr><td>5<\/td><td colspan=\"2\">terraform-docs<\/td><td>Changing Terraform input\/output metadata and rerunning terraform-docs updates README without manual table editing.<\/td><td colspan=\"2\">\u2610<\/td><\/tr><tr><td>6<\/td><td colspan=\"2\">pre-commit-terraform<\/td><td>pre-commit run &#8211;all-files finishes successfully after you resolve findings.<\/td><td colspan=\"2\">\u2610<\/td><\/tr><tr><td>7<\/td><td colspan=\"2\">Infracost<\/td><td>You can explain the cost difference between two instance sizes without running terraform apply.<\/td><td colspan=\"2\">\u2610<\/td><\/tr><tr><td>8<\/td><td colspan=\"2\">tenv<\/td><td>tenv tf detect and terraform version agree on an allowed Terraform release.<\/td><td colspan=\"2\">\u2610<\/td><\/tr><tr><td>9<\/td><td colspan=\"2\">Terragrunt<\/td><td>You can explain one real problem Terragrunt solves and one reason a simple Terraform-only repo may not need it.<\/td><td colspan=\"2\">\u2610<\/td><\/tr><tr><td>10<\/td><td colspan=\"2\">terraform test<\/td><td>You can intentionally make the test fail, explain why, fix it, and return to a passing test suite.<\/td><td colspan=\"2\">\u2610<\/td><\/tr><tr><td>11<\/td><td colspan=\"2\">terraform console<\/td><td>You can explain at least one expression result and recreate it in Terraform code if needed.<\/td><td colspan=\"2\">\u2610<\/td><\/tr><tr><td>12<\/td><td colspan=\"2\">Checkov<\/td><td>You can explain why running two scanners with identical findings may add noise rather than value.<\/td><td colspan=\"2\">\u2610<\/td><\/tr><tr><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">Student Completion Checklist &#8211; Continued<\/h1>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>#<\/strong><\/td><td><strong>Tool<\/strong><\/td><td><strong>Evidence<\/strong><\/td><td><strong>Pass<\/strong><\/td><\/tr><\/thead><tbody><tr><td>13<\/td><td>Conftest \/ OPA<\/td><td>You can change only input data and observe policy behavior without changing the Rego rule.<\/td><td>\u2610<\/td><\/tr><tr><td>14<\/td><td>Sentinel<\/td><td>You can make a policy pass\/fail locally and explain advisory versus blocking enforcement conceptually.<\/td><td>\u2610<\/td><\/tr><tr><td>15<\/td><td>HCP Terraform<\/td><td>You can explain why HCP Terraform workspaces are deployment\/state\/RBAC boundaries, not merely local CLI workspaces.<\/td><td>\u2610<\/td><\/tr><tr><td>16<\/td><td>GitHub Actions \/ GitLab CI<\/td><td>CI fails when formatting\/tests fail and passes after the issue is fixed.<\/td><td>\u2610<\/td><\/tr><tr><td>17<\/td><td>Atlantis<\/td><td>You can explain how Atlantis differs from generic CI and from HCP Terraform.<\/td><td>\u2610<\/td><\/tr><tr><td>18<\/td><td>Renovate<\/td><td>You can identify which Terraform dependencies Renovate can keep current and state an appropriate major-upgrade policy.<\/td><td>\u2610<\/td><\/tr><tr><td>19<\/td><td>tfupdate<\/td><td>You can explain when tfupdate is useful and why Renovate is normally the broader long-term dependency workflow.<\/td><td>\u2610<\/td><\/tr><tr><td>20<\/td><td>Terraform MCP Server<\/td><td>You can explain that MCP improves context but does not replace fmt, validate, lint, security scan, test, plan or human review.<\/td><td>\u2610<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">One-Page Tool Decision Guide<\/h1>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Question<\/strong><\/td><td><strong>Primary tool<\/strong><\/td><td><strong>Companion<\/strong><\/td><td><strong>Do not confuse with<\/strong><\/td><\/tr><tr><td>Is the code formatted?<\/td><td>terraform fmt<\/td><td>pre-commit<\/td><td>Validation<\/td><\/tr><tr><td>Is Terraform configuration valid?<\/td><td>terraform validate<\/td><td>CI<\/td><td>Security scanning<\/td><\/tr><tr><td>Any lint\/provider quality issue?<\/td><td>TFLint<\/td><td>terraform validate<\/td><td>Security policy<\/td><\/tr><tr><td>Any IaC security issue?<\/td><td>Trivy<\/td><td>Checkov if needed<\/td><td>Linting<\/td><\/tr><tr><td>Does the module behave as intended?<\/td><td>terraform test<\/td><td>Terratest later if needed<\/td><td>validate<\/td><\/tr><tr><td>Are docs current?<\/td><td>terraform-docs<\/td><td>pre-commit<\/td><td>Manual interface tables<\/td><\/tr><tr><td>What will it cost?<\/td><td>Infracost<\/td><td>FinOps review<\/td><td>Cloud invoice<\/td><\/tr><tr><td>Which Terraform version?<\/td><td>tenv<\/td><td>required_version<\/td><td>Provider version<\/td><\/tr><tr><td>Need multi-env orchestration?<\/td><td>Terragrunt if justified<\/td><td>HCP Terraform<\/td><td>Mandatory Terraform layer<\/td><\/tr><tr><td>Need custom policy?<\/td><td>OPA\/Conftest or Sentinel<\/td><td>HCP policy framework<\/td><td>Security scanner only<\/td><\/tr><tr><td>Need centralized runs\/state?<\/td><td>HCP Terraform<\/td><td>CI<\/td><td>Local-only workflow<\/td><\/tr><tr><td>Need PR-comment Terraform?<\/td><td>Atlantis<\/td><td>CI<\/td><td>HCP Terraform platform<\/td><\/tr><tr><td>Need dependency updates?<\/td><td>Renovate<\/td><td>tfupdate<\/td><td>terraform init -upgrade everywhere<\/td><\/tr><tr><td>Need current AI Terraform context?<\/td><td>Terraform MCP Server<\/td><td>Registry docs<\/td><td>Blind AI generation<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">Official Reference Sources<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">The command examples and recommendations in this manual were checked against current official\/vendor documentation on 5 September 2026. Always verify the installed tool version with &#8211;version \/ &#8211;help in long-lived training material.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Terraform CLI and test:<\/strong> https:\/\/developer.hashicorp.com\/terraform\/<\/li>\n\n\n\n<li><strong>VS Code Terraform Extension:<\/strong> https:\/\/marketplace.visualstudio.com\/items?itemName=HashiCorp.terraform<\/li>\n\n\n\n<li><strong>terraform-ls:<\/strong> https:\/\/github.com\/hashicorp\/terraform-ls<\/li>\n\n\n\n<li><strong>Terraform MCP Server:<\/strong> https:\/\/developer.hashicorp.com\/terraform\/mcp-server<\/li>\n\n\n\n<li><strong>tenv:<\/strong> https:\/\/github.com\/tofuutils\/tenv<\/li>\n\n\n\n<li><strong>TFLint:<\/strong> https:\/\/github.com\/terraform-linters\/tflint<\/li>\n\n\n\n<li><strong>Trivy Terraform scanning:<\/strong> https:\/\/trivy.dev\/docs\/<\/li>\n\n\n\n<li><strong>terraform-docs:<\/strong> https:\/\/terraform-docs.io\/<\/li>\n\n\n\n<li><strong>pre-commit-terraform:<\/strong> https:\/\/github.com\/antonbabenko\/pre-commit-terraform<\/li>\n\n\n\n<li><strong>Infracost:<\/strong> https:\/\/www.infracost.io\/docs\/<\/li>\n\n\n\n<li><strong>Terragrunt:<\/strong> https:\/\/terragrunt.gruntwork.io\/<\/li>\n\n\n\n<li><strong>Checkov:<\/strong> https:\/\/www.checkov.io\/<\/li>\n\n\n\n<li><strong>OPA:<\/strong> https:\/\/www.openpolicyagent.org\/docs\/<\/li>\n\n\n\n<li><strong>Conftest:<\/strong> https:\/\/www.conftest.dev\/<\/li>\n\n\n\n<li><strong>Sentinel:<\/strong> https:\/\/developer.hashicorp.com\/sentinel<\/li>\n\n\n\n<li><strong>HCP Terraform:<\/strong> https:\/\/developer.hashicorp.com\/terraform\/cloud-docs<\/li>\n\n\n\n<li><strong>Atlantis:<\/strong> https:\/\/www.runatlantis.io\/<\/li>\n\n\n\n<li><strong>Renovate:<\/strong> https:\/\/docs.renovatebot.com\/<\/li>\n\n\n\n<li><strong>tfupdate:<\/strong> https:\/\/github.com\/minamijoyo\/tfupdate<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>FINAL<\/strong><\/td><td>The goal is not to install every tool in production. The goal is to know which problem each tool solves, avoid duplicate tooling, and build a clear Terraform engineering workflow.<\/td><\/tr><\/tbody><\/table><\/figure>\n","protected":false},"excerpt":{"rendered":"<p>DOWNLOAD &#8211; HERE Write -&gt; Validate -&gt; Test -&gt; Lint -&gt; Secure -&gt; Document -&gt; Cost -&gt; Automate -&gt; Govern Framework What | Why | When |&#8230; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1158","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/posts\/1158","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/comments?post=1158"}],"version-history":[{"count":2,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/posts\/1158\/revisions"}],"predecessor-version":[{"id":1161,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/posts\/1158\/revisions\/1161"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/media?parent=1158"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/categories?post=1158"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/tags?post=1158"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}