Ravindra BagaleCourses & study guides मराठी

Chapter 42: Terraform Drift and Troubleshooting

42.3 Troubleshooting table — error, cause, fix

Story. Every Instagram-style platform team keeps a short runbook of the Terraform errors that come back again and again. Reading the first line of the error, not the last, usually names the problem. The table below covers the errors you will meet most in the labs. Each one maps to a section earlier in this course.

read first error line match table fix plan again green
Most Terraform errors are one of a handful of patterns.
| Error text                                   | Usual cause                          | Fix                                        |
|----------------------------------------------|--------------------------------------|--------------------------------------------|
| Inconsistent dependency lock file            | init not run after provider change   | terraform init (or -upgrade)               |
| Error acquiring the state lock               | another run in progress              | wait; force-unlock only if it crashed      |
| Saved plan is stale                          | state changed after plan             | plan again                                 |
| Invalid count / for_each argument            | value unknown at plan time           | use known variables or toset()             |
| Cycle: a, b                                  | circular depends_on or references    | terraform graph, remove one edge           |
| BucketAlreadyExists                          | global name taken                    | add account id suffix                      |
| DependencyViolation on destroy               | resource made outside Terraform      | delete it, destroy again                   |
| No valid credential sources found            | no role or SSO session               | aws sso login or attach instance role      |
| Unexpected destroy after rename              | missing moved block                  | add moved { from to }                      |

What each line does

  • Inconsistent dependency lock file — The lock file and provider block disagree; init fixes it.
  • Error acquiring the state lock — Locking working as designed; wait for the other run.
  • Saved plan is stale — Somebody applied after your plan.
  • Invalid count / for_each argument — Keys or counts must be known before apply.
  • Cycle: a, b — Two resources wait for each other.
  • BucketAlreadyExists — S3 names are global across all accounts.
  • DependencyViolation on destroy — An unmanaged resource still uses something you are deleting.
  • No valid credential sources found — Terraform has no identity; never solve it with keys in code.
  • Unexpected destroy after rename — Refactors need moved blocks.

When it is green.

No changes. Your infrastructure matches the configuration.

When it is red.

Error: No valid credential sources found
  with provider["registry.terraform.io/hashicorp/aws"]
Please see https://registry.terraform.io/providers/hashicorp/aws for more information.

Fix. Run aws sso login or run Terraform on an instance with a role. Do not paste access keys into the provider block.

What: every term in plain English

  • lock file — .terraform.lock.hcl. Records exact provider versions and hashes.
  • state lock — A lock in DynamoDB or S3 that stops two applies at once.
  • stale plan — A saved plan made before someone else changed state.
  • unknown value — A value Terraform will only know after apply, such as a new instance id.
  • cycle — Two or more resources that wait for each other forever.
  • force-unlock — A command that removes a stuck state lock by its id.

Example 1: Inconsistent lock file

A TCS developer changed the provider version and pushed. CI failed on init.

required_providers {
  aws = {
    source  = "hashicorp/aws"
    version = "~> 5.60"
  }
}
terraform plan
terraform init -upgrade
git add .terraform.lock.hcl
git commit -m "bump aws provider lock"
terraform plan

Output

Error: Inconsistent dependency lock file
  - provider registry.terraform.io/hashicorp/aws: locked version selection 5.31.0 doesn't match the updated version constraints "~> 5.60"

Upgrading provider plugins...
- Installing hashicorp/aws v5.x.x...
Terraform has made some changes to the provider dependency selections recorded in the .terraform.lock.hcl file.

No changes. Your infrastructure matches the configuration.

What it shows: Always commit the lock file. Then CI and laptops use the same provider.

Example 2: Stuck state lock after a killed job

A Jenkins agent died in the middle of an apply at a Swiggy-style team. The lock stayed.

  1. Read the lock info.
  2. Confirm no job is running for that folder.
  3. force-unlock with the exact id.
  4. Plan to check state is fine.
terraform plan
# check Jenkins: no running job for envs/prod
terraform force-unlock 9db590f1-b6fe-c5f2-2678-8804f089deba
terraform plan

Output

Error: Error acquiring the state lock
Lock Info:
  ID:        9db590f1-b6fe-c5f2-2678-8804f089deba
  Path:      company-tf-state/prod/terraform.tfstate
  Operation: OperationTypeApply
  Who:       jenkins@agent-7
  Created:   2026-10-24 21:10:03 UTC

Do you really want to force-unlock?
  Enter a value: yes
Terraform state has been successfully unlocked!

No changes. Your infrastructure matches the configuration.

What it shows: 21:10 UTC is 02:40 IST the next day. Force-unlock only when you are sure nothing is running.

