Sign in
Menu
Sign up
Docs
Operational rulesWorkarounds

EC2 instance

Some things people ask the terraform-aws-modules/ec2-instance/aws module for cannot be implemented by any module, in any registry: they are limits of Terraform and OpenTofu themselves. This page is for engineers who hit one of them. For each it says what causes it, gives the native workaround in full, and, where one exists, shows the Operational Rule that removes the need for a fork. Read the native option first; several are the right answer on their own.

Terraform wants to replace my EC2 instance because the AMI changed

The configuration resolves the AMI through a data source with most_recent = true. When AWS publishes a new image, the data source returns a different id and Terraform plans to replace a running instance - usually at the least convenient moment.

Why Terraform cannot fix this. Terraform's model is that it owns every attribute it can see. When a controller, an autoscaler, a scanner, or a deployment pipeline writes to one of those attributes, Terraform reads the new value as drift and plans to put its own value back. Both systems are behaving correctly; they simply disagree about who owns the field.

The only mechanism Terraform offers for splitting ownership is ignore_changes, and that is a lifecycle argument, which brings the literal-value restriction above with it. A module cannot accept the list of attributes to ignore as an input, so a module consumer has no way to declare the split.

RepositoryIssueTitle
hashicorp/terraform#27360A method to override configuration and meta arguments within a module
hashicorp/terraform#24188Support for dynamic blocks and meta-arguments
hashicorp/terraform-provider-aws#19583Provider produced inconsistent final plan / an invalid new value for .tags_all

The native workaround. Pin the AMI id instead of resolving the newest one. An explicit id, or an SSM parameter you promote deliberately, removes the surprise entirely and is the right answer for anything long-lived.

Where the lookup has to stay dynamic, this module has a native option: set ignore_ami_changes = true. The module then builds the instance from a second resource block, aws_instance.ignore_ami, which ignores ami. Turning it on for an instance that already exists moves it to a new state address, so add a moved block from the module's aws_instance.this[0] to aws_instance.ignore_ami[0] in the same change, or Terraform plans to replace it. The rule below ignores ami on the resource where it is, with no state move.

The rule that answers it. lifecycle_ignore_ami_changes adds the block for you, server-side, while the module is being downloaded. What arrives in .terraform/modules/ is ordinary HCL with this change already in it, and nothing else about the module changes: same inputs, same outputs, same version.

 resource "aws_instance" "this" {
   # ... the module's own configuration, unchanged ...
+
+  lifecycle {
+    ignore_changes = [ami]
+  }
 }

Use the rule

This source line applies the rules above during terraform init. The inputs and outputs stay the upstream module's.

module "ec2_instance" {
  source = "https://registry.compliance.tf/terraform-aws-modules/ec2-instance/aws?version=x.y.z&add_rules=lifecycle_ignore_ami_changes"

  # ... the same inputs you pass today; the version is in the URL above ...
}
  • Pin the version in the URL. ?version= takes an exact release; without it the registry serves the latest. Delete the version argument, which Terraform accepts only on registry-protocol sources. Several rules go in ?add_rules=, separated by commas.
  • Keep the token out of the source line. Register a free compliance.tf account and configure an access token; it never goes in the source line or in git.
  • Preview in the Playground, within its limits. The Rules Playground shows the diff with no account, at the version it pins for EC2 instance, which need not be yours. It reads the module root only: a rule whose resource lives in a submodule shows no diff there even though the download applies it, and a module that publishes nothing but submodules cannot be previewed. It takes the list as ?rules= because it previews rules on their own rather than against your Baseline.

On registry.compliance.tf, or on a framework host, ?add_rules= adds to whatever Baseline your organization has Enforced; on an organization host that organization's configuration decides. A module compliance.tf builds for you carries a compliancetf-manifest.json naming every rule the build received, each with an outcome recording what it did.

On this page

Ask AI about this

Help improve this page