The TF-Associate-004 blueprint is split across 8 domains. Weights are the official share of the exam; see the official exam guide for the authoritative breakdown.
What you must be able to do. Explain what infrastructure as code is, map an organisational problem to the IaC advantage that solves it, and say why one Terraform workflow spans many providers.
In one sentenceThe why behind Terraform: declarative desired-state configuration under version control, the advantages that follow, and one workflow across multi-cloud and hybrid environments.
Recall check: answer these from memory first
- Give a one-line distinction between declarative desired-state configuration and imperative scripting.
- Name four advantages of infrastructure as code and the organisational problem each one solves.
- Explain in one sentence how Terraform manages multi-cloud and hybrid workflows with a single workflow.
What it tests. The concept of infrastructure as code: defining infrastructure in version-controlled configuration and applying it with a tool rather than by hand, and the difference between declarative desired-state configuration and imperative scripting. It also tests the advantages of IaC, such as repeatability, reviewable change and reduced drift, and how Terraform manages multi-cloud, hybrid and service-agnostic workflows through provider plugins under one constant workflow.
How to study it. Get the declarative versus imperative distinction crisp: Terraform describes the desired end state and reconciles reality toward it, rather than listing steps to run. Then learn the advantages as answers to problems, not a list, so you can match a stated pain (drift, no audit trail, environment sprawl) to the IaC benefit that addresses it. Be ready to explain why one language and workflow across AWS, Azure and on-premises reduces tool sprawl.
Easy to confuse
- Declarative versus imperative infrastructure. Declarative configuration states the desired end result and lets the tool work out the steps and order; imperative scripting lists the commands to run. Terraform is declarative, so a scenario asking for desired-state, reconciled configuration points to Terraform rather than a shell script.
- Multi-cloud versus hybrid cloud. Multi-cloud means using more than one public cloud provider; hybrid cloud combines public cloud with on-premises or private infrastructure. Both are managed by the same Terraform workflow through different providers, and the exam checks you can name which situation a scenario describes.
Worked example from the TF-Associate-004 bank
lock_openFree sampleInfrastructure as Code (IaC) with Terraformeasy
A team has been provisioning identical staging and production environments by clicking through the cloud console by hand, and the two environments keep ending up subtly different. They decide to define the whole environment once as Terraform configuration and apply it to each environment. Which advantage of IaC most directly addresses their problem?
- AFaster raw provisioning speed, because Terraform always creates cloud resources more quickly than the console does.
- BRepeatability, because applying the same configuration produces the same environment every time it is run.check_circle Correct
- CAutomatic cost reduction, because Terraform selects the cheapest resource sizes on the team's behalf.
- DElimination of the need for any cloud provider credentials during provisioning.
Repeatability means applying the same Terraform configuration reliably reproduces the same environment, removing manual inconsistency. Because the environment is expressed as code and Terraform converges real infrastructure to that declared configuration, reapplying the same files yields the same result each time, which is the mechanism that keeps staging and production identical.
Why A is wrong: Speed can improve, but Terraform is not guaranteed to be faster than the console, and speed is not what fixes environments that differ from each other.
Why B is correct: Defining the environment as code and reapplying it gives a repeatable, reproducible build, which is exactly what removes the subtle differences between the two environments.
Why C is wrong: Terraform provisions whatever sizes the configuration specifies and does not choose cheaper options by itself, so this does not address inconsistency.
Why D is wrong: Terraform still authenticates to the provider with credentials to make changes, so this is factually wrong and unrelated to environment consistency.
What you must be able to do. Declare and version providers, explain how a provider plugin works, configure multiple providers with alias, and say what state is for.
In one sentenceThe building blocks: providers declared and pinned in required_providers and the lock file, provider plugins mapping resources onto an API, aliasing multiple providers, and the purpose of state.
Recall check: answer these from memory first
- Say where a provider's source address and version constraint are declared, and what the lock file adds.
- Explain how you configure two provider configurations for two regions and how a resource selects the non-default one.
- State in one line what Terraform state is for and why it must not be casually hand-edited.
What it tests. How Terraform uses providers: the required_providers block declaring each provider's source address and version constraint, terraform init installing them, and the dependency lock file pinning exact versions across machines. It tests writing configuration with multiple providers distinguished by alias and selected through a resource's provider meta-argument, and why Terraform keeps state as the authoritative map of configuration to real resources.
How to study it. Learn where a provider is declared (required_providers inside the terraform block) versus configured (a provider block with credentials and region), because the exam separates the two. Understand version constraint operators and why the lock file pins a provider so every machine and run uses the same version. For multiple providers, practise predicting which configuration a resource uses given an alias. Fix the idea that state is authoritative for what Terraform manages and must not be hand-edited casually.
Easy to confuse
- required_providers block versus provider block. required_providers, inside the terraform block, declares each provider's source and version constraint; a separate provider block configures a declared provider with credentials and settings such as region. The exam tests whether you put versioning where configuration belongs, or the reverse.
- Version constraint versus the dependency lock file. A version constraint in required_providers says which versions are acceptable; the .terraform.lock.hcl lock file records the exact version init selected, so runs are reproducible. The constraint is your intent, the lock file is the pinned result, and only init updates the lock file.
- Provider alias versus a second provider. alias creates an additional configuration of the same provider, for example a second AWS region, and a resource opts in through its provider meta-argument. It is still one provider plugin, not a different provider, which is the distinction the exam draws.
Worked example from the TF-Associate-004 bank
lock_openFree sampleTerraform fundamentalseasy
A practitioner writes a new configuration that declares an aws_s3_bucket resource but never adds a required_providers entry or a provider block for AWS. They run terraform init in the empty working directory. What does Terraform do about the AWS provider?
resource "aws_s3_bucket" "assets" {
bucket = "example-assets-bucket"
}
- AIt infers the provider from the resource type prefix and downloads the hashicorp/aws plugin from the registry during init.check_circle Correct
- BIt fails init immediately because every provider must be pinned in a required_providers block before any plugin can be installed.
- CIt skips provider installation entirely and defers downloading the plugin until the first terraform apply is run.
- DIt prompts the practitioner to type the provider source address interactively before continuing the initialisation.
Terraform infers a provider from a resource type prefix and installs the plugin during init even without an explicit required_providers entry. A resource type such as aws_s3_bucket carries the provider local name as its prefix; Terraform resolves that to the default hashicorp/aws source address and installs the plugin during terraform init, which is the phase responsible for provider installation.
Why A is correct: Terraform maps the aws_ prefix to the aws provider, resolves it to the default hashicorp namespace, and installs the plugin during init even without an explicit required_providers entry.
Why B is wrong: Pinning in required_providers is best practice for version control, but its absence does not stop init; Terraform can still infer and install the provider, so this overstates the requirement.
Why C is wrong: Plugin installation is the job of init, not apply; deferring it to apply would leave the working directory uninitialised, so this misplaces when the download happens.
Why D is wrong: Terraform init is non-interactive for provider resolution and infers the source automatically, so it never pauses to ask the user to type a source address.
What you must be able to do. Place every core command correctly around write, plan, apply, and know exactly which commands change real infrastructure and which do not.
In one sentenceThe core loop: write, plan, apply, with init, validate, fmt and destroy placed correctly around it, and a firm grip on which step actually changes infrastructure.
Recall check: answer these from memory first
- List the three steps of the core workflow and place init, validate, fmt and destroy around them.
- Name every core command that changes real infrastructure, and confirm the rest do not.
- Say when re-running terraform init is required.
What it tests. The core workflow of write, plan, apply and the role of each command around it. It tests terraform init (installs providers, downloads modules, initialises the backend, changes nothing), validate (syntax and consistency, no provider access), fmt (canonical style only), plan (previews create, update, replace and destroy actions and changes nothing), apply (executes the plan and updates state), and destroy (removes state-tracked resources). Reading plan output and knowing a saved plan can be passed to apply are in scope.
How to study it. Build a mental map of the workflow and pin each command to its place and its effect. The single most tested idea is which step changes infrastructure: only apply and destroy do. Practise reading plan symbols for create, update in place, replace and destroy, and know that plan and validate are safe to run anywhere. Learn when init must be re-run (new provider or module, backend change) and what a saved plan file gives you when passed to apply.
Easy to confuse
- terraform init versus terraform apply. init prepares the working directory by installing providers and modules and initialising the backend, and creates no infrastructure; apply executes planned actions and changes real resources. A scenario that provisions anything needs apply, not init, which is the exam's most common workflow trap.
- terraform validate versus terraform plan. validate checks syntax and internal consistency with no provider or state access and needs no credentials; plan contacts providers, refreshes real resources and diffs against state to preview changes. If the question stresses no credentials or offline checking, it is validate.
- terraform plan versus terraform apply. plan previews the actions and writes nothing; apply carries them out and updates state. A saved plan file from plan can be handed to apply so it executes exactly those actions with no re-prompt, but plan on its own never changes anything.
Worked example from the TF-Associate-004 bank
lock_openFree sampleCore Terraform workflowmedium
An engineer runs 'terraform apply' with no extra flags in a directory using the local backend. Terraform prints the proposed plan and then pauses. What must happen before Terraform makes any changes to the real infrastructure?
$ terraform apply
# aws_instance.web will be created
Plan: 1 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
- ATerraform applies the changes immediately because a plan was already generated during the same command.
- BThe engineer must re-run the command with 'terraform apply -refresh-only' to confirm the intended changes.
- CTerraform waits for the engineer to run 'terraform plan' in a second terminal to release the pause.
- DThe engineer must type 'yes' at the interactive approval prompt before Terraform provisions the resources.check_circle Correct
A plain terraform apply presents a plan and requires explicit interactive approval before changing infrastructure. By default terraform apply computes a plan, displays it, and blocks on an interactive confirmation; only after the operator types 'yes' does it call the providers to create, update, or destroy real resources.
Why A is wrong: Tempting because apply does generate its own plan, but without a saved plan file or -auto-approve it still stops and asks for confirmation first.
Why B is wrong: Refresh-only only reconciles state with real infrastructure and proposes no resource changes, so it would never provision the new instance.
Why C is wrong: A separate plan run has no effect on the waiting apply, which is blocked purely on the interactive yes-or-no approval prompt.
Why D is correct: Correct: a bare 'terraform apply' shows the plan and waits for the operator to type 'yes' before it executes any changes.
What you must be able to do. Author configuration with resources, data sources, variables, outputs, complex types, expressions, dependencies, custom conditions, and handle sensitive data correctly.
In one sentenceThe largest domain: resource versus data blocks, variables and outputs with fixed precedence, complex types and expressions, implicit versus explicit dependencies, custom conditions, and sensitive data that is still plaintext in state.
Recall check: answer these from memory first
- State the variable precedence order from highest to lowest.
- Distinguish a resource block from a data source in one line, and an argument from an attribute.
- Explain what marking a value sensitive does and does not do to that value in state.
What it tests. Writing configuration: resource blocks that manage objects versus data blocks that read existing information, referencing attributes to wire values and create implicit dependencies, input variables and outputs including variable precedence and sensitive marking, complex collection and structural types, dynamic configuration with expressions, functions and dynamic blocks, declaring dependencies with depends_on when references cannot express them, custom conditions (variable validation, preconditions, postconditions, check blocks), and managing secrets including sourcing from Vault.
How to study it. This is the heaviest domain, so give it the most time. Memorise variable precedence in order: a -var or -var-file on the command line wins, then a TF_VAR_ environment variable, then terraform.tfvars and auto.tfvars files, then the variable default. Drill resource versus data (manage versus read) and argument versus attribute (set in configuration versus exported after apply). Learn that references create implicit dependencies and depends_on is only for orderings the configuration cannot express. Fix firmly that sensitive hides values from output but state still stores them in plaintext, so protecting state is the real control.
Easy to confuse
- Resource block versus data block. A resource block creates and manages an object over its lifecycle; a data block reads existing information and manages nothing, refreshing during plan to produce read-only attributes. If the scenario needs to look something up rather than own it, it is a data source.
- Implicit dependency versus depends_on. An implicit dependency comes from one resource referencing another's attribute and is the preferred way to order operations; depends_on declares an ordering Terraform cannot infer from references. Over-using depends_on harms parallelism, so it is a last resort, not a default.
- sensitive marking versus protecting state. Marking a variable or output sensitive suppresses it in plan and apply output, but the value is still written to state in plaintext. sensitive is not encryption, so the exam wants protecting the state file, or sourcing secrets from Vault, named as the actual safeguard.
Worked example from the TF-Associate-004 bank
lock_openFree sampleTerraform configurationmedium
A practitioner defines a variable to hold two Availability Zones and iterates over it with for_each to create one subnet per zone. When they run terraform plan, Terraform reports that for_each cannot be used with this value. What change to the variable makes for_each work directly?
variable "azs" {
type = list(string)
default = ["eu-west-1a", "eu-west-1b"]
}
resource "aws_subnet" "this" {
for_each = var.azs
availability_zone = each.value
}
- AChange the variable type to tuple([string, string]), because a fixed-length tuple is the collection for_each expects.
- BChange the variable type to set(string), because for_each accepts a map or a set of strings, not a list.check_circle Correct
- CLeave the type as list(string) and add a lifecycle block with create_before_destroy set to true.
- DLeave the type as list(string) and reference each.key instead of each.value in the resource.
Recognise that for_each accepts a map or a set of strings, so a list must be converted to a set before iterating over it. for_each keys each instance by a stable string, which a map or set of strings provides but a list does not; converting the list to set(string) supplies acceptable keys and lets the iteration proceed.
Why A is wrong: A tuple looks like an ordered pair that might suit two zones, but for_each rejects tuples just as it rejects lists; it needs a map or a set.
Why B is correct: for_each requires a map or a set of strings; converting the list to a set of strings gives it a collection it can key by value, so it works directly.
Why C is wrong: create_before_destroy governs replacement ordering and does nothing about the for_each input type, so the plan would fail with the same error.
Why D is wrong: Swapping each.key for each.value does not change that a raw list is an invalid for_each argument, so Terraform still refuses the list.
What you must be able to do. Source modules from local paths and registries, reason about input and output scope, call modules in configuration, and pin module versions deliberately.
In one sentenceComposing configuration: sourcing modules locally or from a registry, the input-and-output boundary that encapsulates a module, calling modules, and pinning versions so upgrades are deliberate.
Recall check: answer these from memory first
- Name the kinds of source a module block can use and say which step retrieves the module.
- Explain how values get into and out of a module, and why a parent cannot reach a child's resources directly.
- Say which modules the version argument applies to and why pinning matters.
What it tests. How Terraform sources modules through the source argument (local path, public or private registry, Git or HTTP) with init retrieving them, variable scope within modules (a module receives values only through input variables and exposes them only through outputs, so a parent cannot reach a child's internal resources), using module blocks to pass inputs and reference module.name.output including count and for_each on modules, and managing module versions with the version argument for registry modules.
How to study it. Distinguish a local module from a registry module and remember that changing the source argument requires re-running init. Fix the encapsulation model: values flow in through input variables and out through outputs, and nothing else crosses the boundary, which is the whole point of a module. Practise calling a module and referencing its outputs as module.name.output, and know count and for_each apply to module blocks. For versioning, note the version argument is for registry modules and that pinning protects you from an unexpected upstream change.
Easy to confuse
- Local module versus registry module. A local module is sourced by a relative path in the same repository and is not versioned by a registry; a registry module is sourced by a registry address and supports the version argument. Only registry modules take version constraints, which the exam uses to test the difference.
- Module input and output versus reaching inside a module. A module communicates only through its declared input variables and outputs; a parent cannot reference a child module's internal resource attributes directly. If a scenario needs a value out of a module, the module must expose it as an output, which is the encapsulation the exam checks.
Worked example from the TF-Associate-004 bank
lock_openFree sampleTerraform modulesmedium
A practitioner adds a module block that points at a subdirectory of the current project, then runs 'terraform init'. What does Terraform do to retrieve this module?
module "network" {
source = "./modules/network"
}
- AIt queries the public Terraform Registry for a module named 'network' and downloads the newest matching version into the cache.
- BIt reads the module directly from the given relative path on disk without downloading anything, because a source beginning with './' is a local path.check_circle Correct
- CIt clones the path as a Git repository into the '.terraform/modules' directory before the configuration can be used.
- DIt copies the subdirectory into '.terraform/modules' and thereafter ignores later edits to the original files.
A module source beginning with './' or '../' is a local path that Terraform reads directly without any download. Terraform classifies each module source by its syntax. A relative path prefixed with './' or '../' denotes a local module, so init resolves it against the working directory on disk and never contacts the registry or a remote host.
Why A is wrong: Registry retrieval is tempting because init does fetch registry modules, but a registry source must be a namespaced address such as 'namespace/name/provider', not a relative path.
Why B is correct: A source starting with './' or '../' is a local path, so Terraform reads the files in place during init and performs no download.
Why C is wrong: Git cloning happens only for a Git source address, and a leading './' is never interpreted as a Git URL, so no clone occurs for a local path.
Why D is wrong: This sounds plausible because remote modules are cached under '.terraform/modules', but local modules are referenced in place and edits are picked up on the next run.
What you must be able to do. Explain the local backend and its limits, describe state locking, configure remote state with a backend block, and manage drift with the right state commands.
In one sentenceState in practice: the default local backend and why teams outgrow it, state locking, remote state via the backend block, and handling drift with refresh, state mv and rm, and -replace.
Recall check: answer these from memory first
- Give two reasons the local backend is unsuitable for a team.
- Say what state locking prevents and what force-unlock is for.
- Name the command to force a single resource to be recreated on the next apply, and the deprecated command it replaces.
What it tests. Managing state: the default local backend storing terraform.tfstate on disk and its limits for teams, state locking that stops concurrent applies corrupting state and depends on the backend, configuring remote state with a backend block (for example S3 or HCP Terraform via cloud) for shared state and locking with migration on init, and managing drift, where real-world change diverges from state, using refresh, terraform state mv and rm, and the -replace option, plus why hand-editing state is dangerous.
How to study it. Understand why the local backend does not suit teams: state sits on one machine and there is no shared locking across people, so remote state is preferred for collaboration. Learn what locking protects against and what -lock and force-unlock do. Know that changing the backend requires terraform init and that Terraform can migrate existing state. For drift, learn refresh reconciles state with reality and plan surfaces drift as proposed changes, and reach for -replace rather than the deprecated taint to force recreation. Treat state mv and rm as surgical tools and never hand-edit the file.
Easy to confuse
- Local backend versus remote backend. The local backend keeps terraform.tfstate on one machine with no shared locking; a remote backend such as S3 or HCP Terraform gives shared state and locking for a team. A collaboration scenario points to remote state, and switching backends needs terraform init.
- -replace versus taint. -replace, given to plan or apply, marks a resource for recreation on the next run and is the current approach; the taint command did the same but is deprecated in 004. When a question offers both, the exam wants -replace, so treat taint as the older-version trap.
- terraform state mv versus terraform state rm. state mv renames or moves an object within state, keeping it managed; state rm removes an object from state so Terraform stops managing it without destroying the real resource. The exam checks you pick the one that matches the stated outcome.
Worked example from the TF-Associate-004 bank
lock_openFree sampleTerraform state managementeasy
A practitioner runs 'terraform apply' in a new directory that contains only 'main.tf', with no 'backend' or 'cloud' block declared. After the apply succeeds, where does Terraform record the resulting state by default?
terraform {
required_providers {
local = {
source = "hashicorp/local"
}
}
}
resource "local_file" "note" {
filename = "${path.module}/note.txt"
content = "hello"
}
- AIn a file named 'terraform.tfstate' in the current working directory, using the local backend.check_circle Correct
- BIn HCP Terraform, because any workspace is automatically linked to the remote state service.
- CIn the provider's own datastore, since the 'local' provider manages where state is persisted.
- DNowhere on disk, because state is held only in memory until a backend is added.
When no backend is configured, Terraform defaults to the local backend and stores state in terraform.tfstate on disk. The local backend is Terraform's built-in default. When neither a 'backend' nor a 'cloud' block is present, Terraform persists the working state to a file called terraform.tfstate in the current directory after every successful operation.
Why A is correct: With no backend or cloud block configured, Terraform uses the local backend by default and writes state to 'terraform.tfstate' in the working directory.
Why B is wrong: HCP Terraform is only used when a 'cloud' block is present; without one Terraform never links a workspace to a remote service, so this is wrong.
Why C is wrong: It is tempting to conflate the 'local' provider with the local backend, but providers manage resources and never decide where state is stored.
Why D is wrong: Terraform always persists state after an apply; the default local backend writes it to disk immediately rather than keeping it only in memory.
What you must be able to do. Import existing infrastructure into state, use read-only CLI commands to inspect state, and enable verbose logging when diagnosing behaviour.
In one sentenceKeeping infrastructure running: importing unmanaged resources into state (which does not write your configuration), read-only state inspection, and environment-variable-driven verbose logging.
Recall check: answer these from memory first
- Say what terraform import does and does not do, and name the two ways to import.
- Match terraform state list, terraform state show and terraform show to the question each one answers.
- Name the environment variables that control verbose logging and where output goes.
What it tests. Maintaining running infrastructure: importing existing resources into Terraform with the terraform import command or an import block, after which configuration must be written to match the imported object, inspecting state read-only with terraform state list and terraform state show and rendering state or a saved plan with terraform show, and using verbose logging controlled by the TF_LOG environment variable at levels such as TRACE and DEBUG with TF_LOG_PATH to write logs to a file.
How to study it. Fix the key limit of import: it populates state but does not generate configuration, so you still write matching configuration yourself, and the import block is the current, plan-reviewable way to do it. Sort the read-only inspection commands by what each answers: state list for what is managed, state show for one resource's attributes, terraform show for whole state or a saved plan. Remember logging is controlled by environment variables (TF_LOG, TF_LOG_PATH), not a configuration argument, which is a common trap.
Easy to confuse
- Import populating state versus writing configuration. Import brings an existing resource into state so Terraform manages it, but it does not write the resource block for you; you must author configuration that matches the imported object. The exam plants the belief that import generates configuration, and it does not.
- terraform import command versus import block. The terraform import command imports imperatively at the CLI; an import block declares the import in configuration so it is previewed in plan and applied like other changes. The block is the current, reviewable approach the 004 exam favours.
- TF_LOG environment variable versus a configuration setting. Verbose logging is turned on by the TF_LOG environment variable and directed to a file by TF_LOG_PATH, not by any argument in the configuration. A scenario that tries to enable logging inside a .tf file is the trap.
Worked example from the TF-Associate-004 bank
lock_openFree sampleMaintain infrastructure with Terraformmedium
A practitioner has applied a configuration that manages roughly forty resources and wants to see the full list of resource addresses currently tracked in state, without printing any attribute values or changing anything. Which command produces exactly this?
- Aterraform show
- Bterraform state listcheck_circle Correct
- Cterraform state show
- Dterraform plan
Use terraform state list to enumerate the resource addresses tracked in state without printing attribute values. terraform state list walks the state file and emits each managed resource's address, giving an index of what Terraform tracks; it is read-only and outputs no attribute data, which distinguishes it from terraform show.
Why A is wrong: terraform show does read state read-only, but it prints the full human-readable attributes of every resource rather than a bare list of addresses, which is more than was asked for.
Why B is correct: terraform state list reads the current state and prints one resource address per line for every tracked resource, with no attribute values and no modification to state.
Why C is wrong: terraform state show requires a specific resource address argument and prints that one resource's attributes, so it cannot enumerate the whole list of addresses.
Why D is wrong: terraform plan compares configuration against state and refreshes it to compute a diff, so it does extra work and does not simply list the addresses held in state.
What you must be able to do. Use HCP Terraform to run Terraform remotely with managed state, and match a governance or organisation need to the right HCP Terraform feature.
In one sentenceThe managed platform: HCP Terraform (formerly Terraform Cloud) running remotely with managed state, its collaboration and governance features, workspaces and projects, and VCS and API integrations.
Recall check: answer these from memory first
- Give the current name for the product formerly called Terraform Cloud and say how a remote run differs from a local apply.
- Match policy as code, cost estimation and private registries to the governance need each one meets.
- Distinguish an HCP Terraform workspace from a Terraform CLI workspace.
What it tests. Using HCP Terraform: remote runs with managed state connected through the cloud block or a VCS workflow, collaboration and governance features (remote state sharing, policy as code with Sentinel or OPA, cost estimation, private module and provider registries), organising work with workspaces (each holding its own state, variables and run history) and projects that group workspaces, and integrations such as VCS-driven plans on pull requests, run triggers between workspaces, notifications, and CLI-driven or API-driven runs.
How to study it. Learn HCP Terraform as the current product name and treat Terraform Cloud as the old label the distractors use. Understand how a remote run differs from a local apply and how the cloud block or a VCS connection wires it up. Match each governance need to its feature: policy as code with Sentinel or OPA for guardrails, cost estimation for spend, private registries for internal sharing. Most importantly, separate an HCP Terraform workspace, which holds its own state, from a Terraform CLI workspace, which only switches state within one backend.
Easy to confuse
- HCP Terraform versus Terraform Cloud. HCP Terraform is the current product name for what was called Terraform Cloud. They are the same platform, but 004 uses the new name, so an option still saying Terraform Cloud is usually the older-version distractor.
- HCP Terraform workspace versus Terraform CLI workspace. An HCP Terraform workspace is an organising unit that holds its own state, variables and run history on the platform; a Terraform CLI workspace only switches between named states within a single backend locally. The exam contrasts the two directly, so do not treat them as the same thing.
- Sentinel and OPA versus cost estimation. Sentinel and OPA are policy-as-code engines that enforce rules on a run as guardrails; cost estimation reports the spend impact of a plan. Both are governance features, but the exam wants the enforcement mechanism separated from the reporting one.
Worked example from the TF-Associate-004 bank
lock_openFree sampleHCP Terraformmedium
A platform team wants every run in an HCP Terraform workspace to be blocked from applying if a proposed AWS security group opens port 22 to the whole internet. They want this enforced automatically as part of the run, before the apply stage. Which HCP Terraform feature should they use?
# proposed change flagged during plan
resource "aws_security_group_rule" "ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
- AA policy as code check using Sentinel or OPA, attached to the workspace so it evaluates the plan between the plan and apply stages.check_circle Correct
- BThe cost estimation feature, which inspects the plan and refuses to apply resources that create a security risk.
- CA private module registry constraint that rejects any module publishing a rule with an open CIDR block.
- DA workspace run trigger that fires a downstream workspace to revert the offending rule after the apply completes.
Policy as code with Sentinel or OPA in HCP Terraform enforces governance rules against a plan and can block an apply. HCP Terraform evaluates attached Sentinel or OPA policy sets against the plan output during the run, and a hard-mandatory policy that fails prevents the apply stage from proceeding, which is precisely the automated gate the team needs.
Why A is correct: Sentinel and OPA are HCP Terraform's policy as code engines; a policy set attached to the workspace runs against the plan and can produce a hard-mandatory failure that blocks the apply.
Why B is wrong: Cost estimation is a genuine HCP Terraform feature and runs at a similar point in the run, which makes it tempting, but it only reports projected spend and never blocks a run for a security condition.
Why C is wrong: The private registry hosts and versions shared modules and providers, so it sounds governance related, but it does not evaluate the contents of a plan or enforce rules at run time.
Why D is wrong: Run triggers do chain workspaces together, which feels like automation, but they act after a run finishes and cannot prevent the risky apply from happening in the first place.