SaveMyCert
Log in
5 of 5 free questions left today·for 10 a day
Terraform Pro 002 practice

Terraform Pro 002 sample questions — 25 answered and explained

25 Terraform Pro 002 sample questions written to the official HashiCorp Certified: Terraform Infrastructure Engineer Professional (002) exam guide, grouped by exam domain and weighted like the real exam. Each shows the correct answer, why every other option is wrong, and the lesson that teaches it. None of them repeat the examples on the other Terraform Pro 002 practice pages.

On this page
25
answers included
Full bank
793
on the practice page
Domains
7
weighted like the exam
Free, no account
5/day
a free account allows 10 a day

Domain 1: Scalable Terraform configuration design

20% of the Terraform Pro 002 exam, so 5 of the 25 questions on this page.

Question 1Scalable Terraform configuration design

A pipeline passes a short-lived access token into a root module, where it is used only in a provider block. The pipeline saves the plan with terraform plan -out and uploads it as a build artifact. The requirement is that the token must not be written into that plan file. What should the module author do?

Choose one.

  • a
    Set sensitive = true on the input variable that carries the token

    Sensitive only redacts the value in output; the saved plan file still contains it.

  • b
    Set sensitive = true and pass the token in a TF_VAR_ environment variable

    How a value is supplied does not change storage; a non-ephemeral variable's value is still recorded in the saved plan.

  • c
    Set ephemeral = true on the input variable that carries the token Correct

    Ephemeral variables are omitted from plan and state files, and provider blocks are one of the contexts allowed to reference them.

  • d
    Set nullable = false and pass the token with the -var command-line flag

    nullable only forbids null values, and -var is just another way to set a variable that is stored in the plan.

The concept

Ephemeral input variables (Terraform 1.10 and later) are available during the run but omitted from plan and state files.

Why that’s the answer

A saved plan records the values of input variables so apply can reproduce them. Marking the variable ephemeral tells Terraform to keep the value out of plan and state files; it must then be supplied again at apply time. Provider configuration is an allowed place to reference an ephemeral variable. Sensitive only hides display, and the method of supplying a value does not change what the plan stores.

How to reason it out
  1. Identify the artefact to protect: the saved plan file.
  2. Recall that saved plans store variable values unless they are ephemeral.
  3. Confirm the token is used in an allowed ephemeral context: a provider block.
  4. Reject display-only or delivery-only changes.

Exam tip: Use ephemeral = true when a variable's value must stay out of plan and state files.

Terraform Professional (002): Turning Requirements into Predictable, Secure Configuration — the lesson that teaches this.

Question 2Scalable Terraform configuration design

To hand a temporary token from Terraform to a later pipeline step, an engineer adds ephemeral = true to an output block in the root module of a Terraform 1.15 configuration. What is the result?

Choose one.

  • a
    Terraform keeps this root module output out of state but shows it in terraform output

    Root module outputs are stored in state for terraform output to read; an ephemeral root output is not allowed at all.

  • b
    Terraform stores this root module output in state but redacts it in terraform output

    That describes a sensitive output, not an ephemeral one, and the configuration here is rejected.

  • c
    Terraform exposes this ephemeral root module output to terraform_remote_state readers

    Remote state reads root outputs from state, so an ephemeral value could never reach it; the block is rejected.

  • d
    Terraform returns an error because the argument is allowed only on child module outputs Correct

    The ephemeral argument is allowed only on outputs of child modules, which pass ephemeral values up to their caller.

The concept

Ephemeral outputs exist to pass ephemeral values from a child module to its caller; root module outputs cannot be ephemeral.

Why that’s the answer

Root outputs are persisted in state so they can be read later by terraform output or remote state consumers, which contradicts ephemerality. Terraform therefore rejects ephemeral = true on root module outputs and allows it only in child modules. A value that a later pipeline step needs must come from a secrets manager or similar, not a root output.

How to reason it out
  1. Note that the output is in the root module.
  2. Recall that root outputs are persisted in state.
  3. Recall that ephemeral outputs are allowed only in child modules.
  4. Conclude the configuration is rejected.

Exam tip: Ephemeral outputs belong in child modules only; a root module output cannot be ephemeral.

Terraform Professional (002): Turning Requirements into Predictable, Secure Configuration — the lesson that teaches this.

Question 3Scalable Terraform configuration design

A team wants every engineer and its CI pipeline to install exactly the same provider builds on each terraform init, without editing the version constraints in required_providers. What should the team do?

Choose one.

  • a
    Commit the .terraform directory to version control

    The .terraform directory holds downloaded, platform-specific plugins and should not be committed; the lock file is what pins selections.

  • b
    Run terraform init -upgrade at the start of each pipeline run

    The -upgrade flag picks the newest versions the constraints allow, which is the opposite of reproducible installs.

  • c
    Commit the .terraform.lock.hcl file to version control Correct

    The dependency lock file records the selected provider versions and checksums, and terraform init reuses them on every machine.

  • d
    Set required_version to an exact Terraform release

    required_version constrains the Terraform CLI, not the provider builds that init installs.

The concept

The dependency lock file makes provider selection reproducible across machines and runs.

Why that’s the answer

Version constraints describe acceptable ranges; the dependency lock file records the exact provider versions and package checksums chosen within them. When the file is committed, terraform init on any machine installs those same selections until someone deliberately runs init -upgrade. The .terraform directory is local cache, and required_version governs only the CLI.

How to reason it out
  1. Separate constraints (ranges) from selections (exact versions).
  2. Recall that the lock file stores selections and checksums.
  3. Commit it so every init reuses it.
  4. Upgrade deliberately with terraform init -upgrade.

Exam tip: Commit .terraform.lock.hcl so every init installs the same provider builds.

