Terraform

Mastering Terraform lifecycle Meta-Arguments: create_before_destroy and Beyond for Practice and Exams

2026-04-19
NicheeLab Editorial Team

Terraform's lifecycle meta-arguments directly affect deployment safety and observability — controlling change ordering, suppressing destructive changes, and triggering intentional replacements.

This article follows official documentation behavior, centers on the frequently-tested create_before_destroy, and is structured so you can quickly review both exam patterns (Associate/Pro) and real-world patterns.

lifecycle Meta-Argument Basics

lifecycle is a meta-argument that can be specified on each resource (and on modules in some recent Terraform versions) to control update strategy during Plan/Apply. The main ones are create_before_destroy (ordering control during replacement), prevent_destroy (preventing unintended deletion), ignore_changes (ignoring diffs on specific attributes), and replace_triggered_by (forcing replacement on external changes).

An important premise: Terraform builds plans based on a declarative dependency graph. lifecycle provides hints about ordering and replacement decisions on that graph, but the final behavior depends on each provider and remote API's constraints (for example, create_before_destroy may not work due to unique-name conflicts or quota limits). The exam frequently assumes this premise (provider/API-dependent constraints) as prerequisite knowledge.

  • create_before_destroy: When replacement is required, create the new resource first, then delete the old one.
  • prevent_destroy: A safety mechanism that errors out any plan involving destruction.
  • ignore_changes: Ignore diffs on specified attributes (such as tags overwritten by external systems).
  • replace_triggered_by: Replace the target resource triggered by changes to other resources (or attributes).

Minimal example: Typical lifecycle specification

resource "aws_instance" "app" {
  ami           = var.ami_id
  instance_type = "t3.micro"

  lifecycle {
    create_before_destroy = true
    prevent_destroy       = false
    ignore_changes        = [
      # 例: 外部で上書きされやすいタグ
      tags["owner"],
    ]
    # replace_triggered_by は必要時のみ使う
  }
}

Using create_before_destroy Safely

create_before_destroy takes effect on changes that cause resource replacement (new resource required). It shows as -/+ in the Plan and is effective when you want to switch from old to new. Use it when ensuring availability (zero-downtime switching) or when temporary dual-running is acceptable.

The caveat is that it assumes duplicate creation is allowed. If names are unique and conflict, the same IP/EIP can't be attached simultaneously, or quotas are reached, prior creation will fail. Standard workarounds include unique naming (random suffixes), careful attachment switching order, or isolating conflicting resources with a Blue/Green configuration.

  • Effective scenarios: Instance replacement (AMI updates), Launch Template/Config rollouts, designs with suffixes on role/policy names.
  • Failure-prone scenarios: Buckets/users with unique names, NICs that can't hold a fixed IP simultaneously, tight capacity quotas.
  • Reading the Plan: The replacement reason line is annotated (new resource required), with -/+ symbols on the target.

create_before_destroy switching diagram

Old State                     New State (plan)
-----------                   -----------------
[instance v1]  --create-->    [instance v2]
      |                            |
      |  traffic cutover           |  <-- attach / register
      v                            v
  --destroy-------------------->  [instance v1 deleted]

時間軸:  作成 -> 接続/紐付け変更 -> 旧破棄

Example: Zero-downtime replacement on AMI update (an AWS EC2 example)

resource "aws_instance" "app" {
  ami           = var.ami_id        # AMI を更新すると置換が必要
  instance_type = "t3.micro"
  subnet_id     = var.subnet_id

  lifecycle {
    create_before_destroy = true
  }

  # 固定名/固定 IP が衝突する場合は、切替を段階的に行う
  # 例: EIP は新インスタンスへ付け替えてから旧を破棄
}

# 名前衝突が発生しうるリソースではサフィックスで緩和
resource "random_id" "name" { byte_length = 2 }
resource "aws_iam_role" "app" {
  name = "app-role-${random_id.name.hex}"
  assume_role_policy = data.aws_iam_policy_document.app.json
  lifecycle { create_before_destroy = true }
}

Caveats for prevent_destroy and ignore_changes

prevent_destroy errors out any plan containing Destroy and stops the apply. It's effective for preventing accidental deletion, but replacements (-/+) are also blocked because they involve Destroy. When deletion or replacement is truly necessary, the standard operational approach is to temporarily remove prevent_destroy before applying.

