Terraform’s replace command doesn’t actually replace anything; it’s a way to tell Terraform to forget about a resource and create a new one as if the old one never existed.

Imagine you have a Terraform configuration that defines an AWS S3 bucket.

resource "aws_s3_bucket" "my_bucket" {
  bucket = "my-unique-terraform-bucket-12345"
  acl    = "private"

  tags = {
    Name        = "MyBucket"
    Environment = "Dev"
  }
}

Later, you realize you need to change the acl from "private" to "public-read". Terraform, by default, won’t allow this because changing the acl on an S3 bucket is often a destructive operation that AWS doesn’t support in-place.

Error: error updating S3 bucket (my-unique-terraform-bucket-12345): InvalidBucketAclWithObjectOwnership: The bucket owner owns the objects and the bucket ACLs are not permitted.

This is where terraform replace comes in. It’s not about modifying the existing resource; it’s about marking the existing resource as "gone" in Terraform’s state and then letting Terraform create a brand new resource with the updated configuration.

Here’s how you’d use it:

First, you need to identify the resource you want to "replace." You do this using its address in Terraform. For our S3 bucket, the address is aws_s3_bucket.my_bucket.

Then, you run the replace command:

terraform state rm aws_s3_bucket.my_bucket

This command doesn’t delete the actual S3 bucket from AWS. It only removes the resource’s entry from Terraform’s state file (terraform.tfstate). The S3 bucket still exists in AWS, but Terraform no longer knows about it.

After removing it from the state, you apply your configuration again. Terraform will see that aws_s3_bucket.my_bucket is no longer in its state, but it is defined in your configuration. It will then attempt to create a new S3 bucket.

terraform apply

However, this will fail because an S3 bucket with the name "my-unique-terraform-bucket-12345" already exists in AWS. Terraform can’t create a new one with the same name.

This is where the terraform state replace command (which is slightly different from terraform state rm and what people often mean when they say "replace") is actually useful. The replace command is a more direct way to achieve the "forget and recreate" workflow.

Let’s rewind. Suppose you want to change the ACL. You’ve modified your configuration to:

resource "aws_s3_bucket" "my_bucket" {
  bucket = "my-unique-terraform-bucket-12345"
  acl    = "public-read" # Changed from "private"

  tags = {
    Name        = "MyBucket"
    Environment = "Dev"
  }
}

Now, instead of just running terraform apply which would error out, you use terraform state replace:

terraform state replace aws_s3_bucket.my_bucket

This command tells Terraform: "I want to replace the resource currently known as aws_s3_bucket.my_bucket in my state with a new one based on the current configuration."

Terraform will then perform the following steps internally:

  1. Identify the resource: It finds aws_s3_bucket.my_bucket in your terraform.tfstate file.
  2. Mark for replacement: It flags this resource as pending replacement.
  3. Plan creation: It looks at your current configuration and plans to create a new resource with the desired state (e.g., acl = "public-read").
  4. Plan destruction (of the old state entry): It plans to remove the old entry from the state file.
  5. Execute: When you run terraform apply after terraform state replace, Terraform will first execute the create operation for the new resource. Since the original bucket still exists, this will fail. This is the crucial point.

The terraform state replace command is often misunderstood. It’s not a magic bullet that makes AWS magically allow in-place updates. It’s a command that manipulates Terraform’s state to facilitate a recreate workflow.

The actual, common workflow to achieve a resource replacement when an in-place update isn’t possible involves these steps:

  1. Modify your Terraform configuration to the desired state.
  2. Run terraform plan. You will likely see an error indicating that the change cannot be applied (like the InvalidBucketAclWithObjectOwnership error).
  3. Manually intervene or use a specific provider feature. For S3 ACLs, the error message itself gives a hint: "The bucket owner owns the objects and the bucket ACLs are not permitted." This implies a change in ownership or configuration that AWS doesn’t permit alongside ACL changes. The more robust solution is often to use S3 Object Ownership settings.
  4. A more direct approach for replacement: If you truly want to replace a resource and are okay with potential downtime or data loss (if not handled carefully), you can use terraform state rm and then terraform apply.

Let’s illustrate the state rm then apply method, which is what most people mean when they talk about forcing recreation.

Scenario: You have an aws_instance and you want to change its ami to a completely different one, which is not allowed in-place.

Step 1: Modify Configuration

resource "aws_instance" "web_server" {
  ami           = "ami-0abcdef1234567890" # New AMI ID
  instance_type = "t3.micro"
  # ... other configurations
}

Step 2: Remove from State

terraform state rm aws_instance.web_server
  • Diagnosis: This command tells Terraform to forget about the aws_instance.web_server resource that currently exists in your terraform.tfstate. The actual EC2 instance in AWS is not terminated by this command.
  • Fix: No fix here, this is the diagnostic step.
  • Why it works: Terraform’s state file is its source of truth for what it manages. By removing an entry, Terraform believes that resource no longer exists under its management.

Step 3: Apply Configuration

terraform apply
  • Diagnosis: Terraform will see that aws_instance.web_server is defined in your configuration but is missing from the state file. It will then plan to create a new EC2 instance using the new AMI ID (ami-0abcdef1234567890). It will also detect that the old instance is still running in AWS (because state rm didn’t delete it) and will attempt to destroy it.
  • Fix: The terraform apply command will first provision the new EC2 instance. Once that is successful, it will proceed to destroy the old EC2 instance.
  • Why it works: Terraform’s apply process is to reconcile the desired state (your configuration) with the actual state (what exists in your cloud provider and state file). When a resource is missing from the state but present in the config, it’s created. When a resource is in the state but not in the config (or marked for removal), it’s destroyed. In this state rm then apply scenario, Terraform sees the old instance as something it should have managed but no longer does (because it was rm’d), and the new instance as something it should manage but doesn’t yet (because it’s in the config but not state). The default behavior is to create first, then destroy.

Important Considerations:

  • Downtime: This process will involve downtime for your application because the old instance is destroyed after the new one is created.
  • Data Loss: If your instance has local storage that isn’t backed by EBS volumes that are preserved separately, that data will be lost.
  • Resource Dependencies: Be mindful of dependencies. If other resources depend on the resource being replaced, they might fail during the apply phase.
  • IP Addresses: If the resource has a static IP or Elastic IP associated with it, you’ll need to ensure it’s detached from the old resource and re-associated with the new one, or that your configuration handles this. For EC2 instances, you’d typically re-associate an Elastic IP after the new instance is running.

The next error you’ll likely hit is a dependency issue if not all resources were correctly accounted for in the state removal and re-creation process.

Want structured learning?

Take the full Terraform course →