Terraform Professional (002): Turning Requirements into Predictable, Secure Configuration — the lesson that teaches this.

Question 4Scalable Terraform configuration design

A shared module starts using cross-variable validation rules, a feature added in Terraform 1.9. Teams consume the module on every release from 1.9 to 1.15, and some still run 1.8. The module owner wants teams on releases that cannot parse it to get a clear version error, without excluding any release that can. What should the owner add?

Choose one.

  • a
    required_version = "~> 1.9.0" in the shared module's terraform block

    ~> 1.9.0 allows only 1.9.x patch releases, so it wrongly rejects teams on 1.10 through 1.15.

  • b
    required_version = ">= 1.15.0" in the shared module's terraform block

    This rejects every team on 1.9 through 1.14, even though those releases can run the module.

  • c
    required_version = ">= 1.9.0" in the shared module's terraform block Correct

    Terraform checks every module's required_version, so 1.8 users get a clear version error while 1.9 and later are accepted.

  • d
    A Terraform 1.9 entry in the shared module's .terraform.lock.hcl file

    The dependency lock file records provider selections only; it does not record or enforce the Terraform CLI version.

The concept

required_version in any module, including a child module, constrains which Terraform CLI releases can use it.

Why that’s the answer

The requirement is a lower bound only: allow anything that understands the feature and reject older releases with a clear message. required_version is checked for the root module and every child module, so declaring ">= 1.9.0" in the shared module does exactly that. The pessimistic ~> form adds an unwanted upper bound, a 1.15 floor excludes capable releases, and the lock file has no CLI version entry.

How to reason it out
  1. Find the oldest release that supports the features used: 1.9.
  2. Express a lower bound only, since newer releases must keep working.
  3. Put it in the shared module, where Terraform also checks it.
  4. Reject operators that add an upper bound and files that do not track the CLI.

Exam tip: Shared modules declare a minimum Terraform version with >=, not an exact or pessimistic pin.

Terraform Professional (002): Turning Requirements into Predictable, Secure Configuration — the lesson that teaches this.

Question 5Scalable Terraform configuration design

A requirement says each business unit listed in var.business_units (a list of names) gets its own storage bucket. Units are added and removed every quarter, and removing one unit must never cause Terraform to replace or rename another unit's bucket. Which argument should the bucket resource use?

Choose one.

  • a
    count = length(var.business_units)

    Instances are keyed by position, so removing a unit from the middle shifts every later index and plans changes to other buckets.

  • b
    for_each = { for i, u in var.business_units : i => u }

    This uses for_each but keys each instance by list position, so removing one unit still shifts the keys of the units after it.

  • c
    count = length(sort(var.business_units))

    Sorting changes the order but not the indexing; adding or removing a name still shifts positions of other buckets.

  • d
    for_each = toset(var.business_units) Correct

    Instances are keyed by unit name, so removing one unit removes only that instance and leaves the others' addresses unchanged.

The concept

Instance identity follows the key: for_each keyed by a stable business identifier keeps unrelated instances untouched when the collection changes.

Why that’s the answer

The deciding requirement is that removing one unit leaves the rest alone. With count or with for_each keyed by position, each instance's address depends on where its name sits in the list, so deleting an entry shifts later instances and Terraform plans to change or replace them. Converting the list to a set and using for_each keys each instance by the unit name itself, which does not change when other names come and go.

How to reason it out
  1. Ask what each instance's address will be keyed by.
  2. Positions shift when list items are removed; names do not.
  3. Choose for_each with keys that are stable business identifiers.
  4. Reject for_each maps that are still keyed by index.

Exam tip: Key instances by a stable business identifier, never by list position.

Terraform Professional (002): Turning Requirements into Predictable, Secure Configuration — the lesson that teaches this.

Domain 2: Terraform state and backend operations

13% of the Terraform Pro 002 exam, so 3 of the 25 questions on this page.

Question 6Terraform state and backend operations

A team wants state locking on its S3 backend in Terraform 1.15 without creating or paying for any additional AWS resource. Which backend setting should it add?

Choose one.

  • a
    dynamodb_table = "tf-locks"

    DynamoDB-based locking needs a separate table and is deprecated in the S3 backend.

  • b
    use_lockfile = true Correct

    use_lockfile makes the S3 backend lock state with a .tflock object in the same bucket, with no other AWS resource needed.

  • c
    encrypt = true

    encrypt enables server-side encryption of the state and lock files; it does not turn on locking.

  • d
    use_path_style = true

    use_path_style switches to path-style S3 URLs; it has nothing to do with locking.

The concept

The S3 backend can lock state natively with a lock file in the bucket.

Why that’s the answer

Setting use_lockfile = true makes the S3 backend write a lock object (the state key plus .tflock) beside the state, so no DynamoDB table is needed. use_lockfile defaults to false, so locking must be opted into. DynamoDB-based locking still works but is deprecated and needs an extra resource.

How to reason it out
  1. Note the constraint: no additional AWS resource.
  2. Recall that S3 can lock with a .tflock object.
  3. Pick use_lockfile = true.

Exam tip: S3 backend locking today: use_lockfile = true (off by default); DynamoDB locking is deprecated.

Terraform Professional: Remote Backends and Remote State — the lesson that teaches this.

Question 7Terraform state and backend operations

After adding use_lockfile = true to an S3 backend with key "prod/app.tfstate", plans fail with an AccessDenied error. The pipeline role currently allows s3:ListBucket on the bucket and s3:GetObject and s3:PutObject on prod/app.tfstate. Which permission set must be added?

