Ravindra BagaleCourses & study guides मराठी

Chapter 36: Terraform count for_each dynamic lifecycle and depends_on

36.4 lifecycle — control how changes happen

Story. An Instagram-style team changed the AMI of a production instance, and Terraform destroyed the old server before the new one was ready, causing a short outage. The lifecycle block changes that behaviour. create_before_destroy builds the replacement first. prevent_destroy refuses to delete precious resources. ignore_changes stops Terraform fighting with a value another system manages.

change AMI lifecycle create new first switch destroy old
create_before_destroy builds the replacement, then removes the old one.
resource "aws_instance" "web" {
  ami           = data.aws_ami.al2023.id
  instance_type = var.instance_type

  lifecycle {
    create_before_destroy = true
    ignore_changes        = [tags["LastPatched"]]
    precondition {
      condition     = var.instance_type != "t3.nano"
      error_message = "t3.nano is too small for nginx plus the app."
    }
  }
}

resource "aws_s3_bucket" "tfstate" {
  bucket = "badge-tfstate-<your-account-id>"
  lifecycle {
    prevent_destroy = true
  }
}

What each line does

  • lifecycle { — Opens settings for how Terraform changes this resource.
  • create_before_destroy = true — When replacement is needed, create the new instance before deleting the old one.
  • ignore_changes = [tags["LastPatched"]] — A patching tool updates this tag; Terraform will not undo it.
  • precondition { ... } — A check that runs before the resource is planned.
  • condition = var.instance_type != "t3.nano" — Fails the plan if someone picks a tiny type.
  • prevent_destroy = true — Any plan that would delete the state bucket fails instead.

When it is green.

  # aws_instance.web must be replaced
+/- resource "aws_instance" "web" {
      ~ ami = "ami-0old..." -> "ami-0new..." # forces replacement
    }
Plan: 1 to add, 0 to change, 1 to destroy.

When it is red.

Error: Instance cannot be destroyed
  on main.tf line 18: resource "aws_s3_bucket" "tfstate":
Resource aws_s3_bucket.tfstate has lifecycle.prevent_destroy set, but the plan calls for this
resource to be destroyed.

Fix. This is the guard working. If deleting is truly intended, remove prevent_destroy in a reviewed change first. Note +/- means create then destroy; -/+ means destroy then create.

What: every term in plain English

  • create_before_destroy — Build the replacement first, then delete the old one.
  • prevent_destroy — Fail any plan that would delete this resource.
  • ignore_changes — Do not undo changes to the listed attributes.
  • replace_triggered_by — Replace this resource when another one changes.
  • precondition — A check before the resource is created or changed.
  • postcondition — A check after Terraform reads the result.

Example 1: create_before_destroy on a free resource

See the order with no cloud cost. random_pet replaced with the new one made first.

variable "v" {
  default = "1"
}

resource "random_pet" "web" {
  keepers = {
    version = var.v
  }
  lifecycle {
    create_before_destroy = true
  }
}
terraform apply -auto-approve
terraform apply -auto-approve -var v=2

Output

random_pet.web: Creating...
random_pet.web: Creation complete after 0s [id=calm-otter]
random_pet.web (deposed object 1a2b3c4d): Destroying... [id=quiet-panda]
random_pet.web: Destruction complete after 0s
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.

What it shows: Creating comes before Destroying. On EC2 behind a load balancer this means no gap.

Example 2: prevent_destroy guards a bucket

At a Razorpay-style company, the audit-log bucket must never be deleted by mistake.

resource "aws_s3_bucket" "audit" {
  bucket = "audit-logs-111122223333"
  lifecycle {
    prevent_destroy = true
  }
}
terraform destroy

Output

Error: Instance cannot be destroyed
  on main.tf line 1:
   1: resource "aws_s3_bucket" "audit" {
Resource aws_s3_bucket.audit has lifecycle.prevent_destroy set, but the plan calls for this resource to be destroyed.

What it shows: Note: deleting the whole resource block from code also removes the guard. Protect with IAM too.

Example 3: ignore_changes for an autoscaled count

An autoscaling group changes desired_capacity at runtime. Without ignore_changes, every apply resets it.

resource "aws_autoscaling_group" "web" {
  name             = "web-asg"
  min_size         = 2
  max_size         = 10
  desired_capacity = 2
  vpc_zone_identifier = var.subnet_ids
  launch_template {
    id      = aws_launch_template.web.id
    version = "$Latest"
  }
  lifecycle {
    ignore_changes = [desired_capacity]
  }
}
aws autoscaling set-desired-capacity --auto-scaling-group-name web-asg --desired-capacity 6
terraform plan | tail -1

Output

No changes. Your infrastructure matches the configuration.

What it shows: Without ignore_changes the plan would show desired_capacity = 6 -> 2 and shrink the fleet at peak time.

Example 4: replace_triggered_by and a postcondition

When the user_data script changes, a Swiggy-style team wants a fresh instance. A postcondition checks it got a public IP.

resource "terraform_data" "boot" {
  input = filesha256("${path.module}/nginx.sh")
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.al2023.id
  instance_type = "t3.micro"
  user_data     = file("${path.module}/nginx.sh")
  lifecycle {
    replace_triggered_by = [terraform_data.boot]
    postcondition {
      condition     = self.public_ip != ""
      error_message = "The web server must have a public IP."
    }
  }
}
echo "# v2" >> nginx.sh
terraform plan | grep -E "replace|Plan:"

Output

  # aws_instance.web will be replaced due to changes in replace_triggered_by
Plan: 1 to add, 1 to change, 1 to destroy.

What it shows: terraform_data changes when the script changes, and that triggers the instance replace.

Example 5: Real AWS: deletion_protection plus prevent_destroy on RDS

A bank-style team never wants the prod database deleted. Two guards work together: Terraform blocks the plan, and AWS blocks the API call.

resource "aws_db_instance" "prod" {
  identifier          = "orders-prod"
  engine              = "mysql"
  instance_class      = "db.t3.micro"
  allocated_storage   = 20
  username            = "admin"
  manage_master_user_password = true
  deletion_protection = true
  skip_final_snapshot = false
  final_snapshot_identifier = "orders-prod-final"
  lifecycle {
    prevent_destroy = true
    ignore_changes  = [engine_version]
  }
}
terraform plan -destroy 2>&1 | grep -E "Error|prevent_destroy"

Output

Error: Instance cannot be destroyed
Resource aws_db_instance.prod has lifecycle.prevent_destroy set

What it shows: ignore_changes on engine_version lets AWS minor upgrades happen without Terraform undoing them. manage_master_user_password keeps the password in Secrets Manager.

Command cheat sheet

Command What it does When to use it
create_​before_​destroy = true New first, then old Zero-gap replace
prevent_​destroy = true Block deletes State buckets, databases
ignore_​changes = [attr] Do not undo outside edits ASG size, patched tags
ignore_​changes = all Ignore every change Rare; resources managed elsewhere
replace_​triggered_​by = [res] Replace when another changes Config files
precondition / postcondition Checks before or after Guard rails

Verify: did it work?

  1. Guard works

    terraform plan -destroy 2>&1 | grep -c "cannot be destroyed"
    

    Output

    1
    
  2. Ignored field

    terraform plan -detailed-exitcode; echo $?
    

    Output

    0
    

When it is red 1

Error: creating EC2 Instance: InvalidGroup.Duplicate: The security group 'web-sg' already exists

Cause. create_before_destroy tried to make a new group with the same fixed name.

Fix, step by step

  1. Use name_prefix instead of name.
  2. Plan again.

When it is red 2

Error: Resource postcondition failed
  The web server must have a public IP.

Cause. The subnet does not give public IPs.

Fix, step by step

  1. Set associate_public_ip_address = true.
  2. Or use a public subnet.

When it is red 3

Error: Invalid ignore_changes wildcard
The ["*"] form of ignore_changes wildcard is was deprecated and is now invalid. Use "ignore_changes = all".

Cause. Old syntax.

Fix, step by step

  1. Write ignore_changes = all.

Cleanup

terraform destroy -auto-approve

Ravindra Bagale's Tip

Put prevent_destroy on state buckets and databases on day one. It costs nothing and saves a career.

Practice tasks

Easy See the order

Why: Feel create first.

What to do: Run the random_pet replace demo.

How:
  1. Read the log order.

Check: Creating before Destroying.

Medium Guard

Why: Block deletes.

What to do: Add prevent_destroy and try destroy.

How:
  1. Read the error.

Check: Destroy fails.

Medium ASG size

Why: Stop resets.

What to do: Add ignore_changes to desired_capacity.

How:
  1. Change size in CLI, then plan.

Check: No changes.

Hard Trigger

Why: Rebuild on config change.

What to do: Use replace_triggered_by with terraform_data.

How:
  1. Edit the script.

Check: Plan shows replace.

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

List the three most important resources in your project. Decide which lifecycle rule each one needs and why.

Recap

  • create_before_destroy avoids gaps on replace.
  • prevent_destroy blocks deletes.
  • ignore_changes stops undoing outside edits.
  • replace_triggered_by links rebuilds.
  • Pre and post conditions are guard rails.

FAQ

Does prevent_destroy protect against console deletes?

No. Use IAM and AWS Backup for that.

Can lifecycle use variables?

Most lifecycle settings must be literal values.

What does +/- mean in plan?

Create then destroy, from create_before_destroy.

Is taint the same as replace_triggered_by?

No. -replace is a one-time CLI choice; replace_triggered_by is in code.

Why use name_prefix?

So the new and old copy can exist at the same time.

Interview questions

  1. How do you avoid downtime on replace?

    create_before_destroy, plus name_prefix for named resources.

  2. How do you protect a database from terraform destroy?

    prevent_destroy, deletion_protection, and IAM.

  3. When do you use ignore_changes?

    When another system owns an attribute, such as an ASG size.

  4. What is replace_triggered_by?

    It forces a replace when a referenced resource changes.

  5. What is a postcondition?

    A check on the result after apply or read.

Quick quiz

Q1. Plan symbol for create then destroy?

+/-

Q2. Rule that blocks deletes?

prevent_destroy

Q3. Keep ASG size from resets?

ignore_changes = [desired_capacity]

Q4. Use instead of name to avoid clashes?

name_prefix

Q5. Resource often used as a trigger?

terraform_data

Samajla ka?

Ravi Sir's Cards for this lesson

मराठी cards