Ignore scaling changes
For platform engineers deciding whether to apply lifecycle_ignore_scaling_changes: what it changes, how to enable it, and how to check it worked.
Adds ignore_changes for the capacity attribute an external autoscaler owns on Auto Scaling groups, EKS node groups and ECS services.
What this rule does
Adds a lifecycle { ignore_changes = [...] } block to each matching resource. Terraform and OpenTofu will ignore external changes to the listed attributes on subsequent plans.
Ignored attributes: desired_capacity
Adds a lifecycle { ignore_changes = [...] } block to each matching resource. Terraform and OpenTofu will ignore external changes to the listed attributes on subsequent plans.
Ignored attributes: scaling_config[0].desired_size
Adds a lifecycle { ignore_changes = [...] } block to each matching resource. Terraform and OpenTofu will ignore external changes to the listed attributes on subsequent plans.
Ignored attributes: desired_count
Affected resources
| Resource | Service | Why |
|---|---|---|
aws_autoscaling_group | Amazon EC2 Auto Scaling | desired_capacity, moved by scaling policies and the cluster autoscaler |
aws_eks_node_group | Amazon EKS | scaling_config[0].desired_size, moved by the cluster autoscaler or Karpenter |
aws_ecs_service | Amazon ECS | desired_count, moved by Application Auto Scaling |
Before and after
Before (a module without the change):
resource "aws_autoscaling_group" "this" {
# ... resource configuration ...
tags = var.tags
}After (with Ignore scaling changes applied):
resource "aws_autoscaling_group" "this" {
# ... resource configuration ...
tags = var.tags
lifecycle {
ignore_changes = [desired_capacity]
}
}Before (a module without the change):
resource "aws_autoscaling_group" "this" {
# ... resource configuration ...
tags = var.tags
}After (with Ignore scaling changes applied):
resource "aws_autoscaling_group" "this" {
# ... resource configuration ...
tags = var.tags
lifecycle {
ignore_changes = [scaling_config[0].desired_size]
}
}Before (a module without the change):
resource "aws_autoscaling_group" "this" {
# ... resource configuration ...
tags = var.tags
}After (with Ignore scaling changes applied):
resource "aws_autoscaling_group" "this" {
# ... resource configuration ...
tags = var.tags
lifecycle {
ignore_changes = [desired_count]
}
}The only change is the rule transformation. All existing arguments, outputs, and module behavior remain identical.
The example above is illustrative. To see this rule run for real, open it in the
Rules Playground: it applies lifecycle_ignore_scaling_changes to a
reviewed upstream module that declares the resources this rule targets, and
shows the diff the registry would serve. No account needed.
Why this rule exists
An autoscaler changes the desired capacity many times a day. Terraform state holds the number from the last apply, so every plan reads the live value as drift and proposes setting it back, and an apply that goes through undoes the scaler.
The fix is ignore_changes on exactly that attribute, and a lifecycle block cannot be passed into an upstream module as a variable.
Related Terraform and OpenTofu issues
| Repository | Issue | Title |
|---|---|---|
| hashicorp/terraform | #27360 | A method to override configuration and meta arguments within a module |
| hashicorp/terraform | #24188 | Support for dynamic blocks and meta-arguments |
| hashicorp/terraform-provider-aws | #19583 | Provider produced inconsistent final plan / an invalid new value for .tags_all |
Limitations this rule answers
Per-module pages under Workarounds where this rule is the answer, each with the native workaround first and the diff the rule makes:
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.
| Parameter | Type | Default | Description |
|---|---|---|---|
autoscaling_group_ignore_changes | list(string) | ["desired_capacity"] | Attributes to ignore on autoscaling groups. Override to [] to contribute nothing to the merged set |
autoscaling_group_resource_types | list(string) | ["aws_autoscaling_group"] | Resource types carrying the autoscaling group capacity attribute |
eks_node_group_ignore_changes | list(string) | ["scaling_config[0].desired_size"] | Attributes to ignore on EKS node groups; the desired size sits inside the scaling_config block. Override to [] to contribute nothing to the merged set |
eks_node_group_resource_types | list(string) | ["aws_eks_node_group"] | Resource types carrying the EKS node group capacity attribute |
ecs_service_ignore_changes | list(string) | ["desired_count"] | Attributes to ignore on ECS services. Override to [] to contribute nothing to the merged set |
ecs_service_resource_types | list(string) | ["aws_ecs_service"] | Resource types carrying the ECS service capacity attribute |
The HCL this rule is written in, with its parameters and the transformers it runs, is published at lifecycle_ignore_scaling_changes.
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 "autoscaling" {
source = "https://soc2.compliance.tf/terraform-aws-modules/autoscaling/aws?version=x.y.z&add_rules=lifecycle_ignore_scaling_changes"
}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 "autoscaling" {
source = "https://bobthecorp.compliance.tf/terraform-aws-modules/autoscaling/aws?version=x.y.z&ruleset=<ruleset-name>"
}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
- terraform-aws-eks node groups and the terraform-aws-ecs service module already ignore these attributes, so on those modules the rule plans no change. It matters for
aws_eks_node_groupandaws_ecs_servicewritten by hand or in modules that do not ignore them. To CHANGE an EKS node group's size from Terraform, seeeks_node_group_set_desired_size. - terraform-aws-autoscaling has its own
ignore_desired_capacity_changes, which moves the group to a new state address; this rule ignoresdesired_capacityon the resource where it is. - Only the desired-capacity attribute on each of the three resource types.
min_sizeandmax_sizestay managed, which is how the scaler's bounds remain under Terraform's control. - Each attribute list is a parameter; an organization can set one to
[]to contribute nothing for that resource type. - DynamoDB capacity is a separate rule,
lifecycle_ignore_autoscaling_changes. - Only
.tfand.tofufiles 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_ignore_scaling_changes 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
| Scenario | Result |
|---|---|
| Rule targets a module with no matching resource types | No-op. Module is delivered unchanged and the manifest records not_applicable. |
| The module already ignores the attribute (terraform-aws-eks node groups, the terraform-aws-ecs service module) | No diff. The attribute is already in the module's ignore_changes list. |
You change desired_capacity in Terraform on purpose | The change is ignored on plan. Change min_size and max_size instead, or set the scaler's target. |
The resource already carries an ignore_changes list | The list is kept and the attribute is merged in. |
Related
- Auto Scaling workarounds
- Ignore autoscaling changes
- Ignore deployed artifacts
- Rulesets in the console - where an org admin adds this rule to the Baseline or a ruleset
- Rule catalog - every rule, with what each one changes