Choose one.

  • a
    s3:PutObject and s3:DeleteObject on prod/app.tfstate in the same bucket

    The lock is a separate object at the .tflock key, so permissions on the state key alone do not cover it.

  • b
    s3:PutObject and s3:ListBucket on the prod/app.tfstate.tflock object

    Creating the lock is not enough (ListBucket is a bucket-level action); Terraform must also read the lock object and delete it to release the lock.

  • c
    dynamodb:PutItem and dynamodb:DeleteItem on the tf-locks lock table

    Those are permissions for the deprecated DynamoDB locking, which this backend no longer uses.

  • d
    s3:GetObject, s3:PutObject and s3:DeleteObject on prod/app.tfstate.tflock Correct

    With use_lockfile, Terraform reads, creates and deletes the lock object, which is the state key with .tflock appended.

The concept

The S3 lock file is its own object and needs its own read, write and delete permissions.

Why that’s the answer

With use_lockfile set, the S3 backend requires s3:GetObject, s3:PutObject and s3:DeleteObject on the lock file, whose key is the state path with .tflock appended. A policy scoped tightly to the state object therefore fails as soon as locking is enabled. Granting only PutObject would let Terraform take a lock it could never release.

How to reason it out
  1. Identify the lock object's key: the state key plus .tflock.
  2. Recall the three actions locking needs: get, put, delete.
  3. Reject DynamoDB permissions, which belong to the old mechanism.

Exam tip: use_lockfile needs Get, Put and Delete on <key>.tflock, not just on the state key.

Terraform Professional: Remote Backends and Remote State — the lesson that teaches this.

Question 8Terraform state and backend operations

Several pipelines share an S3 backend that locks with a DynamoDB table. The team is moving to use_lockfile, but for the next two weeks any job may still run, or be rolled back to, an older commit of the configuration that only sets dynamodb_table. What is the safe cutover?

Choose one.

  • a
    Set use_lockfile and keep dynamodb_table until every job runs the new config Correct

    Both mechanisms can be configured together for migration, so new runs take both locks and still exclude old runs that only check DynamoDB.

  • b
    Set use_lockfile, remove dynamodb_table and delete the table in the same change

    New runs would lock only the .tflock object while old runs lock only DynamoDB, so the two groups could write state concurrently.

  • c
    Keep only dynamodb_table until the cutover, then switch every job to use_lockfile

    Rollbacks can run the older commit after the switch; those runs lock only DynamoDB while new runs lock only the .tflock object, so the two could write state at once.

  • d
    Set use_lockfile, remove dynamodb_table and run old jobs with -lock=false meanwhile

    Disabling locking on the old jobs removes the protection entirely during the window it is most needed.

The concept

During a lock migration, configure both DynamoDB and lock-file locking until no run relies on DynamoDB alone.

Why that’s the answer

Locking only protects runs that check the same lock. The S3 backend documents that its S3 and DynamoDB locking arguments can be configured simultaneously to support migration: new runs then acquire both locks, so they still conflict with old runs that check only DynamoDB. Once every job uses the new configuration, dynamodb_table (deprecated) can be removed and the table deleted.

How to reason it out
  1. Note that old and new commits can run side by side for two weeks, including rollbacks.
  2. Recall that a lock only excludes runs checking the same lock.
  3. Recall that both locking arguments may be set together for migration.
  4. Remove DynamoDB only after the overlap ends.

Exam tip: Migrate S3 locking by running both locks in parallel, then drop dynamodb_table.

Terraform Professional: Remote Backends and Remote State — the lesson that teaches this.

Domain 3: Infrastructure lifecycle and operations

22% of the Terraform Pro 002 exam, so 6 of the 25 questions on this page.

Question 9Infrastructure lifecycle and operations

One root directory serves dev and prod with partial backend files dev.s3.tfbackend and prod.s3.tfbackend. An engineer initialised with the dev file and planned dev. They now run init with the prod file, and Terraform reports that the backend configuration has changed. Which command lets them plan prod without copying dev's state to prod's state key?

Choose one.

  • a
    terraform init -migrate-state -backend-config=prod.s3.tfbackend

    -migrate-state offers to copy the current (dev) state into the new backend, which is exactly what must not happen.

  • b
    terraform init -force-copy -backend-config=prod.s3.tfbackend

    -force-copy answers the migration prompts with yes, copying dev's state to prod without asking.

  • c
    terraform init -reconfigure -backend-config=prod.s3.tfbackend Correct

    -reconfigure ignores the saved backend settings and attaches to the prod backend without migrating any state.

  • d
    terraform init -upgrade -backend-config=prod.s3.tfbackend

    -upgrade updates providers and modules within constraints; it does not resolve a backend configuration change.

The concept

When the backend settings change, init needs -reconfigure (start fresh against the new backend) or -migrate-state (copy existing state across).

Why that’s the answer

Switching environments in one directory is not a migration: prod already has its own state. -reconfigure discards the saved dev backend settings and connects to prod without copying anything. -migrate-state and -force-copy copy the dev state into prod's key, overwriting or corrupting prod. -upgrade is about dependency versions.

How to reason it out
  1. Recognise that the backend settings in .terraform changed.
  2. Decide whether state should move: it should not.
  3. Pick the init flag that reattaches without migration.

Exam tip: Switching environments with partial backend files: init -reconfigure, never -migrate-state.

Terraform Professional: Scaling Projects and Multiple Environments — the lesson that teaches this.

Question 10Infrastructure lifecycle and operations

Dev and prod plans run as parallel CI jobs on one shared runner, from the same checkout directory. Each job runs terraform init -reconfigure with its own partial backend file, then plan. Occasionally the prod plan reads the dev state. Which change stops this with the least restructuring?

