Core Terraform workflow
This domain covers the day-to-day command sequence every Terraform practitioner runs: write → plan → apply, plus the supporting commands terraform init, terraform validate, terraform fmt, and terraform destroy. HashiCorp does not publish per-objective weightings; the ~13% figure used here is an even split across the exam's objective groups, and the commonly cited "57 questions / 70% to pass / 60 minutes" numbers are carried over from version 003 and should be treated as estimates. Regardless of weighting, this is the highest-yield domain on the exam: command flags, plan symbols, and "which command do I run next?" scenarios appear throughout the other domains too. Expect precise questions about exact flag names, defaults, exit codes, what each command does and does not do, and when re-initialization is required.
Describe the Terraform workflow: write, plan, apply
The core Terraform workflow has exactly three steps:
| Step | What happens | Primary commands |
|---|---|---|
| Write | Author configuration in HCL files (.tf, .tf.json), iterating locally | editor, terraform fmt, terraform validate |
| Plan | Terraform reads state, refreshes real-world status, and computes the diff between desired and current state | terraform plan |
| Apply | Terraform executes the planned actions against provider APIs in dependency order and writes new state | terraform apply |
terraform init is a prerequisite/setup step, not one of the three core steps. terraform destroy is the teardown of the lifecycle, not a core workflow step. The exam loves the distractor "write, init, plan, apply" — reject it when the question asks for the core workflow.
How the same workflow scales:
| Context | Write | Plan | Apply |
|---|---|---|---|
| Individual | Local files, no VCS coordination required | Run terraform plan locally, inspect output | terraform apply locally, type yes |
| Team | Files in version control, feature branches, pull requests | Plan output pasted into PR for peer review; state stored in a shared remote backend with locking | Apply after merge, ideally from one place to avoid concurrent runs |
| HCP Terraform / Terraform Enterprise | VCS-connected workspace | Speculative plans on PRs, remote plan execution, policy checks (Sentinel/OPA) | Remote apply with mandatory confirmation or auto-apply; state stored and versioned remotely |
Caption: the full command lifecycle, with the three core workflow steps highlighted in sequence.
Common traps
- Calling
initpart of the core workflow. The core workflow is write, plan, apply only. - Assuming
planchanges infrastructure. Plan is read-only against infrastructure — it never creates, modifies, or deletes real resources (it may update state metadata only when you explicitly use-refresh-onlyon apply). - Assuming
applyalways runs a plan. It does — unless you hand it a saved plan file, in which case it executes that plan verbatim. - Thinking a speculative plan in HCP Terraform can apply. Speculative plans cannot be applied; they are plan-only.
- Confusing "the core workflow" with "the enterprise workflow." The steps are identical; only where they run and who reviews them changes.
Initialize a Terraform working directory
terraform init prepares a working directory for use. It is safe to run multiple times (idempotent) and is the first command you run in a new or cloned configuration.
What init does, in order:
- Backend initialization — reads the
backend/cloudblock, configures state storage, and offers to migrate existing state if the backend changed. - Module installation — downloads/copies every
modulesource into.terraform/modules/and writesmodules.json. - Provider plugin installation — resolves version constraints, downloads plugins from the Terraform Registry (
registry.terraform.ioby default) into.terraform/providers/. - Dependency lock file — creates or updates
.terraform.lock.hclin the working directory with selected provider versions and checksums.
The .terraform directory:
| Path | Contents |
|---|---|
.terraform/providers/<host>/<namespace>/<type>/<version>/<os_arch>/ | Provider plugin binaries |
.terraform/modules/ | Downloaded module sources plus modules.json manifest |
.terraform/terraform.tfstate | Backend configuration cache (not your resource state when using a remote backend) |
.terraform/environment | Name of the currently selected workspace |
.terraform.lock.hcl | Outside .terraform — provider dependency lock; commit this to version control |
.terraform/ is machine-local and belongs in .gitignore. .terraform.lock.hcl must be committed so teammates and CI get identical provider versions.
Key init flags:
| Flag | Effect |
|---|---|
-backend=false | Skip backend initialization (useful when you only want plugins) |
-backend-config=PATH or -backend-config="k=v" | Supply partial backend configuration (secrets, per-environment values) |
-reconfigure | Reinitialize, ignoring existing backend state; does not migrate state |
-migrate-state | Reinitialize and copy existing state to the newly configured backend |
-force-copy | Like -migrate-state but auto-answers "yes" to migration prompts |
-upgrade | Re-resolve providers/modules to the newest versions allowed by constraints and update the lock file |
-get=false | Skip module downloads |
-plugin-dir=PATH | Use only plugins from a local directory (disables registry downloads) |
-lockfile=readonly | Fail rather than update .terraform.lock.hcl |
-input=false | Never prompt — standard in automation |
-from-module=SOURCE | Copy a module's source into the current empty directory, then init |
When do you need to re-run init?
| Change | Re-init required? |
|---|---|
Added a new provider / new resource type from a new provider | Yes |
| Changed a provider version constraint | Yes (with -upgrade if loosening to a newer version) |
Added, removed, or changed a module source or version | Yes |
Changed the backend/cloud block | Yes (-reconfigure or -migrate-state) |
| Cloned a repo to a new machine or fresh CI runner | Yes |
| Changed a resource argument, variable, or output | No |
Switched workspace with terraform workspace select | No |
Caption: decision tree for choosing the right terraform init flag.
Worked scenario: A team has been running Terraform with local state. They add an s3 backend block and rerun terraform init. Terraform detects the backend change and prompts to copy existing state. They want the existing resources preserved in S3 but must run non-interactively in CI.
Reasoning: Preserving state = migration, so -reconfigure is wrong (it would discard the association and leave local state behind). Non-interactive migration = terraform init -migrate-state -force-copy (or -migrate-state -input=false with -force-copy). Answer: -migrate-state, with -force-copy to suppress the prompt.
Common traps
- Believing
initdownloads providers into a global directory by default. They go into the working directory's.terraform/providers/unlessTF_PLUGIN_CACHE_DIRor a filesystem mirror is configured. - Confusing
-reconfigurewith-migrate-state.-reconfigurediscards the old backend association without migrating state. - Thinking
-upgradeignores version constraints. It upgrades only within the constraints inrequired_providers. - Thinking
.terraform.lock.hclshould be gitignored. It should be committed;.terraform/should not. - Thinking
terraform getis a substitute forinit.terraform getinstalls modules only — no backend, no providers, no lock file. - Assuming
initvalidates configuration correctness. It parses enough to resolve providers/modules but is not a substitute forvalidate. - Believing running
inittwice is destructive. It is safe and idempotent.
Unlock with a paid plan
8 more sections (about 3,005 words) continue below. Every paid plan unlocks all 004 study guides, the full question bank and mock exams, from $4.97/month.