エラーのシナリオ
terraform plan — あるいは terraform validate を実行すると、Terraform が次のメッセージを出して突然停止します:
Error: Duplicate provider "aws" configuration
A module may have only one "default" (non-aliased) provider configuration per provider type.
プランは何も実行されず、適用も行われません。問題ないと思っていた設定が読み込みを拒否したのです。
これは主に次の3つの状況で発生します:
- 別のファイルからプロバイダーブロックをコピー&ペーストした際、すでにそのブロックが存在するモジュールに貼り付けてしまった
- Terraform の設定を複数の
.tfファイルに分割し、2つのファイルで同じプロバイダーを定義してしまった main.tfにデフォルトのプロバイダーブロックがすでに存在することに気づかず、2つ目の AWS リージョン設定を追加してしまった
Terraform がこのエラーを出す理由
Terraform は、単一のモジュール内でプロバイダータイプごとに デフォルト のプロバイダーブロックを1つしか許可しません。デフォルトプロバイダーとは、alias 引数が設定されていない provider ブロックのことです。同じモジュール内に — どの .tf ファイルにあるかに関わらず — このようなブロックが2つ存在すると、Terraform はどちらをデフォルトとして使用すべきか判断できず、推測するのではなくエラーを出して停止します。
以下はエラーを再現する最小限の例です:
# main.tf
provider "aws" {
region = "us-east-1"
}
# providers.tf — 同じモジュール、別のファイル
provider "aws" {
region = "ap-southeast-1"
}
Terraform はディレクトリ内のすべての .tf ファイルを1つの論理モジュールにマージします。上記の両ブロックが aws のデフォルトプロバイダーになってしまい、それが競合を引き起こします。
クイックフィックス:2つ目のプロバイダーにエイリアスを追加する
2つの異なる AWS 設定(2つのリージョン、2つのアカウント、2セットの認証情報)が実際に必要な場合、1つのブロックをデフォルトのままにして、もう一方に alias を付与するのが解決策です。
# providers.tf
provider "aws" {
region = "us-east-1" # デフォルトプロバイダー — エイリアスなし
}
provider "aws" {
alias = "ap"
region = "ap-southeast-1"
}
2つ目の設定を必要とするリソースは、provider 引数を使って明示的に参照します:
resource "aws_s3_bucket" "asia_logs" {
provider = aws.ap
bucket = "my-asia-logs-bucket"
}
provider 引数のないリソースは、自動的にデフォルト(エイリアスなし)のブロックを使用します。エイリアス付きのプロバイダーを使用したい場合にのみ指定が必要です。
根本的な解決策:重複を削除する
AWS の設定が1つだけ必要な場合、本来の解決策は重複したブロックを完全に削除することです。まずすべての箇所を見つけます:
grep -rn 'provider "aws"' .
出力例は次のようになります:
./main.tf:1:provider "aws" {
./providers.tf:1:provider "aws" {
両方のファイルを開き、正しい設定がどちらにあるかを判断して、もう一方を削除します。そして、すべてを単一の信頼できるプロバイダーブロックに統合します — 慣例的な場所は providers.tf または versions.tf です:
# providers.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
profile = "my-profile"
}
特殊ケース:子モジュール
子モジュールを呼び出す際に特定のプロバイダー設定を使用させたい場合は、providers マップを使ってルートからプロバイダーを渡します。子モジュールは独自の provider "aws" {} ブロックを定義すべきではなく、必要なものだけを宣言するようにします。
# ルートモジュール — 子を呼び出してプロバイダーを渡す
module "networking" {
source = "./modules/networking"
providers = {
aws = aws.ap # エイリアス付きプロバイダーを明示的に渡す
}
}
# modules/networking/versions.tf — 要件のみを宣言し、完全なプロバイダーブロックは記述しない
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
子モジュール内に provider "aws" { region = ... } ブロックを追加すると、Terraform の観点からは2つ目のデフォルトプロバイダーが作成されます — サブディレクトリ内にあるため気づきにくいだけで、同じエラーが発生します。
確認手順
変更を加えた後、次の3つのコマンドを順番に実行してください:
# 1. 設定の構文と構造を検証する
terraform validate
# 2. プロバイダーの依存関係ツリーを確認する
terraform providers
# 3. プランが正常に実行されることを確認する
terraform plan
terraform validate の出力は次のようになるはずです:
Success! The configuration is valid.
terraform providers は、プロバイダーがどこから取り込まれているかを診断するのに特に役立ちます。子モジュールを含む完全なツリーが表示されるため、ネストされたモジュールに重複が隠れていても、ここで確認できます。
予防のヒント
最も効果的な習慣はひとつです:プロバイダーとバージョンの宣言をすべて1つのファイル(providers.tf または versions.tf)にまとめ、他の場所には絶対に provider ブロックを書かないことです。プロバイダーが1つのファイルにあるとチーム全員が把握していれば、誤って重複させてしまうことはほぼなくなります。
CI パイプラインに検証ステップを追加して、このエラーが誰かのローカル環境に届く前に検出できるようにしましょう:
# .github/workflows/terraform.yml (抜粋)
- name: Validate Terraform
run: |
terraform init -backend=false
terraform validate
YAML と JSON 形式の変数ファイルが混在する設定を扱う場合 — 大規模な Terraform プロジェクトではよくあることですが — Terraform に渡す前に .tfvars や変数定義ファイルの構造をすばやく確認するのに、ToolCraft の YAML ↔ JSON Converter が便利です。完全にブラウザ上で動作し、何もアップロードされません。