Choose one.

  • a
    Give each job its own TF_DATA_DIR value for all of its terraform commands Correct

    TF_DATA_DIR moves the per-directory data, including the saved backend settings, so each job keeps its own copy instead of overwriting the other's.

  • b
    Add -lock-timeout=5m so each job waits for the other job's state lock

    Locks are per state file; dev and prod lock different states, and the clash is in the shared .terraform directory, not in state.

  • c
    Run init with -migrate-state so each job copies the correct state first

    Migration copies state between backends; it would move dev state into prod rather than isolate the jobs.

  • d
    Run both jobs with -parallelism=1 so graph operations happen one at a time

    -parallelism limits concurrent operations inside one run; it does nothing about two processes sharing one working directory.

The concept

Backend settings chosen at init are saved in the working directory's data dir (.terraform by default); TF_DATA_DIR relocates it and must be consistent from init onward.

Why that’s the answer

Both jobs write their backend choice into the same .terraform directory, so whichever init runs last decides which state the other job's plan reads. Giving each job its own TF_DATA_DIR keeps two independent copies of that data while the code stays shared. Lock timeouts do not help because the two jobs lock different states, -migrate-state would copy state between environments, and -parallelism controls concurrency within a single run.

How to reason it out
  1. Locate where init saves the selected backend: the data directory.
  2. See that two jobs overwrite one shared data directory.
  3. Recall that TF_DATA_DIR relocates it per process, set for every command from init on.
  4. Reject fixes aimed at state locks or graph concurrency.

Exam tip: Two environments in one checkout at the same time: separate TF_DATA_DIR per job.

Terraform Professional: Scaling Projects and Multiple Environments — the lesson that teaches this.

Question 11Infrastructure lifecycle and operations

Each environment must have its own backend, its own provider credentials and its own Terraform version constraint, and reviewers must see in the repository exactly what configuration prod runs. Which repository layout fits?

Choose one.

  • a
    One root module with a workspace and a .tfvars file for each environment

    Workspaces share one backend and one configuration, so the environments cannot have different backends or version constraints.

  • b
    environments/dev and environments/prod symlinking one shared set of .tf files

    Symlinked files are one configuration seen twice: the backend block and required_version are shared, so neither can differ per environment.

  • c
    One root module with for_each over an environments map of settings

    for_each repeats resources inside one configuration and state; it cannot give each environment its own backend.

  • d
    environments/dev and environments/prod roots calling shared modules Correct

    Each environment root has its own backend, credentials and required_version, and the prod directory shows exactly what prod runs.

The concept

A directory per environment makes each environment a separate root configuration with its own backend, credentials and constraints, built from shared modules.

Why that’s the answer

HashiCorp's alternative to workspaces is a separate configuration per deployment that instantiates common modules against its own backend. Every requirement here is a property of a root configuration: backend, credentials, required_version, and the code a reviewer reads. Workspaces and for_each keep the environments in one configuration, and directories that symlink the same files are still one configuration with one backend block and one version constraint.

How to reason it out
  1. List the requirements: backend, credentials, version constraint, visible prod config.
  2. Note that each is set in the root configuration's own files.
  3. Choose one root per environment, with its own backend and versions files, built from shared modules.

Exam tip: Per-environment backend, credentials or Terraform version: a root module per environment.

Terraform Professional: Scaling Projects and Multiple Environments — the lesson that teaches this.

Question 12Infrastructure lifecycle and operations

An architect compares a directory-per-environment layout (one root module per environment calling shared modules) with CLI workspaces in one directory. Which two statements about the directory layout are accurate? (Select TWO.)

Choose TWO.

  • a
    All environments share one state file, so one apply updates every environment

    Each root has its own state; there is no shared state across the directories.

  • b
    Each environment can use a different backend and different credentials Correct

    Each directory is its own root configuration with its own backend block and run-time credentials.

  • c
    A change must be applied in each environment's directory, so roots can diverge Correct

    Every root is planned and applied separately, so environments differ until a change is deliberately promoted to each.

  • d
    terraform.workspace returns the name of the environment's directory

    terraform.workspace returns the CLI workspace name, which is default in each directory unless one is created.

  • e
    Shared modules cannot be used, so each directory repeats its resource blocks

    Calling shared modules from each root is the documented pattern; only backend and module blocks need to differ.

The concept

Separate roots trade isolation (backend, credentials, versions) for separate applies that must be promoted environment by environment.

Why that’s the answer

The directory layout gives strong isolation because each root is independent, and the same independence means a change reaches an environment only when someone applies that directory. Shared modules keep the duplication small. Each directory still runs in the default CLI workspace, so terraform.workspace does not identify the environment.

How to reason it out
  1. Recall that each directory is a full root configuration.
  2. Derive the benefit: independent backend and credentials.
  3. Derive the cost: separate applies, possible divergence.

Exam tip: Directory per environment: isolation by default, promotion by deliberate apply.

Terraform Professional: Scaling Projects and Multiple Environments — the lesson that teaches this.

Question 13Infrastructure lifecycle and operations

Environment roots dev, staging and prod each call a private-registry network module with version = "3.4.0". Release 3.5.0 changes the subnet layout. The team wants it in dev first and in prod only after a week in staging, without branching the configuration. What should they do?

Choose one.

  • a
    Bump dev's pin to "3.5.0" and init; repeat in staging, then in prod Correct

    Each root pins its own module version, so bumping one root at a time promotes the release environment by environment.

  • b
    Set "~> 3.4" in dev, staging and prod so each takes 3.5.0 at next init

    All three roots would accept 3.5.0 as soon as they are next initialised, so prod gets the change without the staging week.

  • c
    One root with dev, staging and prod workspaces; version from terraform.workspace

    All workspaces share one configuration and one module tree installed at init, so one checkout cannot run 3.4.0 for prod and 3.5.0 for dev; terraform.workspace is not a const value usable in version.

  • d
    Publish 3.5.0 and run init -upgrade in dev, leaving staging and prod as is

    With an exact pin of 3.4.0, -upgrade stays on 3.4.0, so dev never receives the new release.

