Ignore Scaling Changes
Adds ignore_changes for the capacity attribute an external autoscaler owns on Auto Scaling groups, EKS node groups and ECS services.
When to use this rule
Use this when: An autoscaler, the cluster autoscaler, Karpenter or Application Auto Scaling owns the capacity of an Auto Scaling group, an EKS node group or an ECS service, and every plan shows a capacity diff.
Do not use this when: Terraform is the only thing that sets the capacity. Ignoring the attribute would stop a deliberate change to desired_capacity, desired_size or desired_count from being applied.
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:
- Auto Scaling: Auto Scaling group desired_capacity reverts on every apply
- ECS: ECS service desired_count keeps resetting
- EKS: EKS node group desired_size changes on every plan
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 |
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
Before and after
Before (upstream module):
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 (upstream module):
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 (upstream module):
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.
Real-world scenario
The cluster autoscaler had scaled a node group up for a batch job. The next apply put the desired size back to the number in state, and the job's pods went pending.
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 actually declares the resources this rule targets,
and shows the diff the registry would serve. No account needed.
Known limits
- 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.
Default configuration
These are the values the rule ships with. They are not settable from a module source URL: ?rules= carries rule names only, so a per-request enable uses exactly the defaults below. Your organization sets them per rule, in its Baseline or in a ruleset, from the Operational Rules page; see Baseline rules and rulesets.
| 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 rule's definition
The HCL this rule is written in - its parameters, their shipped defaults, and the transformers it runs - is published at lifecycle_ignore_scaling_changes, alongside every other selectable rule.
How to enable
On a framework host, per request:
Add ?rules=lifecycle_ignore_scaling_changes to your HTTPS module source, using the id exactly as written.
module "example" {
source = "https://soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=5.0.0&rules=lifecycle_ignore_scaling_changes"
}A ?rules= list is either all bare names, which replace your organization's rules for that download, or all - prefixed names, which subtract from them. Mixing the two is refused with a 400 rather than silently treated as a replacement. To add this rule on top of your organization's rules instead, use ?add_rules=lifecycle_ignore_scaling_changes; a bare ?rules= list cannot be combined with it, because a bare list already replaced the set there was something to add to.
The + add prefix was removed. It reached the registry only when written %2B, and a literal + in a query string decodes to a space everywhere, so ?rules=+lifecycle_ignore_scaling_changes arrived as a bare name with a leading space — one step from silently replacing your organization's rules. Any +, in either spelling, is now a 400 naming ?add_rules=.
On your organization's host:
Add the rule to the organization's Baseline, which applies to every module served from the bare organization form <alias>.compliance.tf/<namespace>/<name>/<provider>, or to a named ruleset that one module selects with ?ruleset=<name> on the HTTPS form of that address. A project environment (<alias>.compliance.tf/<project>/<env>/<module>) serves the rules frozen in its bound snapshot instead: there the rule goes into the config draft and reaches the environment by promotion. On both organization forms ?rules= and ?add_rules= are refused with a 400 — including the subtractive - prefix — rather than dropped, so you cannot mistake an ignored override for an applied one. Change the configuration, not the URL. See Baseline rules and rulesets for the configuration flow, and registry resolution for how the address forms differ.
Break-glass path
On a framework host, add ?rules=-lifecycle_ignore_scaling_changes to the module source URL and run terraform init -upgrade to fetch a copy without this rule. The copy in .terraform/modules/ stays until the next upgrade, so remove the override when you are done. 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. |
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. |
Terraform and OpenTofu compatible
This rule works with both Terraform (1.x+) and OpenTofu (1.6+). It reads ordinary HCL, and anything it changes stays ordinary HCL, so the module behaves the same under either tool.
Help us improve this page
Operational Rules are a new feature. We'd love your feedback on this rule page — what's useful, what's missing, what's confusing. Share feedback.