Core Workflow
The Terraform workflow is four commands: init, plan, apply, destroy. Every Terraform session follows this pattern.
terraform init
Section titled “terraform init”Initializes a working directory. Run this first (and whenever you add providers or modules):
terraform initWhat it does:
- Downloads provider plugins (e.g. the AWS provider) to
.terraform/. - Downloads modules referenced in your config.
- Configures the backend (where state is stored).
- Creates
.terraform.lock.hcl(dependency lock file — commit this to Git).
terraform init -upgrade # upgrade providers/modules to latest allowed versionsterraform init -reconfigure # reconfigure backend without migrating stateterraform plan
Section titled “terraform plan”Shows what Terraform will do without making any changes:
terraform planExample output:
Terraform will perform the following actions:
# aws_instance.web will be created + resource "aws_instance" "web" { + ami = "ami-0c55b159cbfafe1f0" + instance_type = "t3.micro" + id = (known after apply) + public_ip = (known after apply) + tags = { + "Name" = "web-server" } }
Plan: 1 to add, 0 to change, 0 to destroy.Reading Plan Output
Section titled “Reading Plan Output”| Symbol | Meaning |
|---|---|
+ | Resource will be created |
- | Resource will be destroyed |
~ | Resource will be updated in-place |
-/+ | Resource will be destroyed and recreated (forced replacement) |
<= | Data source will be read |
The (known after apply) marker means the value is only available after the resource is created (e.g. generated IDs, assigned IPs).
Save a Plan
Section titled “Save a Plan”terraform plan -out=tfplan # save plan to a fileterraform apply tfplan # apply the saved plan (no re-prompt)This is the recommended pattern in CI/CD: plan in the PR, save the plan file, apply the exact plan on merge.
Targeting
Section titled “Targeting”Plan or apply only specific resources:
terraform plan -target=aws_instance.webterraform apply -target=aws_instance.webUse sparingly — targeting skips dependency checks and can leave state inconsistent.
terraform apply
Section titled “terraform apply”Executes the plan and makes real changes:
terraform applyTerraform shows the plan and asks for confirmation (yes). To skip the prompt:
terraform apply -auto-approveWith Variables
Section titled “With Variables”terraform apply -var="instance_type=t3.large"terraform apply -var-file="prod.tfvars"Applying a Saved Plan
Section titled “Applying a Saved Plan”terraform apply tfplanWhen applying a saved plan file, Terraform doesn’t re-prompt — it applies exactly what was planned.
terraform destroy
Section titled “terraform destroy”Destroys all resources managed by the current configuration:
terraform destroyShows a plan of what will be destroyed and asks for confirmation. To skip the prompt:
terraform destroy -auto-approveDestroy specific resources:
terraform destroy -target=aws_instance.webOther Useful Commands
Section titled “Other Useful Commands”terraform validate
Section titled “terraform validate”Checks config syntax and internal consistency (no API calls):
terraform validateterraform fmt
Section titled “terraform fmt”Formats .tf files to the canonical style:
terraform fmt # format current directoryterraform fmt -recursive # format all subdirectories tooterraform fmt -check # check without modifying (useful in CI)terraform output
Section titled “terraform output”Display outputs from the current state:
terraform output # all outputsterraform output instance_ip # specific outputterraform output -json # JSON format (for scripting)terraform refresh
Section titled “terraform refresh”Updates state to match real infrastructure (deprecated in favor of terraform apply -refresh-only):
terraform apply -refresh-onlyterraform console
Section titled “terraform console”Interactive REPL for testing expressions:
terraform console> var.instance_type"t3.micro"> length(var.subnets)3> cidrsubnet("10.0.0.0/16", 8, 1)"10.0.1.0/24"Drift Detection
Section titled “Drift Detection”Drift happens when real infrastructure changes outside of Terraform — someone manually edits a security group in the AWS console, an auto-scaler changes instance counts, or another tool modifies a resource.
How Terraform Detects Drift
Section titled “How Terraform Detects Drift”Every time you run terraform plan, Terraform:
- Refreshes — Queries the cloud APIs to get the current state of every managed resource.
- Compares — Checks current state against your config and the stored state.
- Reports — Shows any differences as planned changes.
If someone manually added a tag to an instance, terraform plan would show:
# aws_instance.web will be updated in-place ~ resource "aws_instance" "web" { ~ tags = { - "ManualTag" = "oops" -> null # Terraform will remove the manual tag "Name" = "web-server" } }Refresh-Only Mode
Section titled “Refresh-Only Mode”To detect drift without applying any config changes:
terraform apply -refresh-onlyThis updates the state file to match reality without changing any infrastructure. Useful for:
- Auditing: “Has anything drifted since last apply?”
- Syncing state after expected external changes (e.g. auto-scaling).
You can also plan in refresh-only mode first:
terraform plan -refresh-onlyHandling Drift
Section titled “Handling Drift”| Situation | Action |
|---|---|
| Manual change was a mistake | Run terraform apply — Terraform reverts to your config |
| Manual change should be kept | Update your .tf files to match, then terraform plan (should show no changes) |
| External tool manages the field | Add ignore_changes in the resource’s lifecycle block |
| Resource was deleted externally | Terraform will plan to recreate it on next apply |
Preventing Drift
Section titled “Preventing Drift”- Restrict manual access — Use IAM policies to prevent console edits to Terraform-managed resources.
- Tag managed resources — Add a
ManagedBy = "terraform"tag so people know not to edit manually. - Run scheduled plans — Run
terraform planon a cron (e.g. nightly) to detect drift early. - Use
ignore_changes— For fields that are intentionally managed externally (e.g. auto-scaling desired count).
Scheduled Drift Detection (CI/CD)
Section titled “Scheduled Drift Detection (CI/CD)”# GitHub Actions — nightly drift checkname: Drift Detectionon: schedule: - cron: "0 6 * * *" # daily at 6 AM UTC
jobs: detect: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: hashicorp/setup-terraform@v3 - run: terraform init - run: terraform plan -detailed-exitcode # exit code 0 = no changes, 1 = error, 2 = drift detectedExit code 2 from terraform plan -detailed-exitcode means drift was detected — you can trigger an alert or Slack notification.
The Typical Workflow
Section titled “The Typical Workflow”1. Write .tf files (define resources)2. terraform init (download providers, modules)3. terraform plan (preview changes)4. terraform apply (make changes)5. Iterate: edit → plan → apply6. terraform destroy (tear down when done)In a team/CI setting:
1. Branch → edit .tf files2. PR → terraform plan (posted as PR comment)3. Review the plan4. Merge → terraform apply (automated)Key Takeaways
Section titled “Key Takeaways”- Always plan before apply — review what will change.
- Use saved plans in CI/CD:
plan -out=tfplanin PR,apply tfplanon merge. terraform fmtandterraform validateshould run in CI on every PR.- Avoid
terraform apply -auto-approveoutside of automated pipelines. - Use
-targetsparingly and only for debugging — not as a regular workflow. - Drift detection:
terraform plan -refresh-onlyaudits drift;plan -detailed-exitcodeflags it in CI. - Prevent drift with restricted access, tagging,
ignore_changes, and scheduled plan runs.