The concept

Pinning a module version in each environment root lets a release be promoted one environment at a time; workspaces of one configuration share a single installed module version.

Why that’s the answer

Promotion needs each environment to choose its own module version. In a directory-per-environment layout each root's exact pin is that choice, so the team bumps dev, then staging, then prod. A pessimistic constraint lets every root jump on its next init. Workspaces share one configuration and one set of modules installed by init, so they cannot run two releases side by side, and terraform.workspace is not a const value. -upgrade cannot leave an exact pin.

How to reason it out
  1. Identify the goal: different module versions per environment for a while.
  2. Recall that modules are installed per working directory at init, so the version can only differ between configurations.
  3. Recall that an exact pin stops -upgrade, while ~> lets every root move.
  4. Bump the pin per root in promotion order.

Exam tip: Promote module releases by bumping each environment root's exact pin in turn.

Terraform Professional: Scaling Projects and Multiple Environments — the lesson that teaches this.

Question 14Infrastructure lifecycle and operations

A team runs dev and prod as CLI workspaces of one configuration. On Monday an engineer merged a module refactor to main and applied it in dev. On Tuesday a hotfix must reach prod without the refactor, and both changes are now on main. What problem will the prod apply from main have?

Choose one.

  • a
    It waits until the dev workspace's state lock from Monday has been released

    Each workspace has its own state and lock; a finished dev apply holds no lock that would block prod.

  • b
    It still uses Monday's configuration, which init saved for the prod workspace

    Workspaces store state only; the run reads whatever configuration is in the directory now.

  • c
    It skips the refactor because the refactor was already applied to the dev state

    Applying in dev changes dev's state only; prod's plan compares main's configuration with prod's own state.

  • d
    It also applies the refactor, since workspaces share one configuration Correct

    Workspaces separate state, not code, so a prod run from main includes every change on main, refactor included.

The concept

CLI workspaces share one configuration, so environments cannot run different code from the same commit.

Why that’s the answer

Every workspace evaluates whatever configuration is checked out. The refactor is on main, so any prod run from main includes it. Keeping prod behind requires a different commit for prod, or a layout where environments pin versions independently. Locks and state belong to each workspace and do not carry code between them.

How to reason it out
  1. Recall what a workspace separates: state only.
  2. See that main contains both the refactor and the hotfix.
  3. Conclude that the prod run includes both.

Exam tip: Workspaces split state, not code: what's on the commit goes to every environment you apply.

Terraform Professional: Scaling Projects and Multiple Environments — the lesson that teaches this.

Domain 4: CI/CD and automation workflows

10% of the Terraform Pro 002 exam, so 3 of the 25 questions on this page.

Question 15CI/CD and automation workflows

Merges to main deploy to staging. The prod workspace is connected to the same repository and must queue a run only when a release engineer pushes a tag such as v2.4.0 on a tested commit, never on an ordinary merge to main. Which prod workspace setting meets this?

Choose one.

  • a
    Specified paths, with the trigger pattern set to v*.*.*

    Trigger patterns are matched against changed file paths, not tag names, so pushing a tag would not queue a run.

  • b
    Git tag triggers with the semantic versioning tag format Correct

    Tag-based triggering queues runs only for tags that match the chosen format, regardless of the VCS branch setting.

  • c
    The VCS branch field set to v* so that version refs are tracked

    The VCS branch setting names a branch to track; it is not a tag filter.

  • d
    Automatic speculative plans turned off for the workspace

    This only stops plan-only runs for pull requests; merges to main would still queue runs.

The concept

A VCS-driven workspace can trigger runs from Git tags that match a SemVer or custom regex format.

Why that’s the answer

HCP Terraform's VCS settings let a workspace begin new runs only for changes that have a specific tag format, either semantic versioning or a custom regex, and runs are triggered for all matching tags regardless of the VCS branch setting. That gives a release-by-tag promotion model for prod. Trigger patterns are file globs, the branch setting names a branch, and speculative plans concern pull requests.

How to reason it out
  1. Identify the release signal: a version tag.
  2. Recall that workspaces can trigger on tags matching SemVer or a regex.
  3. Rule out settings that filter files or branches.

Exam tip: Release-by-tag for prod: Git tag triggers with a SemVer format.

Terraform Professional: CI/CD Pipelines and Deployment Strategies — the lesson that teaches this.

Question 16CI/CD and automation workflows

A platform engineer is configuring VCS-driven workspaces in HCP Terraform and reviews these claims about run-trigger settings. Which TWO claims are correct? (Select TWO.)

Choose TWO.

  • a
    Trigger prefixes require the workspace to have a working directory set Correct

    The VCS settings documentation states that a working directory is required when trigger prefixes are used.

  • b
    Trigger patterns take regular expressions, while prefixes take glob syntax

    Trigger patterns use glob syntax and trigger prefixes are plain directory paths; neither takes regular expressions (only the custom Git tag format does).

  • c
    The always-trigger mode reacts just to changes inside the working directory

    Always trigger begins a run when changes are pushed to any file in the repository.

  • d
    Turning off automatic speculative plans also stops runs from tag pushes

    Speculative plans concern pull requests; tag-triggered runs are governed by the tag setting.

  • e
    With Git tag triggers, every matching tag queues a run whatever the branch Correct

    Tag-triggered runs fire for every matching tag; the VCS branch setting does not restrict them.

The concept

Run-trigger settings decide which pushes queue runs: any file, specified paths (patterns or prefixes), or matching Git tags.

