How to Manage an Already Existing aws_route53_record Using Terraform Without Recreation
Use terraform import to bring the existing Route53 record into Terraform state once, then configure lifecycle blocks with prevent_destroy and ignore_changes to stop Terraform from recreating or unintentionally modifying the resource.
When you need to manage an already existing aws_route53_record using Terraform, the tool will attempt to create a new record unless you explicitly tell it the resource already exists. This happens because Terraform maintains its own state file to track infrastructure, and any resource not present in that state is treated as new infrastructure to be built. According to the hashicorp/terraform source code, the import workflow relies on specific graph nodes and provider implementations to safely bring existing resources under management without triggering destructive recreation.
Why Terraform Wants to Recreate Existing Resources
Terraform operates on a desired state model. When you define an aws_route53_record in your configuration but that record does not exist in the Terraform state file (.tfstate), Terraform assumes it needs to create it. During the planning phase, Terraform generates a graph of operations, and for new resources, it schedules a Create action.
The import mechanism overrides this behavior by injecting the real-world resource into the state. Internally, Terraform uses the graphNodeImportState struct defined in internal/terraform/node_resource_import.go to handle this operation. This graph node orchestrates the import process by calling the provider's ImportState function, which retrieves the resource's current attributes and writes them into the state file.
Step 1: Import the Existing Route53 Record into Terraform State
To manage an existing record, you must run the import command once from your CLI. The syntax requires the resource address in your Terraform configuration and the Route53 record ID, which typically follows the format ZONEID_RECORDNAME_TYPE.
terraform import aws_route53_record.my_record Z3P5AZXYZ123_example.com_A
Behind the scenes, Terraform constructs a temporary import graph and executes the graphNodeImportState node. This node delegates to the AWS provider's ImportState implementation, which uses the ImportStatePassthrough helper found in internal/legacy/helper/schema/resource_importer.go. This helper reads the resource ID and populates the state with the current values from AWS.
After the import succeeds, the resource exists in your state file. A subsequent terraform plan should show no changes if your HCL configuration matches the imported state exactly.
Step 2: Configure Lifecycle Rules to Prevent Recreation
Importing alone prevents the initial recreation, but to ensure Terraform never destroys and recreates the record during future operations, you must add a lifecycle block. This meta-argument controls how Terraform handles the resource during updates and destruction.
resource "aws_route53_record" "my_record" {
zone_id = "Z3P5AZXYZ123"
name = "example.com"
type = "A"
ttl = 300
records = ["192.0.2.44"]
lifecycle {
prevent_destroy = true
ignore_changes = [
ttl,
records,
]
}
}
The prevent_destroy = true argument causes Terraform to reject any plan that would destroy this resource, protecting against accidental deletion. The ignore_changes list tells Terraform to disregard differences in specific attributes—such as ttl or records—if they are modified outside of Terraform. This is crucial for records that might be updated by automated systems or other teams.
How Terraform Processes Imports Internally
Understanding the internal mechanics helps debug import failures. When you execute terraform import, the tool performs the following steps according to the source code in the hashicorp/terraform repository:
- Graph Construction: Terraform builds an import graph containing
graphNodeImportStatenodes (internal/terraform/node_resource_import.go). - Provider Delegation: Each node calls the provider's
ImportStatefunction, which for AWS resources typically usesImportStatePassthrough(internal/legacy/helper/schema/resource_importer.go). - State Transformation: The import state is transformed and merged into the main state file (
internal/terraform/transform_import_state_test.gocontains test cases demonstrating this behavior). - Refresh: After import, Terraform runs a refresh to align the state with the actual resource attributes.
These files define the contract between Terraform core and providers, ensuring that imported resources are treated as existing infrastructure rather than new creations.
Summary
- Import first: Use
terraform import aws_route53_record.name ZONEID_NAME_TYPEto bring existing records into state without recreation. - Protect with lifecycle: Add
lifecycle { prevent_destroy = true }to block accidental deletion andignore_changesto prevent drift detection on externally managed attributes. - Understand the internals: Terraform uses
graphNodeImportStateininternal/terraform/node_resource_import.goand provider-specificImportStatelogic to execute imports safely.
Frequently Asked Questions
What happens if I don't import the existing record before running terraform apply?
Terraform will attempt to create a new Route53 record with the same name and type, which will fail with a ResourceRecordAlreadyExists error from AWS. If you force creation by using create_before_destroy or manual state manipulation, you risk creating duplicate records or overwriting the existing one.
Can I import multiple Route53 records at once?
Terraform does not support bulk import in a single command. You must run terraform import individually for each record, specifying the full resource address and the Zone ID/Record Name/Type combination. However, you can script this process using shell loops or Terraform's for_each import patterns available in newer versions.
How do I handle records that change outside of Terraform?
Add the ignore_changes meta-argument to your lifecycle block, listing the specific attributes that external systems modify (such as ttl, records, or alias). This tells Terraform to ignore drift in those fields during planning, while still managing other attributes like zone_id or type.
What's the difference between prevent_destroy and ignore_changes?
prevent_destroy is a boolean that causes Terraform to reject any plan that would destroy the resource, protecting against accidental deletion. ignore_changes is a list of specific attributes that Terraform should not track for drift; changes to these attributes in the real world will not trigger updates, but Terraform will still modify them if other attributes change.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →