Sign in
Menu
Sign up
Docs
Operational rulesRule catalog

Create before destroy

For platform engineers deciding whether to apply lifecycle_create_before_destroy: what it changes, how to enable it, and how to check it worked.

Adds create_before_destroy = true to security groups and ACM certificates. The replacement is created first; the old resource is destroyed after.

What this rule does

Adds a lifecycle { create_before_destroy = true } block to each matching resource (aws_security_group, aws_acm_certificate). When a change forces replacement, Terraform and OpenTofu create the new resource first and destroy the old one only after the replacement exists, so anything still attached to the old one keeps working through the swap. A resource that already carries a lifecycle block keeps it; the argument is merged in.

Affected resources

ResourceServiceWhy
aws_security_groupAmazon VPCStill attached to a network interface, instance or load balancer when a change forces replacement
aws_acm_certificateAWS Certificate ManagerStill bound to a listener or distribution when a change forces replacement

Before and after

Before (a module without the change):

resource "aws_security_group" "this" {
  # ... resource configuration ...

  tags = var.tags
}

After (with Create before destroy applied):

resource "aws_security_group" "this" {
  # ... resource configuration ...

  tags = var.tags

  lifecycle {
    create_before_destroy = true
  }
}

The only change is the rule transformation. All existing arguments, outputs, and module behavior remain identical.

Why this rule exists

A security group that is still attached to a network interface cannot be deleted, so a change that forces replacement fails halfway through the apply. A certificate still bound to a listener has the same problem, with an outage while the listener has nothing to serve.

create_before_destroy is Terraform's own answer: create the new resource, move the references, then destroy the old one. It is a lifecycle argument, so it cannot be passed into an upstream module as a variable.

RepositoryIssueTitle
hashicorp/terraform#22544"Variables may not be used here" for prevent_destroy
hashicorp/terraform#18367Feature request: support prevent_destroy for modules
hashicorp/terraform#24746Ability to use locals or wildcards in ignore_changes lifecycle block
hashicorp/terraform#24188Support for dynamic blocks and meta-arguments
hashicorp/terraform#27360A method to override configuration and meta arguments within a module
opentofu/opentofu#1329Support variables in lifecycle blocks

Default configuration

These are the values the rule ships with. Parameters never travel in a module source URL, so a rule named there runs with exactly these defaults. Your organization sets them per rule, in its Baseline or in a ruleset.

ParameterTypeDefaultDescription
resource_typeslist(string)["aws_security_group", "aws_acm_certificate"]Resource types to replace create-first

The HCL this rule is written in, with its parameters and the transformers it runs, is published at lifecycle_create_before_destroy.

How to enable

On a framework host, add the rule on top of your organization's Baseline with ?add_rules= in the HTTPS module source:

module "acm" {
  source = "https://soc2.compliance.tf/terraform-aws-modules/acm/aws?version=x.y.z&add_rules=lifecycle_create_before_destroy"
}

On your organization's host, an org admin adds the rule to the Baseline, which reaches every module served from that host, or to a named ruleset that a module selects with ?ruleset=:

module "acm" {
  source = "https://bobthecorp.compliance.tf/terraform-aws-modules/acm/aws?version=x.y.z&ruleset=<ruleset-name>"
}

Upstream terraform-aws-acm and terraform-aws-security-group already set create_before_destroy = true, so on them the rule records not_applicable. It is for modules whose security groups or certificates do not set it.

Replace x.y.z with the release you use and bobthecorp with your organization's alias. Which parameters each host accepts, and which it refuses with a 400, is on Rulesets in the console.

How to check it worked

Read compliancetf-manifest.json in the module you received. Its entry for this rule carries an outcome: enforced means the change was made, not_applicable means nothing matched - no resource of a targeted type, or the module already carried the identical change. Do not infer enforcement from a successful terraform init; a rule that changes nothing also inits cleanly.

Known limits

  • Only aws_security_group and aws_acm_certificate by default. Other resource types need the resource_types parameter set by your organization.
  • Does not change what forces a replacement. The provider decides that; the rule changes only the order of create and destroy.
  • A security group with a fixed name cannot be created before the old one is destroyed: the replacement would take a name the old group still holds, and AWS refuses it. Give the group a name_prefix instead (in terraform-aws-modules, use_name_prefix = true).
  • Only .tf and .tofu files are read. Modules written in JSON syntax (.tf.json, .tofu.json) are left untouched.
  • Only the module's own directory and its first-level modules/* submodules are processed. Modules that this module calls by source URL are separate downloads and are handled on their own request.
  • Works with Terraform 1.x and OpenTofu 1.6 and later. The rule reads ordinary HCL, and anything it changes stays ordinary HCL.

Break-glass path

On a framework host, add ?rules=-lifecycle_create_before_destroy to the module source URL and run terraform init; the source changed, so Terraform installs a fresh copy without this rule. When you are done, remove the override and run terraform init again to restore it. On your organization's host the parameter is refused with a 400 rather than ignored. The rule comes from the organization's configuration: a Baseline rule applies to every module served from the bare organization form, so an admin disables it for the duration of the operation, or keeps it out of the Baseline and in a ruleset that only the modules needing it select. On a project environment the posture is the bound snapshot and its waivers. See Baseline rules and rulesets.

Failure modes

ScenarioResult
Rule targets a module with no matching resource typesNo-op. Module is delivered unchanged and the manifest records not_applicable.
A change forces replacement of a resource with a fixed nameThe apply fails on a name conflict because both resources exist at once. Switch the module input to name_prefix.
Another resource references the security group in its security_groups listTerraform updates the reference to the new group before destroying the old one, which is the point of the ordering.

On this page

Ask AI about this

Help improve this page