ignore_changes ignores diffs on specified attributes. It effectively reduces noise when external systems overwrite tags or specific settings, but in long-term operations, it preserves drift from the actual environment. When you want to later reconcile, remove ignore_changes and re-apply in a planned manner.

  • prevent_destroy errors Plan/Apply. There's no CLI option to force through, so removing the setting is the only legitimate approach.
  • ignore_changes simply 'pretends not to see the diff' without changing reality. Limit its use in environments with strict audit requirements.
  • lifecycle acts individually on each element of for_each/count (per-element replacement/suppression).
Meta-argumentMain purposeBehavior on Plan/ApplyFailure/caveat points
create_before_destroyCreate first, then delete during replacementResource is -/+ (new resource required). Order: create → switch → deletePrior creation fails due to unique-name or fixed-resource conflicts, or quota limits
prevent_destroyPrevent unintended deletionPlans containing Destroy error out. -/+ is also blockedIf deletion/replacement is needed, temporarily release before applying
ignore_changesIgnore diffs on specific attributes (tolerate drift)Diffs on the attribute don't appear in Plan and aren't updatedPreserves long-term drift. Temporarily release to reconcile in the future
replace_triggered_byForce replacement triggered by external changesUpdates to the reference target make the target -/+Risk of excessive replacement. Narrow the granularity of dependencies and replacements

Example: Ignore tags, prevent accidental DB deletion

resource "aws_db_instance" "main" {
  identifier = "app-db"
  engine     = "postgres"
  # ... 省略 ...
  lifecycle {
    prevent_destroy = true
  }
}

resource "aws_instance" "app" {
  ami           = var.ami_id
  instance_type = "t3.micro"
  tags = {
    owner = "platform-tooling"  # 外部タグ付与と衝突しやすい例
  }
  lifecycle {
    ignore_changes = [
      tags["owner"],
    ]
  }
}

Difference Between replace_triggered_by and depends_on

depends_on only adds resource creation ordering (an edge in the dependency graph) and does not force replacement. In contrast, replace_triggered_by replaces the target resource (-/+) when a referenced resource (or its attribute) changes. The former controls ordering; the latter adds a replacement trigger — that's the difference in roles.

In practice, replace_triggered_by is used to 'always make new' the dependent secret stores or settings when high-security materials like encryption keys, certificates, or KMS keys are updated. Combined with create_before_destroy, you can also bias the order during replacement toward safety.

  • depends_on is 'ordering only.' Plan changes don't propagate to the target even when the dependency changes.
  • replace_triggered_by is 'replacement propagation.' If there's a change in the reference, the target becomes -/+.
  • Confusion between depends_on and replace_triggered_by is common on exams. Be able to state the terms and purposes concisely.

Example: Force replacement of Secret on KMS key update

resource "aws_kms_key" "root" {
  description = "root key for app secret"
}

resource "aws_secretsmanager_secret" "app" {
  name       = "app/secret"
  kms_key_id = aws_kms_key.root.id

  lifecycle {
    # KMS キーが更新されたら Secret を新品にする
    replace_triggered_by = [
      aws_kms_key.root
    ]
    create_before_destroy = true
  }
}

# これに対し depends_on は順序を保証するだけで置換は起きない
# resource "..." "..." { depends_on = [aws_kms_key.root] }

Practical Pattern Catalog: Blue/Green, Immutable, Conflict Avoidance

Blue/Green: Coexist by color-coding the actual resources, and only switch traffic. create_before_destroy allows dual-running inside each color's components (e.g., VM/ASG/TG attachments), and switching between colors uses DNS/ALB weighting to approach zero downtime.

Immutable: Always update via replacement, assuming AMI/image generation. Default to create_before_destroy and arrange for fixed resources (EIPs, unique names) to be switched last. Use random suffixes on unique names to avoid conflicts.

Conflict avoidance: For resources with strict unique names or limits, delegate state to another resource first (e.g., EIP attachment changes or Route53 switching) before deleting the old entity. Read where the Plan becomes -/+ and identify in advance whether the conflict is over names, bindings, or capacity.

  • Unique names: Add suffixes with random_id/random_pet to enable prior creation.
  • Fixed IP/connection: Re-attach to the new entity, then delete the old one. Split the switch as an update to a separate resource.
  • Suppress unplanned replacements: Pinpoint prevent_destroy (on DBs, production queues, etc.).