Why that’s the answer

Trigger patterns are the recommended glob-based option; trigger prefixes list directories and need a working directory. Git tag triggers queue runs for all tags matching the SemVer or regex format regardless of the VCS branch setting. 'Always trigger' reacts to pushes to any file in the repository, and automatic speculative plans (on by default) only cover pull-request plans.

How to reason it out
  1. Check each claim against the three triggering modes.
  2. Recall that prefixes need a working directory.
  3. Recall that tag triggers ignore the branch setting.
  4. Reject claims that swap pattern types or narrow 'Always trigger'.

Exam tip: Prefixes need a working directory; tag triggers ignore the VCS branch.

Terraform Professional: CI/CD Pipelines and Deployment Strategies — the lesson that teaches this.

Question 17CI/CD and automation workflows

A Jenkins pipeline drives an HCP Terraform workspace through the API-driven workflow. For pull requests it uploads the branch's configuration so reviewers can see a plan, and that run must be impossible to confirm or apply, even by a workspace admin. Which attribute should the pipeline set when it creates the pull request's configuration version?

Choose one.

  • a
    auto-queue-runs set to false

    This stops a run being queued on upload; a run created later could still be confirmed and applied.

  • b
    speculative set to true Correct

    A speculative configuration version can only create speculative runs, which can be neither confirmed nor applied.

  • c
    provisional set to true

    A provisional version is not made current until its run applies, so that run can still be confirmed and applied.

  • d
    auto-apply set to false

    auto-apply is a workspace setting; with manual apply an admin can still confirm the plan.

The concept

API-driven runs start from a configuration version, whose attributes control how its runs behave.

Why that’s the answer

The configuration-versions API says that when speculative is true the version may only be used to create speculative runs, which cannot be confirmed or applied. auto-queue-runs (default true) only controls whether a run is queued automatically on upload, provisional (default false) delays the version becoming current until its run is applied, and manual apply still lets an authorised user confirm.

How to reason it out
  1. Requirement: a plan nobody can apply.
  2. Find the attribute that limits the version to speculative runs.
  3. Rule out attributes that only change queuing or currency.

Exam tip: Plan-only uploads in the API workflow: create the configuration version with speculative: true.

Terraform Professional: CI/CD Pipelines and Deployment Strategies — the lesson that teaches this.

Domain 5: Terraform security and governance

13% of the Terraform Pro 002 exam, so 3 of the 25 questions on this page.

Question 18Terraform security and governance

Terraform 1.15 with a current AWS provider creates an application API secret in Secrets Manager. Today a random_password resource feeds the secret_string of an aws_secretsmanager_secret_version, and the value is in state. Security requires that no copy of the secret is ever in state and that it rotates each quarter through a reviewed pull request. Which design meets both requirements?

Choose one.

  • a
    A random_password resource into secret_string_wo; bump secret_string_wo_version to rotate

    The secret version no longer stores the value, but the managed random_password resource stores its result in state.

  • b
    An ephemeral password into secret_string_wo; bump secret_string_wo_version to rotate Correct

    The ephemeral password and the write-only argument never persist, and only a change to the version argument makes the provider write a new value.

  • c
    An ephemeral password into secret_string with sensitive set; replace the version to rotate

    secret_string is an ordinary argument, so referencing an ephemeral value there fails at plan.

  • d
    An ephemeral password into secret_string_wo; change password_length to rotate

    Terraform cannot detect a change in a write-only value, so nothing is written unless the version argument changes.

The concept

Write-only arguments plus an ephemeral source keep a generated secret out of state; the paired version argument is the rotation trigger.

Why that’s the answer

Two facts decide it. A secret is absent from state only if every hop is non-persistent: an ephemeral generator (for example ephemeral aws_secretsmanager_random_password or ephemeral random_password) and a write-only argument (secret_string_wo). And because Terraform never stores or diffs write-only values, rotation happens only when secret_string_wo_version changes, which fits a reviewed pull request. A managed random_password leaks into state; an ephemeral value cannot go into secret_string; changing a generator input produces no diff.

How to reason it out
  1. List every place the secret passes through and ask which ones persist.
  2. Replace the managed generator with an ephemeral one.
  3. Use the write-only argument on the secret version.
  4. Recall that the version argument, not the value, triggers the update.

Exam tip: Ephemeral source + write-only argument = nothing in state; bump *_wo_version to rotate.

Terraform Professional (002): Secrets, Rotation and Pipeline Identities — the lesson that teaches this.

Question 19Terraform security and governance

A team stores a generated token in AWS Secrets Manager with the aws_secretsmanager_secret_version resource. Terraform 1.15 must write the value without storing it in state or plan files. Which arguments should the resource use?

Choose one.

  • a
    secret_string_wo with secret_string_wo_version Correct

    secret_string_wo is write-only and never persisted; the version argument tracks when to write it.

  • b
    secret_string with a sensitive output referencing it

    secret_string is stored in state; marking an output sensitive only hides it from display.

  • c
    secret_string with lifecycle ignore_changes on it

    ignore_changes suppresses diffs but the value is still recorded in state.

  • d
    secret_binary with a base64encode of the token

    secret_binary is an ordinary argument and is stored in state like secret_string.

The concept

Providers expose write-only variants of secret arguments, usually suffixed _wo and paired with a _wo_version argument.

Why that’s the answer

Write-only arguments (Terraform 1.11 and later) are sent to the provider during the operation and then discarded, so they never appear in state or plan. aws_secretsmanager_secret_version offers secret_string_wo with secret_string_wo_version. The other options all persist the value; sensitivity and ignore_changes change display and diffing, not storage.

