Terraform configuration
A configuration declares desired state, and references wire it into a graph
Terraform configuration is the single largest domain on TA-004, covering eight subtopics, more than any other domain, and it is where you actually write HCL (HashiCorp Configuration Language). The language is declarative: you describe the end state you want block by block, and Terraform works out how to reach it. The unit of that description is the block: a resource or data block declares infrastructure, a variable or output block declares an interface, and a locals block names reusable values. Blocks are wired together by references such as aws_vpc.main.id, var.region, or module.network.subnet_id, and each reference does two jobs at once. It carries a value, and it declares an ordering, because Terraform reads references to build a dependency graph rather than running the file from top to bottom. Hold onto that one model and most of the domain follows: everything here either declares a block, produces or consumes a value, or shapes how those values flow.
The domain unfolds in eight moves, from blocks to guardrails
The eight subtopics build on that model in the order they are numbered. First, resource and data blocks separate the two core block types: a resource block owns an object's full create, update, and destroy lifecycle, while a data block only reads an existing object. Referencing resource attributes then covers how to name a value across blocks, with a fixed prefix per kind (var., local., data., module.) and a bracket selector, [index] for a counted resource or [key] for a keyed one, to reach a single instance. Variables and outputs defines a module's public interface, the inputs it accepts and the results it returns, plus the fixed precedence that resolves a variable's final value. Complex types describes the shapes a value can hold, splitting collection types (list, set, map) from structural types (object, tuple). Expressions and functions shows how to compute a value instead of hard-coding it, through built-in functions, conditionals, for expressions, the splat operator, dynamic blocks, and string templates. Resource dependencies returns to ordering, contrasting the implicit dependencies that references create with the explicit depends_on escape hatch and the lifecycle settings that tune replacement. Custom condition validation adds assertions that catch a bad value early, from variable validation to pre- and postconditions to check blocks. Managing sensitive data closes the domain: sensitive only redacts display, ephemeral values and write-only arguments actually keep a secret out of state, and Vault moves the secret out of the configuration though its fetched value still lands in state.
Prefer the declarative, native construct; the last resort is rarely the answer
When two answers both produce working infrastructure, this domain rewards the one that stays declarative and lets Terraform's own machinery do the work. Prefer an implicit reference over depends_on, for_each over count when instances need stable identity, a built-in function or expression over a provisioner, and a purpose-built secret mechanism over trusting sensitive to protect state. The recurring tell is the phrase last resort: HashiCorp's docs apply it to provisioners and local-exec, and an option the documentation calls a last resort is almost never the intended choice when a native construct exists. The same instinct explains the domain's sharpest traps: sensitive hides a value from the console yet still writes it to state in plain text, and prevent_destroy errors out a destroy plan rather than silently skipping the resource.
The eight configuration concerns and where each is covered
| Configuration concern | What it declares or does | Reach for it when | Drill into |
|---|---|---|---|
| Blocks | `resource` owns an object's lifecycle; `data` only reads one | You declare a piece of infrastructure or look one up | Resource and data blocks |
| References | Name a value across blocks by prefix, and address one instance by index or key | One block needs a value another block produced | Referencing resource attributes |
| Variables and outputs | A module's inputs and results, resolved by a fixed precedence | You parameterize a module or expose a result | Variables and outputs |
| Complex types | Collection types (list, set, map) versus structural types (object, tuple) | A value holds many items or a fixed-shape record | Complex types |
| Expressions and functions | Compute a value with functions, conditionals, for, splat, dynamic blocks, and templates | A value must adapt to its inputs rather than be hard-coded | Expressions and functions |
| Dependencies | Implicit references order the graph; `depends_on` and `lifecycle` tune it | Work must run in a set order, or a replacement needs control | Resource dependencies |
| Validation | Assertions: variable validation, pre/postconditions, and check blocks | You catch a bad value or a broken assumption early | Custom condition validation |
| Sensitive data | `sensitive` redacts display; ephemeral and write-only keep secrets out of state; Vault moves them out of config | A secret must not leak into logs or state | Managing sensitive data |