What this command does in detail
- Without a saved plan (terraform apply alone): Terraform computes a fresh plan itself, shows it, and pauses for you to type yes before doing anything.
- With a saved plan (terraform apply workflow.tfplan): Terraform executes exactly that plan, with no further confirmation prompt — the confirmation already happened implicitly when the plan was reviewed and chosen to be applied.
Either way, once it starts, Terraform calls the Azure APIs to actually create, modify, or delete each resource in the plan, in an order that respects dependencies between them (the resource group before anything inside it, for example), and writes the result back to state as it goes — not just at the very end.
Where to run it from working directory
terraform -chdir=terraform apply
Same folder as the matching terraform plan. If applying a saved plan file, that file has to be one this exact folder's plan produced — you can't apply a plan file generated somewhere else.
Precondition what must already be true
- terraform init completed, with a real backend connection.
- Either a fresh, valid plan can be computed, or a previously saved plan file exists and reality hasn't changed since it was generated.
- Azure credentials with write permission for every resource in the plan — not just read access, which was enough for terraform plan's refresh step.
- Every required variable still has the same values it had when the plan was generated (if applying a saved plan, these are baked into the plan file itself).
Postcondition what becomes true after it succeeds
- Real Azure resources now match the configuration: created, updated, or removed exactly as the plan described.
- State is updated and saved back to the backend, reflecting the new reality — this is what later commands (another plan, or terraform output) will read.
- Any outputs.tf values are (re)computed and available.
- If it fails partway through, resources it already successfully created remain tracked in state — re-running apply after fixing the problem picks up from there rather than starting over.
How this project uses it real examples
terraform -chdir=terraform apply -input=false -lock-timeout=5m workflow.tfplan
Only runs when the workflow's chosen operation isn't plan. Every apply (and destroy) in this project applies a plan generated moments earlier in the very same job — never an old, possibly stale plan from a previous run — so what gets applied always matches what was just reviewed.
Once apply succeeds for a real (non-destroy) run, the workflow optionally runs terraform/scripts/initialize_database.py to load starting data into the now-existing, empty database, then prints terraform output values to the job summary for the next setup step.
Common errors and how to fix them
| What you see | Likely cause | How to fix it |
|---|---|---|
| “Saved plan is stale” | Real infrastructure or state changed between when the plan file was generated and when you tried to apply it. | Discard the old plan file and run terraform plan again to get a current one. |
| “A resource with this name already exists” | An Azure resource name (often one requiring global uniqueness, like a storage account) collides with something that already exists. | Choose a different name, or reuse/import the existing resource if it's actually meant to be the same one. |
| Authorization / 403 during apply | The identity has read access but not enough write permission for a specific resource type. | Check the role assignments in github_oidc.tf or bootstrap/main.tf cover the resource being changed. |
| Quota exceeded | The Azure subscription has hit its limit for a resource type or region. | In a shared teaching subscription, ask whoever manages it before switching regions or retrying — changing region without coordination can cause more confusion mid-class. |
| Database password rejected by Azure | The password passed this project's own length validation in variables.tf, but doesn't meet Azure PostgreSQL's own complexity rules. | Choose a password mixing uppercase, lowercase, digits, and symbols, within the required length. |