Example 3: Invalid for_each with unknown values

An Ola-style team used for_each over ids of subnets created in the same apply. Terraform cannot know the keys yet.

# bad: keys are unknown until apply
resource "aws_route_table_association" "a" {
  for_each       = toset(aws_subnet.public[*].id)
  subnet_id      = each.value
  route_table_id = aws_route_table.public.id
}

# good: keys come from a known map in variables
resource "aws_route_table_association" "b" {
  for_each       = aws_subnet.public_map
  subnet_id      = each.value.id
  route_table_id = aws_route_table.public.id
}
terraform plan

Output

Error: Invalid for_each argument
  on main.tf line 3, in resource "aws_route_table_association" "a":
The "for_each" set includes values derived from resource attributes that cannot be determined until apply.

(after the fix)
Plan: 6 to add, 0 to change, 0 to destroy.

What it shows: Use keys you write in code, like az names, not ids AWS makes later.

Example 4: DependencyViolation on destroy

At a Zomato-style lab, destroy hung on a security group. Someone had launched an EC2 by hand that used it.

terraform destroy -auto-approve
SG=sg-0abc1234def567890
aws ec2 describe-network-interfaces --filters Name=group-id,Values=$SG \
  --query "NetworkInterfaces[].[NetworkInterfaceId,Description]" --output text

Output

Error: deleting Security Group (sg-0abc1234def567890): DependencyViolation: resource sg-0abc1234def567890 has a dependent object

eni-0aa11bb22cc33dd44    Primary network interface   (i-0fedcba9876543210)

What it shows: Terminate the hand-made instance i-0fedc..., wait a minute, then run destroy again.

Command cheat sheet

Command What it does When to use it
terraform init -​upgrade Fix lock file mismatch After changing a provider version
terraform force-​unlock ID Remove a stuck lock Only when no run is active
terraform graph | dot -​Tsvg > g.​svg Draw dependencies Cycle errors
terraform plan -​out=​tfplan Save a fresh plan After a stale plan error
aws ec2 describe-​network-​interfaces Find what uses a group DependencyViolation
aws sso login Get a session No valid credential sources

Verify: did it work?

  1. Lock file committed

    git status --short .terraform.lock.hcl
    

    Output

    (no output)
    
  2. No lock held

    terraform plan -lock-timeout=10s
    

    Output

    No changes. Your infrastructure matches the configuration.
    
  3. Credentials work

    aws sts get-caller-identity --query Account --output text
    

    Output

    111122223333
    

When it is red 1

Error: Saved plan is stale
The given plan file can no longer be applied because the state was changed by another operation after the plan was created.

Cause. Someone applied after your plan.

Fix, step by step

  1. Run terraform plan -out=tfplan again.
  2. Review and apply the new plan.

When it is red 2

Error: Cycle: aws_security_group.app, aws_security_group.db

Cause. Two groups reference each other inline.

Fix, step by step

  1. Move the rules to separate aws_vpc_security_group_ingress_rule resources.
  2. Plan again.

When it is red 3

Error: creating S3 Bucket (logs): BucketAlreadyExists

Cause. S3 names are global; someone has it.

Fix, step by step

  1. Add your account id: "logs-${data.aws_caller_identity.me.account_id}".

Cleanup

terraform destroy -auto-approve

Ravindra Bagale's Tip

Keep this table as a runbook in your repo. Add a line every time a new error costs you more than ten minutes.

Practice tasks

Easy Lock file error

Why: Know it on sight.

What to do: Change the provider version and run plan.

How:
  1. Then init -upgrade.

Check: Plan works.

Medium Stale plan

Why: Know it on sight.

What to do: Save a plan, apply a change from another terminal, apply the old plan.

How:
  1. Plan again.

Check: You saw Saved plan is stale.

Medium Cycle

Why: Draw it.

What to do: Make two groups that reference each other.

How:
  1. terraform graph.

Check: You fixed it with separate rules.

Hard Runbook

Why: Team memory.

What to do: Write RUNBOOK.md with ten errors from your labs.

How:
  1. Error, cause, fix.

Check: A teammate fixes one alone.

Easy Say it in two lines

Why: If you can say it simply, you understand it.

What to do: Write two short lines about this lesson for a fresher.

How:
  1. Write the problem it solves.
  2. Write the one command or file you use.
  3. Read it aloud.

Check: A fresher understands it without a question.

Easy Start a safe lab folder

Why: Practice needs a throwaway place.

What to do: Make a new folder with versions.tf and a random_pet resource, so no AWS cost.

How:
  1. mkdir tf-lab and cd into it.
  2. Add the hashicorp/random provider and one random_pet.
  3. Run terraform init and terraform apply.

