Terraform Error Guide: 'Duplicate resource configuration' (two blocks with the same type and name)
Fix Terraform's 'Duplicate resource configuration' error caused by copy-paste, re-included files, or leftover blocks after a refactor.
- #terraform
- #troubleshooting
- #errors
- #configuration
Exact Error Message
When Terraform finds two resource blocks that share the same type and name in a single module, it refuses to parse the configuration and prints something like this:
Error: Duplicate resource "aws_s3_bucket" configuration
on storage.tf line 14, in resource "aws_s3_bucket" "logs":
14: resource "aws_s3_bucket" "logs" {
A aws_s3_bucket resource named "logs" was already declared at main.tf line 3.
Resource names must be unique per type in each module.
The message is precise: it tells you the file and line of the second declaration, then points back to where the first one lives. The error surfaces during terraform validate, terraform plan, or any command that has to load the configuration, before Terraform ever talks to a provider or your cloud account.
What the Error Means
Terraform treats every .tf file in a directory as part of a single module. It does not matter how you split your code across files; at load time Terraform concatenates main.tf, storage.tf, network.tf, and everything else into one logical configuration.
Within that combined module, a resource is uniquely identified by its type plus its local name, written as <type>.<name>. So aws_s3_bucket.logs is one address, and Terraform expects exactly one block to define it. If two blocks both try to define aws_s3_bucket.logs, Terraform has no way to know which configuration you mean, so it fails fast rather than guessing.
This is purely a parse-time, in-code conflict. It has nothing to do with state or with resources that already exist in your cloud account. It is a code-level uniqueness rule: one address, one block, per module.
Common Causes
Several everyday situations produce a duplicate declaration:
- Copy-paste duplication. You copy a resource block to create a similar one but forget to rename it. Both blocks keep the name
logs. - The same resource defined across two
.tffiles. A block lives inmain.tfand an identical or near-identical block lives instorage.tf. Because Terraform loads both files into one module, the collision is real even though the blocks are in separate files. - Leftover block after splitting into modules. You move a resource into a child module but forget to delete the original block from the root, so it now exists in both the root module and the child.
- Generated and hand-written config collision. A code generator (for example a tool that emits
generated.tf) declaresaws_s3_bucket.logs, and you also wrote it by hand inmain.tf. Both files sit in the same directory. - The same module sourced twice without unique resource names. This is the subtler cousin: calling the same module twice is fine as long as each
moduleblock has a distinct label. If you copy amodule "storage"block and forget to rename the copy, you get a duplicate module call, which triggers the same class of error for module instances.
You will see closely related siblings with the same wording. Duplicate variable appears when two variable "region" blocks exist, Duplicate output when two output "bucket_name" blocks exist, and Duplicate provider configuration when two unaliased provider "aws" blocks exist. The underlying rule is identical: these top-level constructs must be unique per module.
How to Reproduce the Error
Create a directory with two files. main.tf:
resource "aws_s3_bucket" "logs" {
bucket = "example-app-logs"
}
storage.tf:
resource "aws_s3_bucket" "logs" {
bucket = "example-app-logs-archive"
}
Run terraform validate and Terraform reports the duplicate aws_s3_bucket.logs declaration, pointing at storage.tf and referring back to main.tf. The two bucket values differ, which shows that the conflict is about the address, not the attributes.
Diagnostic Commands
All of the commands below are read-only. They inspect your configuration without changing infrastructure or state.
Run validation to confirm the error and see both file locations:
terraform validate
Search every .tf file for the conflicting address. This is usually the fastest way to find both declarations:
grep -rn 'resource "aws_s3_bucket" "logs"' .
To catch every declaration of a type regardless of name, or to find duplicate names quickly:
grep -rn 'aws_s3_bucket' *.tf
List the files Terraform will load so you can spot a stray copy such as storage.tf.bak or generated.tf:
ls -la *.tf
If you suspect a generated file, look for one and compare it against your hand-written config:
grep -rln 'Generated by' .
A planning run will also report the duplicate and nothing else, since the configuration cannot load:
terraform plan
If the resource was already applied at some point and you want to understand what is tracked, inspect state read-only:
terraform state list
Do not run terraform apply, terraform destroy, or terraform state rm to fix a parse error. The problem is in your code, and these commands either will not run or will make things worse.
Step-by-Step Resolution
1. Read the error and open both files. The message gives you both line numbers: the second declaration and the original. Open each and decide which one you actually want.
2. Decide whether the duplicate is accidental or intentional.
If it is accidental (copy-paste, leftover, generated collision), you simply have one block too many.
If you genuinely need two similar resources, you need two distinct addresses.
3a. Accidental duplicate: delete or rename the extra block. Remove the block you no longer want. If the second one was meant to be a different resource, rename it:
resource "aws_s3_bucket" "logs" {
bucket = "example-app-logs"
}
resource "aws_s3_bucket" "logs_archive" {
bucket = "example-app-logs-archive"
}
Now the addresses are aws_s3_bucket.logs and aws_s3_bucket.logs_archive, both unique.
3b. Intentional duplicates: consolidate with count or for_each. If you keep writing near-identical blocks, fold them into one block driven by a collection. for_each is preferred because it gives each instance a stable key:
resource "aws_s3_bucket" "logs" {
for_each = toset(["primary", "archive"])
bucket = "example-app-logs-${each.key}"
}
This produces aws_s3_bucket.logs["primary"] and aws_s3_bucket.logs["archive"], distinct addresses from a single block.
4. Leftover-after-refactor case: remove the root copy. If the resource now lives in a child module, delete the original block from the root module so the child module owns it exclusively.
5. Duplicate module call: rename the second module block. If you copied a module block, give the copy a unique label:
module "storage_primary" {
source = "./modules/storage"
}
module "storage_archive" {
source = "./modules/storage"
}
6. Re-validate. Run terraform validate again. Once it passes, run terraform plan to confirm the resulting configuration matches your intent before applying.
Prevention and Best Practices
- Remember the one-module rule. Every
.tffile in a directory is part of the same module, so a name only needs to collide across files to break. Mentally concatenate your files when reasoning about uniqueness. - Use descriptive, specific local names.
aws_s3_bucket.access_logsandaws_s3_bucket.app_datacollide far less often than two buckets both namedbucketormain. - Run
terraform validatein CI. This catches duplicates on every pull request, long before they reach a real apply. - Keep generated files in their own directory or namespace. If a tool emits
generated.tf, do not hand-write resources of the same type and name in that directory. - Reach for
for_eachearly. When you notice yourself copy-pasting a resource block, that is the signal to consolidate into a single block with a collection. - Format and review diffs. Run
terraform fmtand read the diff; a duplicated block is obvious in review when the formatting is consistent.
For broader runbooks and more Terraform guides, see the Terraform category. If a duplicate is blocking a live deployment and you need structured triage, the incident response dashboard can help you work the problem methodically.
Related Errors
- Duplicate variable / output / provider configuration. Same rule applied to other top-level blocks. Rename or remove the extra declaration, or use a
provideralias when you genuinely need two provider configurations. - Resource already managed / already exists. A state conflict rather than a code conflict, often fixed with
terraform import. See Terraform error: resource already exists / import. - Reference to undeclared resource. The opposite problem, where you reference an address that has no block at all. See Terraform error: reference to undeclared resource.
Frequently Asked Questions
Why does Terraform see a duplicate when the blocks are in different files?
Terraform loads every .tf file in a directory as one module before evaluating anything. File boundaries do not create separate namespaces, so two blocks with the same type and name collide no matter which files hold them.
Can I have the same resource name for two different resource types?
Yes. Uniqueness is per type. aws_s3_bucket.logs and aws_iam_role.logs coexist happily because their full addresses differ. The conflict only occurs when both the type and the name match.
How do I keep two genuinely similar resources without duplicating blocks?
Use count or, preferably, for_each on a single resource block. for_each keys each instance by a stable identifier, producing distinct addresses like aws_s3_bucket.logs["archive"] from one block, which is easier to extend than copy-pasting.
Will deleting one of the duplicate blocks affect my real infrastructure?
Deleting a block is a code change, and Terraform will plan accordingly. If both blocks describe the same real resource, keep the correct one and remove the other, then review terraform plan carefully before applying so you do not accidentally schedule a destroy.
Does this error ever come from a module I am calling rather than my own code?
It can, if you copied a module block without renaming its label, creating two module calls with the same name. Give each module block a unique label. A duplicate inside a third-party module’s own code would need to be fixed in that module’s source.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.