[AWS Provider v6] Part 1: Solving the 'Forced Replace' Crisis for 200 Resources

AWS Provider v6 Migration
terraformawss3iambreaking-changemigrationsecuritydrift

The transition from Terraform AWS Provider v5 to v6 was not just a simple patch; it was a "cataclysm." I recently migrated about 50 operating modules to v6 and faced a daunting situation where over 200 resources were pending "Replace" (destroy and recreate) even though I only ran plan without modifying the code.

This post is a troubleshooting log of 3 critical Breaking Changes encountered in a production environment and how I resolved them to achieve "Plan: 0 to add, 0 to change, 0 to destroy."

1. Observation Environment

  • Terraform Core: v1.5.7 (The minimum version where the import block worked stably in my environment)
  • AWS Provider: v5.87.0 (Last version before v6) → v6.0.0
  • Backend: S3 + DynamoDB (State Locking)
  • Target: Approx. 50 modules including EC2, RDS, S3, IAM, etc.

2. Issue 1: Stricter Type Checking (Boolean String)

From v6, HCL's type flexibility has been removed. Immediate errors occurred in 12 legacy modules out of 50.

[Log]

Error: Invalid value for boolean
  on modules/s3_base/main.tf line 24: mfa_delete = "false"

[Solution]
I corrected "false" (String) to false (Boolean). Since global regex replacement is risky (danger of modifying tag values), I used IDE search to modify only the specific attribute values.

  • Result: Errors 12 → 0. However, about 200 changes still remained in the plan.

3. Issue 2: S3 Lifecycle Rule Conflict

It was observed that inline lifecycle_rule blocks within aws_s3_bucket resources were ignored or deleted in v6.

[Plan Diff]
Existing data retention rules were marked for deletion (Risk of data loss).

~ resource "aws_s3_bucket" "log_archive" {
      - lifecycle_rule { - id = "log_expiration_90days" ... }
    }

[Solution]
I prevented policy deletion by removing the inline block and separating it into a distinct aws_s3_bucket_lifecycle_configuration resource.

4. Issue 3: Default Value Changes & 'Forced Replace'

This was the most dangerous issue. Over 80% of the 200 pending Replace items were Launch Templates and Security Group Rules.

[Root Cause Analysis]
The default value for the disable_api_termination attribute in aws_launch_template changed from null to false. In my case, combined with changes in user_data (Base64 encoding method differences), Terraform judged this as "Modifications impossible, must delete and recreate (ForceNew)."

[Solution]
Instead of relying on the Provider's default values, I explicitly defined the existing state (true or false) in the code.

resource "aws_launch_template" "app" {
  # Defense against v6 default value change: Explicitly set value to remove Diff
  disable_api_termination = false 
}
  • Result: Diff 200 → 145 → 0 (No changes achieved).

5. Summary of Principles

  • Drift First: Eliminate existing inconsistencies first using terraform plan -refresh-only.
  • Code Defense: Defend against Diffs caused by default value changes by modifying the code (fixing values).
  • Zero Diff Goal: The goal at the time of upgrade is not refactoring, but 'Zero Changes'.