# Terraform IaC (State, Modules, Providers, Workspaces & Lifecycle)

> Advanced Infrastructure as Code (IaC) mastery for HashiCorp Terraform Associate & Professional covering HCL syntax, the core Terraform workflow, remote state backends and locking, state manipulation and import, count vs for_each, dynamic blocks, variable precedence, lifecycle meta-arguments, modules, workspaces, and drift detection.

- **Canonical URL:** https://flashcards.world/flashcards/sets/8bc6ec2b-01fc-42bb-a124-844111af5385/
- **Markdown URL:** https://flashcards.world/flashcards/sets/8bc6ec2b-01fc-42bb-a124-844111af5385.md
- **Level:** advanced
- **Languages:** en → en
- **Total Cards:** 32

## Flashcards (32 Cards)

| # | Front (Term / Prompt) | Back (Definition / Answer) | Example | Multiple-Choice Options |
| :--- | :--- | :--- | :--- | :--- |
| 1 | Compare Terraform CLI Workspaces (`terraform workspace new/select`) with Directory-per-Environment structures for managing `dev`, `staging`, and `prod`. | CLI Workspaces (`terraform.workspace` interpolation) use the exact same HCL directory and backend configuration while storing separate state files under `env:/<workspace>/` (S3) or state prefixes; useful for ephemeral feature-branch environments, but risky for strict `prod` vs `dev` separation because all workspaces share the same backend credentials/access and a developer on the wrong CLI workspace can accidentally modify `prod`. Directory-per-Environment (`envs/dev/`, `envs/prod/` calling versioned shared modules) provides completely isolated backends, IAM credentials, and blast radiuses for production. |  |  |
| 2 | Compare the `count` and `for_each` meta-arguments in Terraform, and explain why `for_each` is almost always safer for collections of resources. | `count` creates resources indexed by numeric list position (`aws_subnet.private[0]`, `aws_subnet.private[1]`, `aws_subnet.private[2]`). If you remove the first item from a list passed to `count`, every subsequent item shifts down an index (`[1]` becomes `[0]`, `[2]` becomes `[1]`), causing Terraform to DESTROY and RECREATE all remaining resources! `for_each` accepts a `map` or `set(string)` and indexes resources by stable string key (`aws_subnet.private["us-east-1a"]`), so removing one key only destroys that single resource. |  |  |
| 3 | How do you configure multiple instances of the same Provider in Terraform (e.g., deploying resources across both `us-east-1` and `us-west-2` in AWS)? | Define a default `provider "aws" { region = "us-east-1" }` block, and a second `provider "aws" { alias = "west" \n region = "us-west-2" }` block using the `alias` meta-argument. On any resource or data source that should be created in `us-west-2`, specify `provider = aws.west`. When passing aliased providers into a Child Module, declare `configuration_aliases = [aws.replica]` inside the child module's `required_providers` block and pass `providers = { aws.replica = aws.west }` in the parent's `module` block. |  |  |
| 4 | What is the exact order of Variable Precedence in Terraform from lowest priority to highest priority (winner)? | From lowest to highest precedence: 1. `default` value inside the `variable` block; 2. Environment variables (`TF_VAR_<name>`); 3. The `terraform.tfvars` file (if present); 4. The `terraform.tfvars.json` file; 5. Any `*.auto.tfvars` or `*.auto.tfvars.json` files (processed in lexical alphabetical order); 6. Command-line `-var` and `-var-file` flags passed on the CLI (processed in the order provided; highest precedence!). |  |  |
| 5 | How does marking a variable or output with `sensitive = true` protect secrets in Terraform, and what does it NOT protect? | Setting `sensitive = true` (or using the `sensitive()` function) redacts the value from appearing in `terraform plan` and `terraform apply` console/CLI log output (`(sensitive value)`), and Terraform requires any output derived from a sensitive variable to also be marked `sensitive = true`. CRITICAL: `sensitive = true` does NOT encrypt or hide the secret inside `terraform.tfstate`! The raw secret value is still written in plain text in the state JSON file (though ephemeral values/write-only attributes in Terraform 1.10+ help avoid storing ephemeral credentials in state). |  |  |
| 6 | What environment variables are used to debug Terraform crashes or inspect provider HTTP API calls? | Set `TF_LOG` to `TRACE` (most verbose, shows internal DAG steps and redacted HTTP requests/responses), `DEBUG`, `INFO`, `WARN`, or `ERROR` (or scope exclusively to core vs provider via `TF_LOG_CORE=TRACE` or `TF_LOG_PROVIDER=TRACE`). Set `TF_LOG_PATH=/tmp/terraform-debug.log` to persist the diagnostic output to a file on disk. |  |  |
| 7 | Compare Policy-as-Code tools for Terraform: HashiCorp Sentinel, Open Policy Agent (OPA / Rego), and static scanners (`tfsec`/`Trivy`/`Checkov`). | Static HCL Scanners (`Trivy`, `tfsec`, `Checkov`): Scan `.tf` source files or `terraform plan` JSON locally in CI/CD before apply for misconfigurations (e.g., public S3 buckets, unencrypted disks). Open Policy Agent (OPA): CNCF open-source policy engine using the declarative `Rego` language to evaluate `terraform show -json tfplan.binary` output in any CI pipeline. HashiCorp Sentinel: Proprietary policy-as-code framework integrated into HCP Terraform / Terraform Enterprise, evaluated between `plan` and `apply` with `advisory`, `soft-mandatory`, and `hard-mandatory` enforcement levels. |  |  |
| 8 | What does `terraform state rm <address>` (or the `removed` block in Terraform 1.7+) do? | It removes ('forgets') the resource from Terraform's state file (`terraform.tfstate`) so Terraform stops managing it, WITHOUT destroying the actual physical resource in the cloud! Remember to also remove the corresponding `resource` block from your `.tf` files (or use `removed { from = ... lifecycle { destroy = false } }` in Terraform 1.7+). |  |  |
| 9 | What are `dynamic` blocks in Terraform and when should they be used vs. avoided? | `dynamic` blocks generate repeatable nested configuration blocks INSIDE a resource (such as multiple `ingress` rule blocks inside an `aws_security_group` or `setting` blocks in an App Service) using `for_each` and a `content { ... }` body (referencing the iterator via `<block_name>.key` and `<block_name>.value`). Overusing `dynamic` blocks makes HCL hard to read and maintain; use them only when repeating nested blocks dynamically based on input variables. |  |  |
| 10 | What happens if a Terraform run crashes mid-apply and leaves a Remote State Lock stuck, and what precautions apply to `terraform force-unlock`? | If a CI runner dies abruptly, the remote backend lock (e.g., DynamoDB lock item or Azure Blob lease) may remain locked, blocking subsequent runs. After verifying with 100% certainty that no other person or CI job is actively running `plan`/`apply` against that state, run `terraform force-unlock <LOCK_ID>` using the unique Lock ID printed in the error output. |  |  |
| 11 | Why does HashiCorp consider Provisioners (`local-exec` and `remote-exec`) a 'Last Resort', and what are the better alternatives? | `local-exec` runs a shell command on the machine running Terraform; `remote-exec` SSHs/WinRMs into a newly created VM to run scripts. They are a 'last resort' because Terraform cannot model or preview what imperative shell scripts will do during `terraform plan`, cannot detect configuration drift on files changed by scripts, and `remote-exec` requires opening SSH/WinRM ports and managing keys. Alternatives: Use `cloud-init` / `user_data` startup scripts, golden immutable VM images built with HashiCorp Packer, or `terraform_data` (which replaced `null_resource` in Terraform 1.4+). |  |  |
| 12 | How does native `terraform test` (introduced in Terraform 1.6+) work for testing modules? | `terraform test` executes `.tftest.hcl` files located in a `tests/` directory. Each test file defines one or more `run "<test_name>"` blocks with `command = plan` (fast unit test validating input validations, locals, and planned resource attributes without creating cloud resources) or `command = apply` (integration test provisioning real ephemeral sandbox resources and automatically destroying them at the end of the test suite), verified via `assert { condition = ... error_message = ... }` blocks. |  |  |
| 13 | Explain how `cidrsubnet(prefix, newbits, netnum)` calculates subnet CIDRs in Terraform with a concrete example. | `cidrsubnet(prefix, newbits, netnum)` takes a base CIDR `prefix`, adds `newbits` to the prefix length, and selects subnet index `netnum` (0-indexed). For example, `cidrsubnet("10.10.0.0/16", 8, 5)`: `16 + 8 = /24` subnet mask, and the 8 new bits are placed in the 3rd octet with value `5`, yielding `"10.10.5.0/24"`. In `cidrsubnet("10.0.0.0/20", 4, 3)`, `20 + 4 = /24` (block size of 16 in 3rd octet: `3 * 16 = 48`), yielding `"10.0.48.0/24"`. |  |  |
| 14 | What does `terraform apply -target=<resource_address>` do, and why is it discouraged in routine workflows? | `-target` tells Terraform to focus its plan and apply strictly on the specified resource (or module) and its upstream dependencies, ignoring the rest of the configuration. It is discouraged for daily use because your `.tfstate` and live infrastructure become out of sync with the full `.tf` codebase in Git, potentially bypassing downstream dependency updates; use it only for emergency troubleshooting or decomposing stuck circular deletions. |  |  |
| 15 | What is the purpose of `.terraform.lock.hcl` (the Dependency Lock File) and should it be committed to Git? | Created and updated by `terraform init`, `.terraform.lock.hcl` records the exact version numbers and cryptographic SHA-256 hashes of every Provider plugin selected for the project. YES, `.terraform.lock.hcl` MUST be committed to version control (Git) so that every developer and CI/CD pipeline runs the exact same provider binary ( whereas the `.terraform/` directory and `*.tfstate` files must be ignored in `.gitignore`). Run `terraform init -upgrade` to intentionally update provider versions within constraints. |  |  |
| 16 | How do you write an idiomatic conditional resource creation (`0` or `1` instance) in Terraform? | Use `count` with a ternary expression: `count = var.enable_nat_gateway ? 1 : 0`. When `var.enable_nat_gateway` is `false`, `count` evaluates to `0` and the resource is not created. Because `count` turns the resource into a list of length 0 or 1, reference its attributes elsewhere using index `[0]` or the splat expression `one(aws_nat_gateway.this[*].id)`. |  |  |
| 17 | How do `precondition` and `postcondition` blocks inside a resource's `lifecycle` block (plus `check` blocks) improve infrastructure reliability? | Introduced in Terraform 1.2+, `precondition` validates assumptions BEFORE Terraform creates/updates a resource (e.g., verifying an AMI is `x86_64`), while `postcondition` validates guarantees AFTER a resource is created/read using `self.<attr>` (e.g., asserting `self.encrypted == true`). If either fails, Terraform halts execution with your custom `error_message`. Standalone `check` blocks (Terraform 1.5+) run continuous assertions (including scoped data sources like HTTP health checks) at the end of `plan`/`apply` producing warnings without blocking provisioning. |  |  |
| 18 | What is the difference between Implicit Dependencies and Explicit Dependencies (`depends_on`) in Terraform's Directed Acyclic Graph (DAG)? | Implicit Dependency (preferred): When Resource B references an attribute of Resource A (e.g., `subnet_id = aws_subnet.main.id`), Terraform automatically infers that A must be created before B (and B destroyed before A). Explicit Dependency (`depends_on = [aws_iam_role_policy.app]`): Used ONLY when there is a hidden runtime dependency between two resources that do not reference each other's attributes in HCL (for example, an application pod/Lambda needing an IAM policy attachment or S3 bucket policy to finish propagating before the app starts). |  |  |
| 19 | Why was `terraform taint` deprecated in Terraform v0.15.2+, and what command replaced it? | `terraform taint <address>` modified the remote state file directly to mark a resource as tainted, meaning another teammate running `terraform plan` in the meantime could accidentally trigger the replacement before you were ready. It was replaced by `terraform apply -replace="aws_instance.web[0]"` (or `terraform plan -replace=...`), which includes the replacement directly inside a single atomic plan/apply workflow. |  |  |
| 20 | How do `data` sources work in Terraform, and how does `terraform_remote_state` share data between separate Terraform configurations? | A `data` block (`data "aws_ami" "ubuntu" { ... }`) is a read-only query that fetches information about existing infrastructure or cloud provider metadata during the `Read`/`Plan` phase without managing its lifecycle. `data "terraform_remote_state" "vpc"` reads the root-level `output` values directly from another Terraform configuration's remote state backend (alternatively, teams often publish outputs to SSM Parameter Store / Key Vault / Consul and read them via standard provider `data` sources to decouple state files). |  |  |
| 21 | Why does Terraform require a State file (`terraform.tfstate`), and why must Remote State with State Locking be used in team environments? | Terraform State maps declarative HCL resource blocks (`aws_instance.web`) to real-world cloud resource IDs (`i-0abcd1234`), tracks resource dependency metadata, and caches attribute values for performance. Storing state locally fails in teams because: 1. Concurrent `terraform apply` runs corrupt state without State Locking; 2. State lacks shared visibility; 3. State files contain sensitive plaintext secrets! Remote backends (e.g., AWS S3 + DynamoDB lock / S3 native lock, Azure Blob Storage with native lease locking, GCS with native object locking, HCP Terraform) provide encryption at rest, shared access, and atomic locking. |  |  |
| 22 | Compare Input `variable` blocks, `locals` blocks, and `output` blocks in Terraform. | Input `variable` blocks act like function parameters passed into a module from outside (`type`, `default`, `description`, `validation`, `sensitive`, `nullable`). `locals` blocks act like private local constants/expressions computed inside a module to avoid repeating DRY expressions (cannot be overridden directly from CLI flags). `output` blocks act like function return values, exposing selected resource attributes to the CLI after `apply`, to a parent module (`module.<name>.<output>`), or to `terraform_remote_state`. |  |  |
| 23 | How do you rename a resource or move it into a child module in Terraform without destroying and recreating the real cloud resource? | If you simply rename `resource "aws_db_instance" "old"` to `"new"` in HCL, Terraform plans to DESTROY `old` and CREATE `new`! To refactor safely with zero downtime, either: 1. Add a declarative `moved { from = aws_db_instance.old \n to = aws_db_instance.new }` block in HCL (preferred for shared GitOps/CI pipelines and reusable modules), OR 2. Run `terraform state mv aws_db_instance.old aws_db_instance.new` on the CLI. |  |  |
| 24 | How has the built-in `terraform_data` resource (introduced in Terraform 1.4) replaced the legacy `null_resource` provider? | Previously, engineers had to install the external `hashicorp/null` provider and use `null_resource` with `triggers = { ... }` to run lifecycle hooks or hold arbitrary state replacements. `terraform_data` is built directly into Terraform core (no external provider download required) and provides `triggers_replace` (to force replacement when values change, usable with `replace_triggered_by` on other resources) and `input`/`output` attributes stored in state. |  |  |
| 25 | Explain the four `lifecycle` meta-arguments in Terraform: `create_before_destroy`, `prevent_destroy`, `ignore_changes`, and `replace_triggered_by`. | 1. `create_before_destroy = true`: Reverses Terraform's default replacement order when a resource must be recreated (`+/-` instead of `-/+`), provisioning the replacement resource BEFORE destroying the old one (essential for zero-downtime Launch Templates, TLS certificates, or API Gateway deployments). 2. `prevent_destroy = true`: Causes Terraform to error out and reject any plan that would destroy the resource (so long as the block remains in HCL). 3. `ignore_changes = [tags, ami]`: Tells Terraform to ignore external drift on specified attributes (e.g., autoscaling `desired_capacity` or Azure/AWS auto-tags). 4. `replace_triggered_by`: Forces replacement when a referenced resource/attribute changes. |  |  |
| 26 | Compare `terraform plan -refresh-only` and `terraform plan -refresh=false` for handling Configuration Drift. | `terraform plan -refresh-only` (which replaced the deprecated `terraform refresh` command because `refresh` modified state without showing a confirmation prompt!) queries the real cloud APIs and shows a diff of any manual out-of-band changes made outside Terraform ('drift'), letting you review and optionally accept those real-world changes into `.tfstate`. `terraform plan -refresh=false` skips querying cloud APIs during `plan` and relies purely on cached `.tfstate` values (useful for speeding up plans across thousands of resources). |  |  |
| 27 | How does a Parent Module access an attribute of a resource created inside a Child Module? | Encapsulation is strictly enforced in Terraform: a parent module CANNOT directly reference `module.network.aws_vpc.main.id`! The child module must explicitly declare an `output "vpc_id" { value = aws_vpc.main.id }` block in its `outputs.tf`, and the parent module then references `module.network.vpc_id`. |  |  |
| 28 | What are the most useful built-in HCL functions and expressions (`for`, splat `[*]`, `lookup`, `try`/`can`, `toset`, `templatefile`, `cidrsubnet`)? | `[for s in var.list : upper(s) if s != ""]` transforms/filters lists or maps; `aws_instance.web[*].private_ip` (splat) extracts an attribute across a list; `try(var.map.key, "default")` and `can(regex(...))` safely evaluate expressions that might error; `toset()` converts a list to an unordered set with duplicates removed (required for `for_each` over lists of strings); `templatefile("${path.module}/init.tftpl", { db_ip = ... })` renders files cleanly; `cidrsubnet("10.0.0.0/16", 8, 2)` calculates `10.0.2.0/24`. |  |  |
| 29 | Explain the five core stages of the Terraform workflow: `Write` -> `init` -> `validate` -> `plan` -> `apply` (`destroy`). | 1. `Write`: Author declarative HCL (`.tf`) configuration files. 2. `terraform init`: Initializes the working directory, downloads required Provider plugins (into `.terraform/providers`), downloads Modules, and initializes the State Backend. 3. `terraform validate`: Statically checks HCL syntax and internal attribute consistency without accessing remote APIs/state. 4. `terraform plan`: Refreshes state against real cloud APIs and computes the execution diff (`+` create, `~` update in-place, `-/+` destroy and recreate). 5. `terraform apply`: Executes the planned changes and updates `terraform.tfstate`. |  |  |
| 30 | How do you bring existing, manually-created cloud infrastructure under Terraform management using `import` blocks (Terraform 1.5+) vs. `terraform import` CLI? | Legacy CLI: First write the `resource` block manually in `.tf`, then run `terraform import aws_s3_bucket.logs my-prod-logs-bucket` (which modifies state immediately without a plan preview). Modern Config-Driven Import (Terraform 1.5+): Add an `import { to = aws_s3_bucket.logs \n id = "my-prod-logs-bucket" }` block in HCL, run `terraform plan -generate-config-out=generated.tf` to auto-generate the HCL resource code, and preview the import safely inside standard CI/CD `plan`/`apply`! |  |  |
| 31 | Why should you always pin Module and Provider versions in production, and what do the version constraint operators `=`, `!=`, `>=`, and `~>` (pessimistic constraint) mean? | Without version pinning, `terraform init` downloads the latest version, which can introduce breaking changes. `~>` is the pessimistic constraint operator allowing only the rightmost version component specified to increment: `~> 5.4` allows `>= 5.4.0` and `< 6.0.0` (any minor or patch release in 5.x), whereas `~> 5.4.1` allows `>= 5.4.1` and `< 5.5.0` (only patch releases in 5.4.x). Note: The `version` argument inside a `module` block works ONLY for modules fetched from a Terraform Registry (for Git sources, pin via `?ref=v1.2.0` in the `source` URL). |  |  |
| 32 | What is the standard file structure of a reusable Terraform Module, and how do Root Modules and Child Modules differ? | The Root Module is the top-level working directory where you run `terraform init/plan/apply`. A Child Module is any module called via a `module "<name>" { source = "..." }` block. Standard module convention: `main.tf` (core resources), `variables.tf` (input variable declarations), `outputs.tf` (output values), `versions.tf` (`terraform { required_version = ... required_providers { ... } }`), `README.md`, and `examples/`. |  |  |

---
Study this deck interactively with FSRS-6 spaced repetition, audio review, and multiple-choice mode at [Flashcards World](https://flashcards.world/flashcards/sets/8bc6ec2b-01fc-42bb-a124-844111af5385/).