Check: Apply complete! with 1 added.

Medium Break it on purpose

Why: You must know the red output by face.

What to do: Cause one error from the red part of this lesson and fix it.

How:
  1. Follow one red section.
  2. Copy the exact message to your notes.
  3. Write the fix in one line.

Check: You fixed it without searching the web.

Medium Run it twice

Why: A second run must be quiet.

What to do: Run the same plan two times with no edits.

How:
  1. Run terraform plan.
  2. Run it again.
  3. Explain any change it still shows.

Check: The second plan says No changes, or you can explain each line.

Medium Write a runbook page

Why: At 2 a.m. nobody remembers flags.

What to do: Write a one-page runbook for this lesson.

How:
  1. Title, when to use, exact commands, how to check, how to undo.

Check: A teammate can follow it alone.

Medium Run it from a pipeline

Why: Real teams run Terraform from CI, not from laptops.

What to do: Run the main command of this lesson inside a GitHub Actions or Jenkins job.

How:
  1. Add a job with hashicorp/setup-terraform or a Terraform Docker image.
  2. Give it AWS access through an IAM role, never a key in the file.
  3. Run init, then the command from this lesson.

Check: The job log shows the same output you saw on your laptop.

Medium Check the cost

Why: Labs left running cost real money.

What to do: Find what this lesson would cost per day if you forgot to destroy it.

How:
  1. Open the AWS Pricing Calculator.
  2. Add each resource from your plan.
  3. Write the daily cost in your notes.

Check: You know the daily cost and you ran terraform destroy.

Hard Draw the flow top to bottom

Why: A drawing shows the order of work.

What to do: Draw this lesson as boxes from top to bottom.

How:
  1. Start at your code, go through plan and state, end at AWS.
  2. Mark where each command reads or writes.

Check: Each box matches a real step you ran.

Hard Explain it like an interview

Why: Interviewers want real stories.

What to do: Record a 60 second answer about this lesson.

How:
  1. Say the problem, the command, and one error you hit.

Check: Your answer has a real command and a real error message.

Homework: try at home

Try at home

Break three things in a lab on purpose: the lock file, a bucket name, and a cycle. Write the first error line and the fix for each.

Recap

  • Read the first error line; it is the cause.
  • Commit .terraform.lock.hcl.
  • force-unlock only when nothing runs.
  • for_each keys must be known at plan.
  • Find hidden users of a group before destroy.

FAQ

Is force-unlock dangerous?

Yes, if a run is still active. Two applies can then corrupt state.

Why is my plan stale?

State changed after you saved it. Plan again.

How do I see a cycle?

terraform graph, then look for arrows going both ways.

Why BucketAlreadyExists if I have no bucket?

Another AWS account owns that name.

Should I delete the lock file to fix errors?

No. Run init -upgrade and commit the new one.

Interview questions

  1. How do you handle a stuck state lock?

    Confirm no run is active, then terraform force-unlock with the lock id, then plan.

  2. What causes Invalid for_each argument?

    Keys that are unknown until apply. Use known keys from variables or locals.

  3. How do you fix a dependency cycle?

    Find it with terraform graph and break one reference, often by moving inline rules to separate resources.

  4. Destroy fails with DependencyViolation. Why?

    Something outside Terraform still uses the resource. Find and remove it, then destroy again.

  5. What is the inconsistent lock file error?

    The lock file and provider constraint disagree. Run init -upgrade and commit the lock file.

Quick quiz

Q1. Command for a stuck lock?

terraform force-unlock ID

Q2. Fix for lock file mismatch?

terraform init -upgrade

Q3. Saved plan is stale means?

State changed after the plan.

Q4. Are S3 bucket names global?

Yes.

Q5. Which line of an error matters most?

The first.

Lab

  1. Apply the Chapter 40 lab.
  2. Add a tag by hand in the console.
  3. Run drift-check.sh and confirm exit code 2.
  4. Decide: add the tag to code, or apply to remove it.
  5. Break a permission on purpose and find it with TF_LOG.
  6. Run terraform destroy to avoid cost.

Practice task

Write a Jenkins cron trigger that runs drift-check.sh every night at 2 AM IST.

Ravindra Bagale's Tip

Read the first error line, not the last. The first one is the cause; the rest are consequences.

In short

  • Drift is a difference between AWS and code plus state.
  • plan -detailed-exitcode returns 2 when drift exists.
  • Codify or revert; never fix drift by clicking.
  • TF_LOG shows the exact failing API call; delete the log after.

Samajla ka?

Ravi Sir's Cards for this lesson

मराठी cards