Skip to content

Core Workflow

First PublishedByAtif Alam

The Terraform workflow is four commands: init, plan, apply, destroy. Every Terraform session follows this pattern.

Initializes a working directory. Run this first (and whenever you add providers or modules):

Terminal window
terraform init

What 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).
Terminal window
terraform init -upgrade # upgrade providers/modules to latest allowed versions
terraform init -reconfigure # reconfigure backend without migrating state

Shows what Terraform will do without making any changes:

Terminal window
terraform plan

Example 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.
SymbolMeaning
+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).

Terminal window
terraform plan -out=tfplan # save plan to a file
terraform 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.

Plan or apply only specific resources:

Terminal window
terraform plan -target=aws_instance.web
terraform apply -target=aws_instance.web

Use sparingly — targeting skips dependency checks and can leave state inconsistent.

Executes the plan and makes real changes:

Terminal window
terraform apply

Terraform shows the plan and asks for confirmation (yes). To skip the prompt:

Terminal window
terraform apply -auto-approve
Terminal window
terraform apply -var="instance_type=t3.large"
terraform apply -var-file="prod.tfvars"
Terminal window
terraform apply tfplan

When applying a saved plan file, Terraform doesn’t re-prompt — it applies exactly what was planned.

Destroys all resources managed by the current configuration:

Terminal window
terraform destroy

Shows a plan of what will be destroyed and asks for confirmation. To skip the prompt:

Terminal window
terraform destroy -auto-approve

Destroy specific resources:

Terminal window
terraform destroy -target=aws_instance.web

Checks config syntax and internal consistency (no API calls):

Terminal window
terraform validate

Formats .tf files to the canonical style:

Terminal window
terraform fmt # format current directory
terraform fmt -recursive # format all subdirectories too
terraform fmt -check # check without modifying (useful in CI)

Display outputs from the current state:

Terminal window
terraform output # all outputs
terraform output instance_ip # specific output
terraform output -json # JSON format (for scripting)

Updates state to match real infrastructure (deprecated in favor of terraform apply -refresh-only):

Terminal window
terraform apply -refresh-only

Interactive REPL for testing expressions:

Terminal window
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 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.

Every time you run terraform plan, Terraform:

  1. Refreshes — Queries the cloud APIs to get the current state of every managed resource.
  2. Compares — Checks current state against your config and the stored state.
  3. 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"
}
}

To detect drift without applying any config changes:

Terminal window
terraform apply -refresh-only

This 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:

Terminal window
terraform plan -refresh-only
SituationAction
Manual change was a mistakeRun terraform apply — Terraform reverts to your config
Manual change should be keptUpdate your .tf files to match, then terraform plan (should show no changes)
External tool manages the fieldAdd ignore_changes in the resource’s lifecycle block
Resource was deleted externallyTerraform will plan to recreate it on next apply
  • 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 plan on 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).
# GitHub Actions — nightly drift check
name: Drift Detection
on:
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 detected

Exit code 2 from terraform plan -detailed-exitcode means drift was detected — you can trigger an alert or Slack notification.


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 → apply
6. terraform destroy (tear down when done)

In a team/CI setting:

1. Branch → edit .tf files
2. PR → terraform plan (posted as PR comment)
3. Review the plan
4. Merge → terraform apply (automated)
  • Always plan before apply — review what will change.
  • Use saved plans in CI/CD: plan -out=tfplan in PR, apply tfplan on merge.
  • terraform fmt and terraform validate should run in CI on every PR.
  • Avoid terraform apply -auto-approve outside of automated pipelines.
  • Use -target sparingly and only for debugging — not as a regular workflow.
  • Drift detection: terraform plan -refresh-only audits drift; plan -detailed-exitcode flags it in CI.
  • Prevent drift with restricted access, tagging, ignore_changes, and scheduled plan runs.