What this command does in detail
- Refresh. Reads the current state, and checks the real Azure resources it describes, to see if anything has drifted (for example, someone editing a resource by hand in the Portal).
- Diff. Compares the refreshed picture of reality against what the .tf files say should exist.
- Report. Prints a human-readable list of every resource that would be created (+), changed in place (~), destroyed (-), or replaced (destroy then re-create, shown as -/+).
Nothing is created, changed, or destroyed by plan itself — it only computes and displays what would happen. Nothing is real until terraform apply runs.
Where to run it from working directory
terraform -chdir=terraform plan
Same working-directory pattern as every other command: run it against the folder holding the configuration you want to preview, after that folder has already been initialized.
Precondition what must already be true
- terraform init has completed, with a real backend connection (not -backend=false) — plan needs to read the current state.
- You're authenticated to Azure (az login locally, or the OIDC identity in CI) with at least read access to the resources involved, so the refresh step can check their current status.
- Every required variable without a default — subscription_id, db_password, client_ip — has a value supplied, whether from terraform.tfvars, TF_VAR_* environment variables, or -var flags. See variables.tf for the full list.
Postcondition what becomes true after it succeeds
- No Azure resources are created, changed, or destroyed — plan only reads.
- State is not permanently updated by a plain plan (the refresh happens in memory for the comparison, but nothing is written back to the stored state file).
- If -out <file> was used, a binary plan file is written to disk. Treat it as sensitive — it can contain values like the database password in plain form — and this project's workflow deletes it at the end of every run.
- With -detailed-exitcode, the exit code itself communicates the result: 0 = no changes, 1 = error, 2 = changes are pending.
How this project uses it real examples
args=()
if [[ "$OPERATION" == destroy ]]; then args+=(-destroy); fi
terraform -chdir=terraform plan -input=false -lock-timeout=5m "${args[@]}" -out workflow.tfplan
The workflow always runs plan first, saving its result to workflow.tfplan. The -destroy flag turns this into a destroy plan instead of a normal one — this project doesn't call a separate terraform destroy command at all; it reuses this exact same plan-then-apply pattern for both directions, so both operations get the same review-before-apply treatment. See terraform destroy.
terraform -chdir=terraform plan
Without -out, this is a read-only preview with nothing saved — useful for a quick sanity check, but not something a later apply can replay exactly.
Common errors and how to fix them
| What you see | Likely cause | How to fix it |
|---|---|---|
| “No value for required variable” | A required variable (like db_password or client_ip) wasn't supplied anywhere. | Set it via TF_VAR_<name>, terraform.tfvars, or -var. See variables.tf. |
| Authentication / authorization error during refresh | Not logged in to Azure, logged in as the wrong account, or missing read permission on an existing resource. | Run az login (or check the OIDC secrets in CI); confirm the identity has at least read access to the resource group. |
| “Error acquiring the state lock” | Another Terraform run (a teammate, or an overlapping CI job) is already using this state. | Wait for the other run to finish. This project's workflow also serializes runs with a concurrency group so overlapping jobs queue instead of colliding. |
| Unexpected changes to resources you didn't touch | Someone edited a resource manually in the Azure Portal, and Terraform's refresh detected the drift. | Decide whether to accept Terraform's plan to fix the drift, or update the .tf files to match the manual change — but avoid manual Portal edits on Terraform-managed resources going forward. |