Fix lỗi Terraform 'Duplicate resource': A managed resource has already been declared

beginner🏗️ Terraform2026-06-01| Terraform 1.x, mọi hệ điều hành (Linux/macOS/Windows), file cấu hình HCL

Error Message

A managed resource "aws_s3_bucket" "example" has already been declared at main.tf:5,1-33
#terraform#hcl#duplicate-resource#module

Lỗi Gặp Phải

Error: Duplicate resource "aws_s3_bucket" configuration

  on main.tf line 15:
  15: resource "aws_s3_bucket" "example" {

A managed resource "aws_s3_bucket" "example" has already been declared at main.tf:5,1-33

Terraform báo lỗi này ngay khi bạn chạy terraform plan hoặc terraform validate. Hai resource block dùng chung type và name — Terraform không tự đoán bạn muốn dùng cái nào. Nó dừng lại ngay ở bước parse, trước khi chạm vào state hay gọi bất kỳ provider nào.

Nguyên Nhân

Mỗi resource trong Terraform có địa chỉ gồm hai phần: resource_type.resource_name. Tổ hợp này phải là duy nhất trong một module. Nếu trùng lặp, Terraform sẽ không tải được cấu hình. Dưới đây là bốn nguyên nhân phổ biến nhất:

  • Copy-paste nhưng quên đổi tên — bạn nhân đôi một block để chỉnh vài giá trị rồi bỏ quên không đổi label.
  • Cùng một resource xuất hiện trong hai file .tf — Terraform gộp tất cả các file .tf trong thư mục lại. Một resource trong storage.tf và cùng resource đó trong main.tf sẽ xung đột nhau.
  • Gọi một module hai lần với cùng label — nếu child module khai báo resource bên trong và bạn gọi nó hai lần với label giống nhau, các tên resource sẽ xung đột trong namespace của parent.
  • Còn sót lại từ merge conflict — một lần merge Git cẩu thả để lại cả hai phiên bản của resource block chưa được giải quyết trong cùng một file.

Cách Khắc Phục Từng Bước

Bước 1 — Tìm tất cả vị trí khai báo

Thông báo lỗi đã chỉ rõ cả hai vị trí khai báo: main.tf:5 cho khai báo đầu tiên, main.tf:15 cho bản trùng lặp. Với các project lớn có nhiều file, dùng grep sẽ nhanh hơn tìm thủ công:

# Tìm kiếm trong tất cả các file .tf trong thư mục module
grep -rn 'resource "aws_s3_bucket" "example"' .

Tất cả file và số dòng khớp sẽ hiện ra ngay lập tức.

Bước 2 — Quyết định giữ block nào

Mở cả hai vị trí để so sánh. Thường thì một cái là bản gốc, cái còn lại là bản thừa vô tình để lại — hoặc một trong hai đã lỗi thời. Giữ lại cái đúng và xóa cái kia.

Trước khi sửa (bị lỗi):

# main.tf — dòng 5-10
resource "aws_s3_bucket" "example" {
  bucket = "my-app-assets"
}

# main.tf — dòng 15-20 (copy-paste rồi quên xóa)
resource "aws_s3_bucket" "example" {
  bucket = "my-app-assets"
  tags = { Environment = "prod" }
}

Sau khi sửa — xóa block đầu tiên, giữ lại block có tags:

resource "aws_s3_bucket" "example" {
  bucket = "my-app-assets"
  tags = { Environment = "prod" }
}

Bước 3 — Nếu cần hai bucket, đổi tên một cái

Có thể cả hai block đều cần thiết — chúng chỉ vô tình bị đặt cùng label. Đặt cho block thứ hai một tên khác biệt là xong:

resource "aws_s3_bucket" "example" {
  bucket = "my-app-assets"
}

resource "aws_s3_bucket" "example_logs" {
  bucket = "my-app-assets-logs"
}

Bước 4 — Sửa trường hợp module được gọi hai lần với cùng label

Gọi một child module hai lần trong cùng một parent là hoàn toàn hợp lệ — miễn là mỗi lần gọi có label riêng. Nếu không có label khác nhau, cả hai instance sẽ cố đăng ký cùng tên resource trong namespace của parent:

# BỊ LỖI: dùng cùng một label hai lần
module "storage" {
  source = "./modules/storage"
  bucket_name = "assets"
}

module "storage" {
  source = "./modules/storage"
  bucket_name = "logs"
}

# ĐÃ SỬA: mỗi lần gọi có label riêng
module "storage_assets" {
  source = "./modules/storage"
  bucket_name = "assets"
}

module "storage_logs" {
  source = "./modules/storage"
  bucket_name = "logs"
}

Mỗi lần gọi có namespace riêng. Các resource bên trong sẽ không xung đột nữa.

Bước 5 — Với nhiều resource tương tự, dùng for_each

Việc copy resource block để tạo nhiều infrastructure tương tự chính là thói quen dẫn đến lỗi này. for_each là giải pháp — một block, nhiều instance:

locals {
  buckets = {
    assets = "my-app-assets"
    logs   = "my-app-assets-logs"
    backup = "my-app-backup"
  }
}

resource "aws_s3_bucket" "example" {
  for_each = local.buckets
  bucket   = each.value

  tags = {
    Name = each.key
  }
}

Kết quả là ba instance riêng biệt — aws_s3_bucket.example["assets"], aws_s3_bucket.example["logs"], và aws_s3_bucket.example["backup"] — từ một block duy nhất. Không trùng lặp, không xung đột.

Kiểm Tra Sau Khi Sửa

terraform validate là bước kiểm tra nhanh nhất. Lệnh này phát hiện lỗi HCL mà không cần credentials hay quyền truy cập state:

terraform validate

Kết quả khi thành công:

Success! The configuration is valid.

Sau đó chạy terraform plan để xác nhận mọi thứ trông đúng.

Một trường hợp đặc biệt cần lưu ý: nếu bạn đổi tên resource thay vì xóa bản trùng lặp, Terraform sẽ lên kế hoạch xóa cái cũ và tạo lại với tên mới. Để tránh chu trình destroy/recreate này, hãy di chuyển state entry trước:

# Di chuyển state sang địa chỉ mới trước khi apply
terraform state mv aws_s3_bucket.example aws_s3_bucket.example_logs

Sau khi di chuyển, terraform plan sẽ không hiển thị thay đổi nào cho resource đó.

Checklist Nhanh

  • Chạy grep -rn 'resource "TYPE" "NAME"' trên tất cả các file .tf để tìm mọi vị trí khai báo.
  • Kiểm tra các dấu hiệu merge conflict chưa được giải quyết (<<<<<<<, =======, >>>>>>>) bên trong các file .tf.
  • Hai lần gọi module với cùng label? Đổi tên ít nhất một cái.
  • Cần tạo nhiều resource tương tự? Dùng for_each thay vì copy block.
  • Đã đổi tên resource? Chạy terraform state mv trước khi apply để tránh destroy/recreate.

Related Error Notes