How to reason it out
  1. Recognise the requirement: written but never stored.
  2. Recall the _wo / _wo_version naming of write-only arguments.
  3. Reject options that only hide or ignore a stored value.

Exam tip: To write a secret without storing it, use the resource's _wo argument and its version.

Terraform Professional (002): Secrets, Rotation and Pipeline Identities — the lesson that teaches this.

Question 20Terraform security and governance

A team creates an Amazon RDS for PostgreSQL database with Terraform. The administrator password must rotate automatically on a schedule, applications must read it from AWS Secrets Manager, and Terraform must never generate or handle the password value. Which aws_db_instance setting meets these requirements?

Choose one.

  • a
    password_wo from an ephemeral random password, with password_wo_version

    This keeps the value out of state, but Terraform still generates and handles it, and rotation needs a manual version bump.

  • b
    password from an aws_secretsmanager_secret_version data source

    Terraform reads and handles the value, stores it in state, and nothing rotates it on a schedule.

  • c
    password_wo from Secrets Manager, plus an aws_secretsmanager_secret_rotation

    Terraform still handles the value; a rotation on a separate secret would leave the database password out of sync.

  • d
    manage_master_user_password = true in the aws_db_instance Correct

    RDS generates the master password, stores it in Secrets Manager and rotates it automatically; Terraform never sees the value.

The concept

When the service can own a secret, let it: RDS-managed master passwords live in Secrets Manager and rotate without Terraform touching them.

Why that’s the answer

Several options keep the password out of state, but the deciding constraint is that Terraform must never generate or handle the value and rotation must be automatic. With manage_master_user_password = true, RDS creates the password in Secrets Manager and rotates it automatically (every 7 days by default); consumers use the secret ARN exposed in master_user_secret. Write-only designs still have Terraform supply the value.

How to reason it out
  1. Note the constraint: Terraform must never handle the value.
  2. Eliminate designs where Terraform supplies the password, write-only or not.
  3. Choose the setting that hands generation and rotation to the service.

Exam tip: Best secret handling for a managed database: let the service own and rotate it.

Terraform Professional (002): Secrets, Rotation and Pipeline Identities — the lesson that teaches this.

Domain 6: Advanced module usage and registry practices

10% of the Terraform Pro 002 exam, so 2 of the 25 questions on this page.

Question 21Advanced module usage and registry practices

A shared module has grown to 60 input variables and many create_* booleans because each team enables a different subset of its resources. A change made for one team regularly breaks another team's plan. Which refactor best follows HashiCorp's module composition guidance?

Choose one.

  • a
    Split it into small modules that root modules compose, passing dependencies as inputs Correct

    Small composable modules let each root combine only what it needs, and dependency inversion replaces internal conditionals.

  • b
    Add more create_* flags with defaults so that each team's subset remains optional

    More conditional branches make the module harder to reason about and keep every team coupled to one codebase.

  • c
    Wrap the module in per-team modules that set its create_* flags for each team

    Wrappers add a nesting level and duplicated inputs while the shared core, and its coupling, stays the same.

  • d
    Collapse the 60 inputs into a single settings variable with no type constraint

    Without a type constraint the variable accepts anything, which removes the contract's type checking without reducing the coupling between teams.

The concept

HashiCorp prefers a flat tree of small modules composed in the root, with dependencies passed in, over modules full of conditional creation flags.

Why that’s the answer

The composition guidance recommends keeping modules small, keeping the module tree flat, and having callers decide what exists and pass it in, instead of building complex conditional branches inside a module. Splitting the large module lets each team's root compose just the pieces it needs, so a change to one piece no longer reaches unrelated teams. Per-team wrappers also work mechanically but add nesting and keep the shared core; more flags or an untyped object variable make the contract weaker.

How to reason it out
  1. Recognise the anti-pattern: conditional creation flags serving many audiences.
  2. Recall the composition guidance: small modules, flat tree, dependencies as inputs.
  3. Compare wrappers with composition: wrappers keep the coupled core.
  4. Choose the option that removes the shared conditional core.

Exam tip: Compose small modules in the root instead of toggling a large one with flags.

Terraform Professional (002): Refactoring into Modules and the Module Contract — the lesson that teaches this.

Question 22Advanced module usage and registry practices

An engineer extracts a root module's aws_s3_bucket.logs resource into a new local module called as module "logs", where the resource is named aws_s3_bucket.this. The existing bucket must not be destroyed, and the change must appear in the reviewed plan. What should the engineer add for this resource?

Choose one.

  • a
    A moved block in the new child module, from aws_s3_bucket.logs to aws_s3_bucket.this

    A module can only make moved statements about its own objects and its children's; the old address belongs to the root module.

  • b
    A moved block in the root module, from aws_s3_bucket.logs to module.logs.aws_s3_bucket.this Correct

    The root owns both the old address and the module call, so it can record the move and the plan shows it.

  • c
    A moved block in the root module, from module.logs.aws_s3_bucket.this to aws_s3_bucket.logs

    from and to are reversed; this would describe moving the object out of the module.

  • d
    An import block in the root module, to module.logs.aws_s3_bucket.this, with the bucket name

    Import adds a second state binding but leaves aws_s3_bucket.logs in state with no configuration, so the plan would destroy the bucket.

The concept

When resources are extracted into a module, a moved block in the calling module maps each old address to its new module address.

Why that’s the answer

Without a refactoring record, Terraform sees the old address removed and a new one added, so it plans destroy-and-create. A moved block with from = aws_s3_bucket.logs and to = module.logs.aws_s3_bucket.this, placed in the root (which owns the old address and the child module call), renames the object in state during planning, and the move is visible in the plan. The child module cannot refer to its caller's addresses, and an import block leaves the old binding behind.

