Terraformの「Duplicate resource」エラーを修正する:マネージドリソースがすでに宣言されています

beginner🏗️ Terraform2026-06-01| Terraform 1.x、任意のOS(Linux/macOS/Windows)、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

エラーの内容

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 plan または terraform validate を実行した瞬間に発生します。2つのリソースブロックが同じタイプと名前を持っているため、Terraformはどちらを意図したのか判断できません。状態ファイルへのアクセスやプロバイダーの呼び出しを行う前に、パース段階で処理が停止します。

発生する原因

Terraformの各リソースは resource_type.resource_name という2つの部分からなるアドレスを持ちます。この組み合わせはモジュール内で一意でなければなりません。同じペアが2回登場すると、Terraformは一切ロードを行いません。よくある原因は以下の4つです:

  • コピー&ペースト時のラベル変更忘れ — ブロックを複製して値を調整する際に、ラベルをそのまま残してしまった場合。
  • 2つの .tf ファイルに同じリソースが存在する — Terraformはディレクトリ内のすべての .tf ファイルをマージします。storage.tfmain.tf の両方に同じリソースがあると競合します。
  • 同じラベルでモジュールを2回呼び出している — 子モジュールがリソースを内部で宣言しており、同一ラベルで2回呼び出すと、親の名前空間でリソース名が衝突します。
  • Gitマージ時の競合が残っている — 不完全なGitマージにより、同一ファイル内にリソースブロックの両バージョンが未解決のまま残っている場合。

修正手順

手順1 — すべての該当箇所を見つける

エラーメッセージにはすでに両方の宣言場所が示されています:最初の宣言が main.tf:5、重複が main.tf:15 です。ファイル数が多い大規模プロジェクトでは、手動で探すより grep を使うほうが速いです:

# モジュールディレクトリ内のすべての .tf ファイルを検索する
grep -rn 'resource "aws_s3_bucket" "example"' .

一致するファイル名と行番号がすぐに表示されます。

手順2 — 残すブロックを決める

両方の場所を並べて確認します。通常、一方が元のブロックで、もう一方は誤って残ったものか、単純に古くなったものです。正しい方を残し、もう一方を削除します。

修正前(エラーあり):

# main.tf — 5〜10行目
resource "aws_s3_bucket" "example" {
  bucket = "my-app-assets"
}

# main.tf — 15〜20行目(コピー&ペーストされたまま放置)
resource "aws_s3_bucket" "example" {
  bucket = "my-app-assets"
  tags = { Environment = "prod" }
}

修正後 — 最初のブロックを削除し、タグ付きのブロックを残す:

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

手順3 — 2つのバケットが必要な場合は一方の名前を変える

両方のブロックが意図的なものであり、たまたま同じラベルになってしまった場合は、2つ目に別の名前を付ければ解決します:

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

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

手順4 — 同じラベルで2回呼び出しているモジュールを修正する

1つの親から子モジュールを2回呼び出すこと自体は問題ありません。ただし、各呼び出しに一意のラベルが必要です。同じラベルを使うと、両方のインスタンスが親の名前空間に同じリソース名を登録しようとして衝突します:

# 問題あり:同じラベルを2回使用している
module "storage" {
  source = "./modules/storage"
  bucket_name = "assets"
}

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

# 修正済み:呼び出しごとに一意のラベルを使用する
module "storage_assets" {
  source = "./modules/storage"
  bucket_name = "assets"
}

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

各呼び出しが独自の名前空間を持つため、内部のリソースが衝突しません。

手順5 — 類似リソースが多い場合は for_each を使う

似たようなインフラをセットアップするためにリソースブロックをコピーする習慣こそ、このエラーの温床です。for_each を使えば、1つのブロックで複数のインスタンスを管理できます:

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
  }
}

これにより、1つのブロックから aws_s3_bucket.example["assets"]aws_s3_bucket.example["logs"]aws_s3_bucket.example["backup"] という3つの独立したインスタンスが作成されます。重複も衝突も発生しません。

修正の確認

terraform validate が最も手軽な最初の確認手段です。認証情報や状態ファイルへのアクセスなしにHCLエラーを検出できます:

terraform validate

正常時の出力:

Success! The configuration is valid.

続けて terraform plan を実行し、内容が正しいことを確認します。

注意点:重複を削除するのではなくリソースをリネームした場合、Terraformは古い名前のリソースを削除し、新しい名前で再作成する計画を立てます。その削除と再作成を避けるには、適用前に状態エントリを移動してください:

# 適用前に新しいアドレスへ状態を移動する
terraform state mv aws_s3_bucket.example aws_s3_bucket.example_logs

移動後、terraform plan を実行するとそのリソースに変更がないことが確認できます。

確認チェックリスト

  • すべての .tf ファイルに対して grep -rn 'resource "TYPE" "NAME"' を実行し、すべての宣言箇所を特定する。
  • .tf ファイル内に未解決のGitマージ競合マーカー(<<<<<<<=======>>>>>>>)がないか確認する。
  • 同じラベルでモジュールを2回呼び出している場合は、少なくとも一方をリネームする。
  • 複数の類似リソースを作成する場合は、ブロックをコピーする代わりに for_each を使用する。
  • リソースをリネームした場合は、削除と再作成を防ぐために適用前に terraform state mv を実行する。

Related Error Notes