HashiCorp Terraform Associate (004) Study Guide
The HashiCorp Terraform Associate (004) exam validates that you understand Infrastructure as Code concepts and can use Terraform's core workflow to provision, manage, and version cloud-agnostic infrastructure. It targets cloud engineers, DevOps practitioners, and developers with hands-on Terraform experience who can write HCL, manage state, consume modules, and use HCP Terraform. The exam is 57-60 multiple-choice questions in 60 minutes, with a scaled passing score around 700.
Domain 1: Understand infrastructure as code (IaC) concepts
- Infrastructure as Code means defining infrastructure in machine-readable configuration files (Terraform's .tf files written in HCL) instead of provisioning resources by hand through a console or imperative scripts.
- Because .tf files are plain text, they can be committed to version control such as Git, giving every change an author, timestamp, and commit message, plus peer review through pull requests before apply.
- A 'snowflake server' is a host that drifted into a unique, hard-to-reproduce state through accumulated manual changes; IaC eliminates snowflakes by rebuilding from a single source of truth.
- Terraform is declarative: you describe the desired end state and Terraform computes the create, update, and destroy operations needed to reach it, rather than you writing step-by-step commands.
- The Terraform configuration itself acts as living documentation - reading the code tells you exactly what infrastructure should exist, and it cannot silently drift from reality because it is what Terraform applies.
- Using one parameterized configuration for dev, staging, and production yields consistent environments that differ only by variable values, reducing 'works in staging but not production' problems.
- Configuration drift is the divergence between real infrastructure and the declared configuration, usually caused by undocumented ad-hoc manual changes.
- To rebuild an environment from code you clone the repository, run terraform init to download providers and configure the backend, then terraform apply to recreate the declared resources.
- Idempotency means re-running the same configuration produces the same end state; Terraform tracks state and only changes what differs from the desired configuration.
- count and for_each let a single resource block create multiple instances, supporting repeatable, scalable definitions instead of copy-pasting blocks.
- Automation benefit: a CI/CD pipeline can run terraform apply automatically after a change is merged, removing manual console steps and making provisioning repeatable.
- Version control plus IaC enables auditing - to know what infrastructure existed on a given date you inspect the Git commit history and the tagged/merged configuration from that time.
- Collaboration benefit: multiple engineers can propose, review, and merge configuration changes through a shared version-controlled repository rather than editing live systems directly.
Domain 2: Understand the purpose of Terraform (vs other IaC)
- Terraform is cloud-agnostic: a single configuration and workflow can provision AWS, Azure, Google Cloud, and many other platforms, unlike AWS CloudFormation which targets AWS only.
- Terraform uses a plugin-based provider architecture; Terraform Core is decoupled from any platform and relies on interchangeable provider plugins to make API calls.
- A provider translates Terraform resource definitions (HCL) into the API calls of a specific platform such as AWS, Azure, Kubernetes, or a SaaS service.
- Providers are downloaded from the Terraform Registry (registry.terraform.io) during terraform init; HashiCorp and the community publish thousands of providers there.
- You can declare multiple providers in one configuration (for example azurerm and aws) and Terraform manages resources for each in a single plan/apply lifecycle.
- Terraform builds one dependency graph spanning all providers, so it can wire an output from one provider into a resource of another (for example a cloud VPC ID feeding a monitoring config) within a single run.
- Providers exist for far more than clouds - DNS, databases, monitoring (Datadog), GitHub, PagerDuty, Auth0, Cloudflare - so one configuration can manage diverse systems with one workflow.
- Standardizing on Terraform lowers cognitive load: engineers learn one language (HCL) and one lifecycle (init, plan, apply, destroy) instead of a separate native tool per cloud.
- Reusable patterns: encapsulating infrastructure in modules lets teams instantiate the same pattern against different providers or environments.
- A consistent, provider-agnostic workflow makes it easier to extend provisioning to new clouds later without retraining on a different tool.
- Terraform can target multiple regions of a single cloud and another cloud entirely from one configuration, which native single-cloud tools cannot.
- Terraform can provision both the underlying cloud infrastructure and the Kubernetes objects running on it through dedicated providers in the same workflow.
- Code-review, module, and workflow practices stay identical regardless of which cloud is targeted, since the same HCL constructs (variables, outputs, modules) are reused everywhere.
Domain 3: Understand Terraform basics
- terraform init initializes a working directory: it downloads provider plugins declared in required_providers, installs modules, and configures the backend.
- A fully qualified provider source address is HOSTNAME/NAMESPACE/TYPE; for AWS it is registry.terraform.io/hashicorp/aws, and an omitted hostname defaults to registry.terraform.io.
- The required_providers block must be nested inside the top-level terraform block and maps each local provider name to its source and version constraint.
- A provider block (separate from required_providers) configures how an installed provider behaves, such as region or credentials; required_providers declares which providers and versions are needed.
- The .terraform.lock.hcl dependency lock file records the exact provider versions and cryptographic checksums selected during init, ensuring reproducible installs across machines.
- Pin or constrain provider versions to prevent an unexpected major upgrade from introducing breaking changes; commit the lock file so the whole team uses identical versions.
- The pessimistic constraint operator ~> allows the rightmost component to increment: ~> 2.0 means >= 2.0.0 and < 3.0.0, while ~> 3.2 means >= 3.2.0 and < 4.0.0.
- When provider requirements change you must re-run terraform init so Terraform resolves the new requirements, installs the provider, and updates the lock file; init is safe to run repeatedly.
- Provider plugins are installed into the .terraform/providers subdirectory of the working directory during init.
- terraform init -plugin-dir=PATH installs providers from a local mirror directory instead of the network, useful for air-gapped environments.
- terraform init -lockfile=readonly fails if the lock file would need changes, enforcing that only the recorded versions are used.
- terraform providers lock -platform=windows_amd64 -platform=darwin_arm64 pre-populates the lock file with checksums for multiple operating systems so the same lock file works across mixed-OS teams.
- Use the alias meta-argument and reference a non-default provider configuration on a resource with provider = aws.west to target multiple regions or accounts.
- If terraform plan or apply errors that providers are not present, the working directory has not been initialized - run terraform init first.
Domain 4: Use Terraform outside the core workflow
- The classic terraform import command takes two positional arguments in order: ADDRESS then ID, for example terraform import aws_instance.web i-0abc123.
- Classic CLI import requires that a matching resource block already exist in your configuration; importing into a non-existent address produces an error.
- Config-driven import (Terraform 1.5+) uses an import block with two arguments: to (the resource address, unquoted) and id (the provider-specific string identifier).
- An import block is part of your .tf files, so it is committed to version control and the import is previewed during terraform plan before being applied.
- terraform plan -generate-config-out=generated.tf generates HCL for imported resources into a new file; the target file must not already exist.
- Import only brings the real object into state - it does not write your configuration. If your resource block differs from actual attributes, the next plan proposes changes to reconcile them.
- import blocks are one-time operations and can be safely removed from configuration after the import has been applied.
- In Terraform 1.5 the import block id must be a literal string; variable interpolation in id was added in Terraform 1.6.
- Import blocks support for_each, letting you import many objects at once using each.key and each.value in the to and id arguments.
- Importing into a child module uses a module-qualified address such as module.network.aws_instance.web.
- terraform state mv OLD_ADDRESS NEW_ADDRESS renames or moves a resource within state without destroying and recreating it.
- terraform state show ADDRESS prints the attributes Terraform has recorded in state for a single resource.
- Some resources do not support import; Terraform returns an error indicating the resource cannot be imported when the provider has no read implementation for it.
- The primary use case for import is bringing manually created or third-party-managed resources under Terraform management without downtime, avoiding destroy-and-recreate of live production data.
Domain 5: Interact with Terraform modules
- Every module block must include the source argument, which tells Terraform where to find the module's .tf files; without it terraform init errors.
- A module is simply a directory containing one or more .tf configuration files; the root module is your working directory and it can call child modules.
- Local path sources begin with ./ or ../ and are resolved relative to the calling module's directory, for example source = "./modules/network".
- Public Terraform Registry module addresses follow NAMESPACE/NAME/PROVIDER, for example terraform-aws-modules/vpc/aws.
- The version argument is honored only for modules from a registry (public or private/HCP); for Git, HTTP, and other generic sources it is ignored - pin those with a ref in the URL.
- Git sources can pin a revision with a ref query parameter, for example source = "git::https://github.com/org/repo.git?ref=v2.0.0".
- A double slash in a remote source selects a subdirectory, for example git::https://example.com/infra.git//modules/network.
- Pass values into a module as arguments inside the module block (for example instance_count = 3); inside the child these are accessed as var.instance_count.
- Expose values from a child module via output blocks; the parent accesses them as module.<name>.<output_name>.
- Run terraform init after adding or changing a module so Terraform downloads it into .terraform/modules and records it in the modules.json manifest.
- Module resource addresses are prefixed with the module call label, for example module.web.aws_instance.this, so the same module can be called multiple times without collision.
- count and for_each work on module blocks; with count instances are addressed as module.app[0], and with for_each as module.app["key"].
- If you omit version for a registry module, Terraform selects the newest matching release at init time, which can cause non-reproducible builds - pin with an explicit constraint such as ~> 2.4.
- A private registry module on HCP Terraform uses an address like app.terraform.io/ORG/NAME/PROVIDER and supports the version argument.
Domain 6: Use the core Terraform workflow
- The core individual workflow is three ordered stages: Write (author HCL), Plan (preview changes), Apply (provision real infrastructure).
- terraform plan creates an execution plan showing proposed create/update/destroy actions without making any changes; it first refreshes in-memory state against real resources to detect drift.
- Plan symbols: + means create, - means destroy, ~ means update in place, and -/+ means destroy and then recreate (replacement).
- A -/+ replacement destroys the existing object and creates a new one because a changed attribute forces replacement; a ~ in-place update keeps the same object and provider-assigned ID.
- terraform apply without a saved plan generates its own plan, displays the changes, then pauses and requires you to type the literal word "yes"; any other input cancels with no changes made.
- Save a reviewed plan with terraform plan -out=tfplan, then terraform apply tfplan executes exactly those actions with no re-prompt, guaranteeing the applied changes match the reviewed plan.
- A saved plan can become stale; if state changed since the plan was created, Terraform refuses to apply it and asks you to re-plan.
- terraform apply -target=ADDRESS limits the operation to a specific resource and its dependencies; it is intended for troubleshooting and recovery, not routine use.
- When apply finishes, Terraform prints a summary like "Apply complete! Resources: X added, Y changed, Z destroyed."
- Computed attributes that are only known after a resource is created display as (known after apply) in the plan because the value cannot be determined until apply runs.
- Removing a resource block from configuration makes the next plan show that resource for destruction (-), since it is in state but no longer in the configuration.
- Terraform locks state during plan and apply to prevent concurrent operations from writing simultaneously and corrupting it.
- If the current state already matches the configuration, plan reports no changes and apply does nothing.
- terraform destroy removes all resources tracked in state for the configuration and, like apply, requires confirmation unless auto-approved.
Domain 7: Implement and maintain state
- With no backend declared, Terraform uses the local backend and writes state to terraform.tfstate in the current working directory.
- The backend is configured with a backend block nested inside the top-level terraform block; only one backend block is allowed per configuration.
- Any change to the backend configuration requires re-running terraform init, which detects the change and offers to migrate existing state to the new location.
- Remote backends (s3, azurerm, gcs, HCP Terraform) store a single shared source-of-truth state so the whole team reads and writes the same state.
- State locking prevents two engineers from running apply simultaneously and corrupting state; most remote backends provide locking automatically.
- Without remote state and locking you risk concurrent applies, merge conflicts, and exposed secrets, since state can contain sensitive values in plain text.
- For the s3 backend, key is the path/name of the state object within the bucket; for azurerm, key is the blob name within the container.
- Use a different key value (or a distinct workspace) per environment so dev, staging, and prod each have isolated state.
- Partial configuration leaves some backend settings out of the block and supplies them at init time with terraform init -backend-config=... or a backend config file, keeping secrets out of code.
- terraform init -reconfigure ignores any existing backend state and reconfigures from scratch without attempting migration; -migrate-state moves existing state to the new backend.
- terraform force-unlock LOCK_ID manually releases a stuck lock; use it only when you are certain no operation is actually running.
- The HCP Terraform / Terraform Enterprise backend (the remote backend or cloud block) stores state securely within the workspace, managed by the platform.
- terraform state list, terraform state show, terraform state mv, and terraform state rm are the commands for inspecting and surgically editing state.
- Provide backend credentials securely, for example the azurerm access key via the ARM_ACCESS_KEY environment variable or terraform init -backend-config, rather than hardcoding them.
Domain 8: Read, generate, and modify configuration
- Variable value precedence from lowest to highest: environment variables (TF_VAR_name), then terraform.tfvars, then *.auto.tfvars (alphabetical), then -var and -var-file flags on the command line, which win over everything.
- Among auto-loaded files, *.auto.tfvars files are processed after terraform.tfvars, so a value in an .auto.tfvars file overrides the same key in terraform.tfvars.
- A variable with a default uses that default when no value is supplied; a variable with no default and no supplied value is required and prompts interactively in a terminal (or errors in non-interactive runs).
- Set values via -var="key=value" or -var-file=staging.tfvars on the CLI, via terraform.tfvars/*.auto.tfvars files, or via the TF_VAR_<name> environment variable.
- Variable type constraints include primitives (string, number, bool) and complex types such as list(...), map(...), set(...), and object({ name = string, count = number }).
- Terraform performs automatic type conversion where possible, for example converting the string "8080" to the number 8080; if conversion is impossible it errors during validation.
- A validation block (condition + error_message) inside a variable rejects values that fail a boolean test, for example contains(["dev","stage","prod"], var.env).
- The description argument documents a variable, appears in CLI prompts, and shows in generated module documentation.
- output blocks expose values; a description documents them and value = aws_instance.app[*].id uses a splat to return all IDs from a counted resource.
- Mark sensitive data with sensitive = true; sensitive outputs are redacted as (sensitive value) in CLI output but still stored in state, and a sensitive value fed into a non-sensitive output causes an error.
- terraform output -raw NAME prints a single value without quotes or formatting for scripting; terraform output -json prints all outputs as JSON, including sensitive ones unredacted.
- An implicit dependency forms automatically when one resource references another's attribute (for example aws_eip referencing aws_instance.web.id), and Terraform orders creation accordingly.
- Use the depends_on meta-argument to declare an explicit dependency when there is no attribute reference but ordering still matters, for example an instance that needs an IAM policy created first.
- To make an optional value, declare default = null and handle it in configuration with coalesce() or a conditional expression.
Domain 9: Understand HCP Terraform capabilities
- Connect a working directory to HCP Terraform with a cloud block nested inside the terraform block, specifying the organization and a workspaces selector (name or tags).
- In the CLI-driven workflow with the cloud block, the default execution mode is remote: terraform apply uploads the configuration and runs plan/apply on HCP Terraform's managed infrastructure while streaming output to your terminal.
- Local execution mode runs plan and apply on your own machine but still stores state in HCP Terraform.
- The VCS-driven workflow connects a workspace to a Git repository (GitHub, GitLab, Bitbucket) via webhooks, so pushes/merges to the tracked branch and PRs targeting it automatically queue runs.
- A VCS-connected workspace treats the repository as the source of truth and rejects CLI-uploaded configuration for apply runs.
- The default run flow is plan, then a Needs Confirmation pause requiring a user with apply permission to click Confirm & Apply, then apply.
- Enabling auto-apply on a workspace skips the confirmation pause and proceeds from a successful plan straight to apply, common for non-production or fully automated pipelines.
- HCP Terraform queues runs and executes them serially per workspace, so a second run waits until the first completes, preventing concurrent state changes.
- Sentinel and OPA policy checks can gate runs; a failed mandatory policy blocks the apply unless a user with override permission overrides it.
- Cost estimation shows the projected monthly cost delta of the planned resource changes before you apply.
- Workspace variables can be Terraform variables (passed exactly like -var to matching variable blocks) or environment variables, and either can be marked sensitive.
- The working_directory setting tells HCP Terraform which subdirectory of the repository to treat as the configuration root.
- Authentication to HCP Terraform uses tokens: user, team, or organization tokens, scoped to different permission levels.
- A configuration version is the packaged set of Terraform files for a run; in VCS workspaces a manually queued run uses the latest commit on the tracked branch.
HashiCorp Terraform Associate (004) exam tips
- Memorize the exact plan action symbols (+, -, ~, -/+) and which CLI flags change behavior (-out, -target, -refresh-only, -auto-approve), since many questions hinge on precise command and symbol meaning.
- Know variable precedence cold - environment variables lose to terraform.tfvars, which loses to *.auto.tfvars, which loses to -var/-var-file flags - because it is a frequent tricky question.
- Distinguish required_providers (declares which providers/versions) from the provider block (configures behavior), and remember the source address format HOSTNAME/NAMESPACE/TYPE.
- Be clear on what terraform init does and when it must be re-run: changing providers, modules, or the backend all require another init.
- Read every option fully on import, state, and HCP workflow questions; the difference between classic CLI import (resource block must pre-exist) and config-driven import blocks (1.5+, generate-config-out) is a common trap.
Study guide FAQ
How many questions are on the exam, how long is it, and what score do I need to pass?
The Terraform Associate (004) exam has roughly 57-60 multiple-choice and multiple-select questions to complete in 60 minutes. It is delivered online with a proctor, and the passing result is reported on a scaled basis (commonly cited around 700); HashiCorp reports pass/fail rather than a percentage.
Do I need to memorize provider-specific resource arguments like AWS or Azure attribute names?
No. The exam is provider-agnostic and tests Terraform itself - HCL syntax, the core workflow, state, modules, variables/outputs, and HCP Terraform. You should recognize that something like aws_instance is a resource and understand how it behaves, but you are not tested on memorizing individual cloud attribute schemas.
What is the difference between Terraform CLI (open source) and HCP Terraform?
Terraform CLI is the free open-source binary you run locally that uses a local or remote backend for state. HCP Terraform (formerly Terraform Cloud) is HashiCorp's managed SaaS that adds remote execution, shared remote state with locking, VCS-driven runs, role-based access, policy enforcement (Sentinel/OPA), cost estimation, and a private module registry. Domain 9 covers HCP Terraform concepts.
How current does my Terraform knowledge need to be for the 004 objectives?
The 004 objectives include features through recent Terraform versions, notably config-driven import using import blocks (Terraform 1.5) and id interpolation in import blocks (Terraform 1.6), plus the cloud block for HCP Terraform. Study with a modern Terraform CLI (1.5 or newer) and know these newer features, not just the older terraform import CLI command.