Implementation hint (pseudo-code): Splitting Blue/Green switches

# 1) 実体は色付きで併存 (blue/green)。VM/ASG 側は create_before_destroy で安全に置換
resource "aws_autoscaling_group" "svc" {
  # for_each = toset([var.active_color]) などで色を制御する設計も可
  lifecycle { create_before_destroy = true }
}

# 2) トラフィックは後段で切替 (DNS/ALB target weight)
resource "aws_route53_record" "svc" {
  # blue -> green に重みを寄せる。完了後に不要な色を縮退
}

# 3) 衝突資源(EIP/固定名)は最後に再アタッチ or rename 用の属性を用意
#    ランダムサフィックスで同名衝突を避ける
resource "random_id" "suffix" { byte_length = 2 }
# name = "app-${random_id.suffix.hex}"

Exam Prep Checklist and Pitfalls

Know Plan symbols and their meanings precisely. -/+ is replacement (new resource required). ~ is in-place change. + is new, - is deletion. Be able to explain that replacement order is ultimately determined by lifecycle and provider constraints.

prevent_destroy can't be disabled via CLI. Remove the setting before applying. replace_triggered_by has a different role from depends_on (replacement vs. ordering). ignore_changes comes with the cost of preserving drift.

  • create_before_destroy is effective 'only during replacement.' It doesn't affect in-place updates.
  • Prior creation fails on unique-name conflicts or quota excess. Avoid via unique names or split applies.
  • Don't cause excessive replacement with replace_triggered_by. Keep granularity minimal.
  • Consider ignore_changes together with your audit and drift strategy.

Command examples: Basics of replacement/diff verification

# 置換を明示して適用(taint の代替)
terraform apply -replace=aws_instance.app

# Plan だけで挙動確認
terraform plan

# 注意: prevent_destroy は CLI で無視できない。設定を外してから apply する

Check with a Practice Question

Associate / Pro

問題 1

You want to replace a production EC2 instance with a new AMI. You aim for near-zero-downtime switching and need to avoid name conflicts. Which is the appropriate Terraform approach?

  1. Enable create_before_destroy and add a random suffix to the name to ensure uniqueness
  2. Enable prevent_destroy, then change the AMI to force replacement
  3. Add ami to ignore_changes to ignore the diff, then swap manually later
  4. Add the new AMI's data source to depends_on to guarantee ordering

正解: A

To approach zero downtime, aim for the create-new → switch → delete-old order via create_before_destroy. To avoid name conflicts, make names unique with random suffixes or similar. prevent_destroy blocks deletion and prevents the replacement itself. ignore_changes ignores the diff and won't update. depends_on only controls ordering and doesn't force replacement.

Frequently Asked Questions

When does create_before_destroy fail to work?

It fails when no replacement is needed (in-place update), or when provider/API constraints prevent duplicate creation (globally unique names, attach conflicts, quota limits). Solutions include adopting unique names, splitting the switch operation (change attachment first, then destroy the old resource), and isolating with Blue/Green deployments.

How do I later reconcile drift on attributes ignored by ignore_changes?

Temporarily remove the attribute from ignore_changes, then plan/apply to converge to the desired value. If forced replacement is needed, use terraform apply -replace=... alongside it.

How do I delete a resource that has prevent_destroy set?

Remove prevent_destroy (or set it to false), then plan/apply again. There is no CLI option to bypass it. Since the purpose is to prevent accidental deletion, an explicit configuration change is required.

Check what you learned with practice questions

Practice with certification-focused question sets

無料で問題を解いてみる
Author

NicheeLab Editorial Team

NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.


Related articles
Terraform

HCL Syntax: Terraform's Configuration Language (2026)

HCL2 fundamentals for Terraform — blocks, attributes, expres...

Terraform

Terraform Authoring & Operations Pro: Complete Guide (2026)

Tactics for the Terraform Pro exam — module authoring, works...

Terraform

Terraform Providers: Plugin Management Fundamentals (2026)

Provider mechanics — required_providers, versions, mirrors, ...

Terraform

Terraform Resource Blocks: Declarative Infra Units (2026)

Resource block fundamentals — addresses, references, common ...

Terraform

Terraform Data Sources: Read-Only External Data (2026)

Data source basics — declaration, refresh behavior, dependen...

Browse all Terraform articles (102)
© 2026 NicheeLab All rights reserved.