How to reason it out
  1. Identify the old and new addresses.
  2. Decide which module owns both addresses: the root.
  3. Check the direction: from is the old address, to is the new one.
  4. Rule out import, which does not remove the old binding.

Exam tip: Extracting into a module: moved from the old root address to module.<name>.<address>, declared in the root.

Terraform Professional (002): Refactoring into Modules and the Module Contract — the lesson that teaches this.

Domain 7: HCP Terraform and Terraform Enterprise operations

12% of the Terraform Pro 002 exam, so 3 of the 25 questions on this page.

Question 23HCP Terraform and Terraform Enterprise operations

Cost estimation is enabled for an HCP Terraform organization, and runs in an AWS workspace normally show their estimated monthly cost. During an incident, an engineer runs terraform plan -target=module.cache through the CLI-driven workflow, and that run shows no cost estimate. What is the most likely reason?

Choose one.

  • a
    Runs started through the CLI skip cost estimation

    CLI-driven runs execute remotely like any other run and get the cost estimation phase.

  • b
    Runs that use resource targeting skip cost estimation Correct

    HCP Terraform disables cost estimation for runs that use resource targeting.

  • c
    Runs touching one module estimate cost after the apply

    Estimates are produced between plan and apply; there is no post-apply estimate for module-scoped runs.

  • d
    Runs with unsupported resources skip the whole estimate

    Unsupported resources are listed as not estimated, while the rest of the run is still estimated.

The concept

When cost estimation does not run

Why that’s the answer

HCP Terraform documents that cost estimation is disabled for runs that use resource targeting, which is why a -target plan has no estimate (and why a cost policy can fail unexpectedly on such runs). The CLI-driven workflow itself is not the cause, and resources that cannot be priced are reported separately rather than suppressing the estimate.

How to reason it out
  1. Spot the difference from normal runs: the -target option.
  2. Recall that targeted runs are excluded from cost estimation.
  3. Reject causes that would affect every CLI run or every run with unpriced resources.

Exam tip: -target runs get no cost estimate.

Terraform Professional (002): Operating HCP Terraform and Tracking Usage and Cost — the lesson that teaches this.

Question 24HCP Terraform and Terraform Enterprise operations

A Sentinel policy, run as a policy check, reads the tfrun import's cost_estimate namespace to cap how much a run may raise a workspace's estimated monthly spend. Which TWO statements about that data are correct? (Select TWO.)

Choose TWO.

  • a
    The namespace is present with zero values when no estimate exists

    The namespace is only present when a cost estimate is available; it is not filled with zeros.

  • b
    delta_monthly_cost can be negative when the plan lowers the estimate Correct

    The delta is proposed minus prior, so a plan that removes or downsizes resources gives a negative value.

  • c
    Each value is a string holding a decimal number, such as "0.0" Correct

    The cost fields are strings containing decimals, so policies convert them (for example with the decimal import) before comparing.

  • d
    proposed_monthly_cost sums just the resources the plan changes

    proposed_monthly_cost is the whole estimate if the plan were applied, not just the changed resources.

  • e
    prior_monthly_cost comes from the provider's last monthly invoice

    prior_monthly_cost is HCP Terraform's estimate at the start of the plan, not billing data from the cloud provider.

The concept

The cost_estimate data available to Sentinel policy checks

Why that’s the answer

tfrun.cost_estimate exposes prior_monthly_cost (the estimate before the plan), proposed_monthly_cost (the estimate if applied) and delta_monthly_cost (their difference, which can be negative). All three are strings holding decimals, and the namespace only exists when an estimate is available, so a policy must handle its absence (for example on targeted runs).

How to reason it out
  1. List the three fields and what each compares.
  2. Recall the type: decimal strings, not numbers.
  3. Recall that the namespace is absent without an estimate.
  4. Keep the two statements that match.

Exam tip: cost_estimate: prior, proposed, delta, all decimal strings, present only with an estimate.

Terraform Professional (002): Operating HCP Terraform and Tracking Usage and Cost — the lesson that teaches this.

Question 25HCP Terraform and Terraform Enterprise operations

Continuous validation runs in about 300 HCP Terraform workspaces. Before a release freeze, the platform lead needs one list of every workspace where at least one check or custom condition is currently failing, without opening each workspace. What should she use?

Choose one.

  • a
    The Explorer's Workspaces with failed checks use case Correct

    This built-in Explorer query lists workspaces that failed at least one continuous validation check.

  • b
    A Check failed notification added to each workspace

    Notifications alert on future events per workspace; they do not produce a current list across the organization.

  • c
    The Usage page's breakdown of workspaces by project

    The Usage page reports managed resources and limits, not check results.

  • d
    The organization audit trail filtered to check events

    Audit trails record actions in the organization; they are not a report of current check status.

The concept

Querying workspace health across an organization

Why that’s the answer

The Explorer queries data across the organization's workspaces, modules, providers and Terraform versions. Its built-in use cases include Drifted workspaces and Workspaces with failed checks, which lists every workspace that failed at least one continuous validation check, sortable in one table.

How to reason it out
  1. Note the need: a current, organization-wide list of failing checks.
  2. Recall the Explorer's pre-built use cases.
  3. Pick the query that reports failed continuous validation checks.

Exam tip: Organization-wide health questions: ask the Explorer.

Terraform Professional (002): Operating HCP Terraform and Tracking Usage and Cost — the lesson that teaches this.

Keep going with Terraform Pro 002

Terraform Pro 002 practice by domain

Terraform Pro 002 sample questions: your questions

No. Real exam questions are confidential. These are original questions written to the skills in the official Terraform Pro 002 exam guide, so they test the same knowledge in the same style without being copies of the exam.

Source

The domains and weights on this page come from the official Terraform Pro 002 exam guide.