Terraform 004 cheat sheet — every exam topic on one page
90 key facts across 8 exam domains — every topic on the Terraform 004 blueprint, with the exam pattern behind each. A condensed cheat sheet, distilled from the full Terraform 004 revision notes. Skim it the week of your exam.
Updated
Infrastructure as Code (IaC) with Terraform
8% of the examInfrastructure as Code with Terraform: Concepts and Multi-Cloud Workflows
- IaC defines infrastructure in machine-readable, version-controlled files — the definition files are the source of truth, not the console
- The advantages of IaC patterns: versioning, repeatability, automation, reduced human error, and self-service
- Approvals live in pull requests; state holds current resource metadata and the provider audit log records API calls — neither records who approved a change
- Configuration rebuilds infrastructure, not the data inside it — databases still need backups
- Terraform is declarative: it converges on the declared state — re-applying unchanged code does nothing, removing a block or lowering count plans destroys, and a manually deleted resource is planned as a create
- In plan output, ~ is an in-place update while -/+ and +/- are replacements that produce a new object — the line to catch for stateful resources
- Providers are separately versioned plugins installed only by terraform init; Terraform core makes no API calls, and one binary serves every platform
- One configuration, plan and state can span clouds, on-premises (vSphere, agentless) and SaaS APIs, with cross-provider references — but resource types are provider-specific and do not port between clouds
- Only provider resources are managed; provisioners and data sources are not. Existing resources are adopted by writing resource blocks and importing them
- Terraform provisions infrastructure lifecycle (immutable, replace-on-change, e.g. a new image); Ansible, Chef, and Puppet manage software on existing servers — complementary, not competing
How the exam tests this
Expect definition and best-description questions: what IaC is, which IaC advantage solves a described symptom (drift between environments, missing change history and approvals, ticket bottlenecks, risky changes reaching production), and whether an approach is declarative or imperative. Behaviour questions ask what plan proposes when nothing changed, when a block is removed or count lowered, or when someone deletes a managed resource by hand, and which plan symbol signals a replacement. Provider questions probe the plugin model: core makes no API calls, init installs providers, providers are versioned separately, and only provider resources are managed. Multi-cloud and hybrid questions test one configuration and state across providers, cross-provider references and the agentless model — and the workflow-versus-configuration distinction, where wrong answers claim resource blocks port between clouds. Also expect adopting existing infrastructure (resource blocks plus import), the limits of IaC (data still needs backups; state is not an audit trail) and Terraform versus configuration management tools.
Terraform fundamentals
11% of the examTerraform Providers, Version Constraints, and State Basics
- Providers are plugins that map resource types to platform API calls; Terraform core makes no cloud API calls itself
- required_providers (inside the terraform block) declares each provider's source and version; an undeclared provider is assumed to be hashicorp/<name>, so non-HashiCorp providers must be declared
- required_version constrains the Terraform CLI and stops init/plan when unmet; Terraform never installs its own binary. A version argument in a provider block is deprecated
- terraform init installs providers into .terraform/providers; a plugin cache reuses downloads across directories, while -plugin-dir installs only from that directory
- ~> 5.1 allows 5.x from 5.1 upward but never 6.0; ~> 5.1.0 allows only 5.1.x. Pre-releases match only an exact constraint
- One version per provider serves the root and every module, so all constraints must overlap; reusable modules set a minimum (>=), root modules bound upgrades
- Commit .terraform.lock.hcl; init keeps locked versions, -upgrade re-selects, -lockfile=readonly guards CI, and providers lock -platform adds checksums for other platforms
- alias adds configurations of one provider, selected with provider = aws.west; two different providers with the same type need distinct local names
- State maps configuration to real objects, stores dependency metadata and caches attributes; it holds secrets in plain text, and sensitive = true only redacts CLI output
How the exam tests this
This objective is heavily code-driven: expect snippets asking what terraform init will install given a required_providers block, which versions a constraint like ~> 5.1 or ~> 5.1.0 permits, why an undeclared non-HashiCorp provider fails, and how to place a resource under a second provider configuration with alias and provider = aws.west. Harder items combine facts: overlapping constraints across a root module and child modules, pre-release versions, when the lock file does and does not change, init -upgrade versus -lockfile=readonly, checksum mismatches between platforms fixed with terraform providers lock -platform, the plugin cache versus -plugin-dir, and two providers that share a type name. required_version is contrasted with provider version constraints. State questions ask why state exists and whether sensitive = true keeps secrets out of it. Trigger words: "pin provider versions", "same provider different accounts", "air-gapped", "CI must not change the lock file", "source of truth for what Terraform manages".
Core Terraform workflow
19% of the examTerraform init, validate, and plan: the core workflow explained
- The core workflow is Write, Plan, Apply — plan previews changes as a diff, apply executes them, and the loop repeats for every change.
- terraform init must run first in any new or cloned directory, and again when providers, modules (source or version) or the backend change — not when a provider argument like region or a resource argument changes. It creates no infrastructure and, with the local backend, no state file.
- terraform get installs modules only; terraform providers lock updates the lock file without installing plugins. Neither replaces init.
- .terraform.lock.hcl pins provider versions and checksums and is committed; .terraform is not. Plain init reinstalls the locked version; init -upgrade picks the newest version within the constraints (never past them), upgrades modules too, and rewrites the lock file to commit.
- Modules are not in the lock file. For registry modules, only an exact version ("3.1.0") installs identical module code on every run; Git sources pin with ?ref= set to a tag or commit.
- A backend change needs -migrate-state (copy state; add -force-copy to skip prompts, which still overwrites the destination) or -reconfigure (new backend already holds the right state). -backend-config supplies partial configuration and is accepted by init only.
- terraform validate checks syntax and internal consistency offline after init (use init -backend=false in CI); fmt -check checks only formatting; plan -refresh=false still needs providers and state.
- terraform plan is read-only and holds the state lock; -refresh=false skips drift detection, -refresh-only reconciles state, -destroy previews a teardown. A plain plan exits 0 even with changes; -detailed-exitcode returns 2 when changes are pending.
- Plan symbols: + create, ~ update in-place, - destroy, -/+ replace destroy-first, +/- replace create-first (create_before_destroy), <= read; a replacement counts as one add plus one destroy.
- terraform plan -out=tfplan saves a binary, possibly sensitive plan; terraform apply tfplan executes exactly it without a prompt, with variables embedded, and rejects it if the state has changed since.
How the exam tests this
Questions are workflow scenarios: which command comes next for a fresh clone, a new provider or module, a changed provider argument or a changed backend; how plain init, init -upgrade, the lock file and module version arguments decide which versions get installed; which backend flag (-migrate-state, -force-copy, -reconfigure, -backend-config, -backend=false) fits a situation; what validate checks compared with fmt -check and plan; what plan reads, locks and changes, its modes (-refresh-only, -destroy, -refresh=false) and exit codes with and without -detailed-exitcode; reading the +, ~, -, -/+, +/- and <= symbols and the summary counts; and the behaviour of saved plan files.
terraform apply, destroy, and fmt: executing and maintaining changes
- terraform apply is the only core-workflow command that changes infrastructure: it plans, waits for the literal word yes, executes in dependency order, and records results in state while holding the state lock. An empty plan finishes without a prompt.
- Apply skips the approval prompt in exactly two cases: -auto-approve, or applying a saved plan file. -input=false (or TF_INPUT=0) disables prompts, so a bare apply fails; TF_IN_AUTOMATION only adjusts output.
- A saved plan embeds the actions, a configuration snapshot and variable values: later .tf/.tfvars edits do not affect it, extra planning options are rejected, and it is refused as stale if state changed since it was created.
- Apply is not transactional: completed operations stay in state and you re-run apply after fixing the cause. A failed creation-time provisioner leaves the resource tainted, so the next plan replaces it.
- Up to 10 operations run concurrently by default; -parallelism=n changes the ceiling for plan, apply or destroy but never overrides dependencies. -lock-timeout only governs retrying the state lock.
- terraform destroy (= terraform apply -destroy) deletes everything in the selected workspace's state, including child-module resources, in reverse dependency order; data-source objects, unmanaged resources, the configuration and the state file remain.
- prevent_destroy = true makes any destroying plan error (a full destroy deletes nothing), but it protects only while the block is in configuration.
- -target is exceptional-use; a targeted destroy also removes the target's dependents. terraform fmt rewrites in place (current directory unless -recursive or a path is given); -check exits non-zero, -write=false exits 0, -diff alone still writes, and fmt never validates or reorders.
- Pipeline order: fmt -check, init, validate, plan -out, then apply the saved plan.
How the exam tests this
Expect questions on when the apply approval prompt appears and the two ways to bypass it (-auto-approve or a saved plan file), contrasted with -input=false, TF_INPUT and TF_IN_AUTOMATION, which do not approve anything. Saved plans: what they fix at plan time (configuration and variable values) and when they are stale. Partial failures (no rollback, tainted resources after a failed provisioner), the default parallelism of 10 and what -parallelism cannot change. For destroy: equivalence with apply -destroy and plan -destroy, exactly what is and is not deleted (workspace, child modules, data sources), prevent_destroy behaviour and its caveat, and -target as an exceptional-use flag that pulls in dependents. For fmt: -check versus -write=false versus -diff, -recursive or a path for scope, and that fmt formats but never validates.
Terraform configuration
22% of the examTerraform Resources, Data Sources, Variables, and Outputs
- resource blocks create and manage infrastructure; data blocks only read existing infrastructure and never change it.
- Resources are referenced as type.name (aws_instance.web); data sources take the data. prefix (data.aws_ami.ubuntu).
- Data sources are read during plan, unless an argument is unknown until apply or they depend on a resource with pending changes; then the read is deferred to apply.
- Deleting a resource block plans a destroy (its prevent_destroy goes with it); a removed block with destroy = false, or terraform state rm, stops management without destroying.
- Referencing another resource's attribute, such as aws_subnet.app.id, creates an implicit dependency that orders operations automatically.
- A variable without a default is required: Terraform prompts, or errors when -input=false or no terminal is available. nullable defaults to true, so an explicit null overrides the default unless nullable = false.
- Precedence, lowest to highest: TF_VAR_, terraform.tfvars, terraform.tfvars.json, *.auto.tfvars (lexical), then -var and -var-file together in command-line order, last wins.
- An output derived from a sensitive value must declare sensitive = true itself; terraform output NAME, -raw and -json still print it in plain text.
- Collections (list, set, map) have one element type, so list(any) unifies mixed elements; tuples and objects keep per-position or per-attribute types, and object conversion drops undeclared attributes.
How the exam tests this
Expect questions that make you differentiate resource and data blocks, including the data. prefix, when a data source is read, and what deleting each kind of block does to real infrastructure (and how to stop managing a resource without destroying it). Variable precedence is heavily tested, usually as a scenario where the same variable is set in several places, including -var and -var-file together. You should also know what happens to a required variable with no value, how nullable treats an explicit null, how outputs are read from child modules (including for_each modules), which commands reveal sensitive outputs, and how Terraform converts values to list, set, map, object and tuple constraints, including optional() defaults.
Terraform Expressions, Functions, Dependencies, and Sensitive Data
- The conditional is condition ? true_val : false_val; null means "argument not set", so cond ? value : null makes an argument optional, but count needs a number (cond ? 1 : 0).
- Splat (resource[*].attribute) works on count-created lists; for_each-created resources form a map, so use values() or a for expression.
- You cannot write custom functions in Terraform; know merge (later wins), contains(list, value), element (wraps), flatten, toset, jsonencode/jsondecode, format vs templatefile, and cidrsubnet arithmetic.
- Indexing a missing map key errors; lookup and try fall back on absence/errors, can tests for errors, and coalesce skips null and empty strings. try does not replace a null value.
- count takes a number and reindexes when a middle item is removed (shifted indexes change, the last is destroyed); for_each takes a map or set of strings and keeps stable keys that must be known at plan time.
- depends_on takes whole resource, data source or module references only, adds to implicit dependencies, and on a data source defers the read to apply when its dependency changes.
- validation, precondition and postcondition fail the run (postconditions can read self; nothing is rolled back); top-level check blocks run at the end of plan/apply and only warn.
- sensitive redacts output only; state keeps sensitive values in plaintext. Vault dynamic secrets expire and are revoked; ephemeral resources and write-only arguments (bump the _wo_version to rotate) keep secrets out of state.
How the exam tests this
Expect to evaluate expressions on sight: a conditional, a for expression, a splat, or a function call such as element with an out-of-range index, merge with a duplicate key, coalesce versus try on a null, or cidrsubnet with non-octet newbits. Count-versus-for_each scenarios are common: the plan after removing a middle list item, why for_each rejects a list or a list of objects, and where count.index and each.key are valid. You will also see which depends_on syntax is legal and how it affects a data source, which custom condition fails the run versus only warns, and sensitive-data facts: sensitive values still sit in state, dynamic Vault secrets expire, and write-only arguments rotate by bumping their version argument.
Terraform modules
11% of the examTerraform Modules: Sourcing, Using, and Versioning Explained
- A module is a container for multiple resources used together; the directory where you run Terraform is the root module, and anything called via a module block is a child module.
- The source argument is required and must be known at init: a literal string on Terraform 1.12 (1.15+ also allows locals and const variables, never ordinary variables or resource attributes). To pick a module per environment, use separate module blocks gated by count.
- Local paths start with ./ or ../ and load in place; absolute paths are copied into .terraform/modules and need another init after edits.
- github.com/ORG/REPO clones over HTTPS, git@github.com:ORG/REPO.git over SSH; an https URL without git:: is an HTTP source. Only a commit SHA in ?ref= is an immutable Git pin.
- Module variables are not global: values enter a child only through declared inputs passed in the module block — at every level of a nested tree — and leave only through declared outputs read as module.NAME.OUTPUT (module.NAME[*].OUTPUT for all count instances).
- Module blocks support count, for_each, depends_on and providers. A child with its own provider blocks cannot be used with count, for_each or depends_on, and module depends_on defers data reads to apply whenever the depended-on objects have pending changes — prefer passing an attribute as an input.
- The version argument works only for registry modules; ~> lets only the rightmost component rise, prereleases match only an exact constraint, and the lock file does not record module versions.
- terraform init installs modules into .terraform/modules; terraform get downloads modules only; only -upgrade (or get -update) moves an installed module to a newer acceptable version.
How the exam tests this
Expect questions on source formats: the ./ or ../ prefix for local paths (and that absolute paths are copied into the module cache), registry NAMESPACE/NAME/PROVIDER addresses, the GitHub HTTPS shorthand versus the git@github.com SSH form, and ?ref= with branches, tags or commit SHAs. Know that source cannot use an ordinary variable and that version works only for registry sources. Version questions cover ~> arithmetic, combined ranges, prerelease exclusion and why a fresh init can install a newer module than a teammate has. Variable scope is heavily tested, including nested modules and splat access to counted module outputs. Know the four module meta-arguments, the provider-block restriction, the cost of module depends_on, and init versus init -upgrade versus terraform get.
Terraform state management
11% of the examTerraform State: Backends, Locking, Remote State, and Drift
- The local backend is the default: state lives in terraform.tfstate in the working directory (previous version in terraform.tfstate.backup), not in .terraform/ - and a fresh clone without the uncommitted state file has no state at all.
- plan, apply, destroy, import and state-writing commands take the lock automatically; terraform_remote_state reads do not.
- -lock-timeout defaults to 0s (fail at once) and only controls waiting; -lock=false skips locking for that one command without releasing anyone else's lock.
- force-unlock LOCK_ID releases an orphaned lock after a confirmation prompt (-force skips it), checks nothing and repairs nothing; another process cannot unlock local state.
- s3 locks only with use_lockfile = true (default false) via a <key>.tflock object; dynamodb_table is deprecated and may coexist with use_lockfile while migrating. gcs locks natively; azurerm uses a blob lease.
- Backend arguments must be literals or come from terraform init -backend-config: var., local., terraform.workspace, TF_VAR_ and tfvars cannot feed a backend, and later -backend-config options win.
- -backend-config values are stored in plain text in .terraform/ and plan files; keep credentials in environment variables or credential files, and protect state with access control and encryption, since sensitive = true only redacts output.
- Changing backends: init -migrate-state copies state, -force-copy does so without prompts, -reconfigure copies nothing.
- terraform_remote_state exposes only root module outputs yet needs read access to the whole state; publish values to a separate store or use tfe_outputs for least privilege.
- A normal plan refreshes in memory: changed updatable attributes become in-place updates, deleted objects are planned for creation. -refresh-only accepts drift into state only; -refresh=false skips refresh and misses unrefreshed drift.
- State surgery: terraform taint is deprecated in favour of plan/apply -replace=ADDRESS, and terraform state rm makes Terraform forget a resource without destroying the real object.
How the exam tests this
Expect scenario questions on what the local backend stores and why a fresh clone has no state, which commands take the state lock, how -lock-timeout (default 0s) and -lock=false behave, and what force-unlock does and does not do. You will be asked how s3 (use_lockfile, the deprecated dynamodb_table), gcs, and azurerm lock state; backend block rules (no named values, no TF_VAR_ or tfvars, one backend, re-init after changes); partial configuration with -backend-config, its ordering and plain-text storage; and choosing -migrate-state, -force-copy, or -reconfigure. Remote-state questions cover the outputs.NAME reference, root-only outputs, whole-state read access, and least-privilege alternatives. Drift questions hinge on what a normal plan proposes for changed versus deleted objects, -refresh-only versus the deprecated terraform refresh, the -refresh=false trade-off, and why accepted drift reverts unless the configuration changes.
Maintain infrastructure with Terraform
8% of the examTerraform Import, State Inspection, and Debug Logging
- terraform import ADDRESS ID writes state immediately and only state; ADDRESS must be a managed resource block that already exists, and ID is the provider-specific identifier its docs define - it never generates or edits .tf files.
- Import into count/for_each instances with addresses like aws_iam_role.svc["ci"] (single-quoted in a POSIX shell); the provider configuration used by the CLI import cannot depend on data sources or other resources.
- Import blocks are plannable, apply the import at terraform apply, support for_each bulk import from Terraform 1.7 (to = res[each.key], id = each.value) and are idempotent - a leftover block plans nothing.
- terraform plan -generate-config-out=FILE writes HCL only for import targets without a resource block; the file must not exist, and generated mutually exclusive arguments must be pruned before planning again.
- state list = addresses (-id= for reverse lookup); state show ADDRESS = one resource's attributes; show FILE = whole state or a saved plan; state pull = the raw state file; output = root outputs.
- terraform show -json is the documented integration format for tools; terraform plan -json runs a new plan rather than reading a saved one.
- terraform output -raw prints a bare primitive for scripts; sensitive outputs are redacted only in the all-outputs listing and print in plain text when named or with -json/-raw.
- TF_LOG turns on logging to stderr (TRACE, DEBUG, INFO, WARN, ERROR, or JSON); TF_LOG_PATH chooses a file but enables nothing; TF_LOG_PROVIDER overrides TF_LOG for providers, while TF_LOG_CORE applies only when TF_LOG is unset. Verbose logs can hold sensitive data - redact before sharing.
How the exam tests this
Expect questions on the terraform import workflow - write the managed resource block, import with ADDRESS then ID, then plan until clean - including for_each instance addresses and why provider configuration that depends on a data source blocks the CLI import. The import block versus CLI distinction is tested: plannable, applied at apply time, for_each bulk import, idempotent leftover blocks, and -generate-config-out as the only config-generating path (new file only, conflicting generated arguments). Command-selection questions ask you to pick among terraform state list (including -id), state show, show (state or saved plan, -json for tools), state pull and output (-raw, -json, sensitive redaction). Logging questions cover TF_LOG levels including JSON, TF_LOG_PATH requiring TF_LOG, and how TF_LOG_CORE and TF_LOG_PROVIDER interact with TF_LOG.
HCP Terraform
10% of the examHCP Terraform: Workspaces, Collaboration, and Governance
- HCP Terraform (formerly Terraform Cloud) is HashiCorp's managed platform: remote runs, versioned remote state with automatic locking, stored variables, and team governance; Terraform Enterprise is the self-hosted version.
- Integration: authenticate (terraform login interactively, TF_TOKEN_app_terraform_io in CI — never a token in the cloud block), add a cloud block (organization + workspace name or tags), then terraform init, which is required before plan or apply and can migrate local state.
- Run workflows: CLI-driven, VCS-driven (merges run plan-and-apply, pull requests get speculative plans) and API-driven (upload a configuration version, no Terraform binary or VCS needed). CLI apply is refused on VCS-connected workspaces; saved plans (plan -out, apply FILE) apply exactly the reviewed plan.
- Execution modes: remote (default), local (runs on your machine; the workspace only stores state and does not supply its variables) and agent (self-hosted agents inside private networks, outbound connections only).
- An HCP Terraform workspace owns state, variables, run history and permissions — a CLI workspace is only an alternate state file. Projects group workspaces so team access, variable sets and policy sets apply to all of them; project roles are Read, Write, Maintain and Admin.
- Credentials go in environment variables; sensitive makes a value write-only for people but still passes it to runs and does not scrub it from output or state. Precedence: priority variable sets > CLI -var > workspace variables > project sets > global sets > tfvars files.
- Workspaces share outputs via remote state sharing (governs terraform_remote_state, which needs the full snapshot) or tfe_outputs (outputs only, governed by its token). Run triggers queue a downstream run after a source apply; they auto-apply only with the separate Auto-apply run triggers setting.
- Policy sets hold Sentinel or OPA policies (one framework per set), scoped globally (with exclusions), to projects or to workspaces. Sentinel: advisory, soft mandatory (overridable), hard mandatory. OPA: advisory, mandatory (overridable with Manage Policy Overrides). Run tasks run at pre-plan, post-plan, pre-apply or post-apply as advisory or mandatory; cost estimation shows monthly cost and delta before apply.
How the exam tests this
The 004 exam is the first Terraform Associate version with a dedicated HCP Terraform objective, so expect several questions from this domain. High-yield items: the CLI-workspace vs HCP-workspace distinction, terraform login versus TF_TOKEN_ variables, the cloud block and the required terraform init, the three run workflows and three execution modes (including what local mode does not do), auto-apply and run triggers, variable categories, sensitive values and precedence, project roles and workspace permission sets, cross-workspace output sharing, and the Sentinel and OPA enforcement levels, policy-set scoping and run task stages. Scenario questions describe a team problem — shared secrets, review before apply, private networks, cost control, least privilege — and ask which feature or setting solves it.
Where these Terraform 004 facts came from
Terraform 004 cheat sheet: your questions
Source
Exam structure, domain weights and scoring on this page come from the official Terraform 004 exam guide.
- HashiCorp Terraform Associate certification page — HashiCorp