Everyday workflow
terraform init -upgrade | Install providers and modules, refresh lock file |
terraform fmt -recursive && terraform validate | Format and check before you commit |
terraform plan -out=tfplan | Save the exact plan you reviewed |
terraform apply tfplan | Apply only what was in that plan |
terraform show -json tfplan | jq '.resource_changes[] | {address, actions: .change.actions}' | Summarise what will change |
State
terraform state list | Everything Terraform manages here |
terraform state show aws_instance.web | Attributes of one resource |
terraform state mv module.old.aws_s3_bucket.b module.new.aws_s3_bucket.b | Rename without destroy/recreate |
terraform state rm aws_iam_role.legacy | Stop managing it (doesn't delete it) |
terraform force-unlock <LOCK_ID> | Only when you're sure nobody else is applying |
Bring things under control
import { to = aws_s3_bucket.logs id = "my-logs" } | Import block (plan shows it, apply adopts it) |
terraform plan -generate-config-out=generated.tf | Generate config for import blocks |
moved { from = aws_instance.a to = aws_instance.web } | Refactor addresses in code, reviewed in the plan |
terraform plan -refresh-only | See drift without changing anything |
terraform apply -replace=aws_instance.web | Force one resource to be recreated |
Scope and environments
terraform plan -target=module.network | Emergency only: partial plans hide dependencies |
terraform workspace list / new staging / select prod | Workspaces (prefer separate state per env) |
terraform output -json | Outputs for scripts and other stacks |
TF_LOG=DEBUG terraform plan 2> debug.log | Provider-level debugging |
Remember
- Remote state with locking (S3 + DynamoDB or S3 native locking) before a second person touches it.
- Never edit state by hand. state mv, moved blocks and import blocks are reviewable.
- If the plan wants to destroy something you didn't change, stop: it's usually a renamed address or a provider upgrade.