エラーの内容
terraform plan または terraform apply を実行すると、すぐにこのエラーが発生します:
│ Error: Invalid combination of "count" and "for_each"
│
│ on main.tf line 3, in resource "aws_instance" "web":
│ 3: resource "aws_instance" "web" {
│
│ The "count" and "for_each" meta-arguments are mutually exclusive.
リソースブロック(またはモジュールブロック)に count と for_each が同時に定義されています。Terraformはどちらのイテレーション戦略を使うか判断できないため、その場で処理を停止します。
なぜこのエラーが起きるのか
count と for_each はどちらもリソースの複数インスタンスを作成するよう Terraform に指示しますが、動作の仕組みが根本的に異なります:
count— N個のコピーを作成し、インデックスで参照する(resource[0]、resource[1]、…)for_each— マップのキーまたはセットの要素ごとに1つのコピーを作成し、キーで参照する(resource["web"]、resource["db"]、…)
同じブロックに両方を指定するのは矛盾しています。シェフに「ステーキをミディアムレアかつウェルダンで焼いて」と頼むようなもので、どちらか一方しか選べません。
このエラーが発生しやすい3つのケース:
countベースのコードをfor_eachに移行するとき、元のcount行を削除し忘れるcountを使っている別プロジェクトからリソースブロックをコピーして、クリーンアップせずにfor_eachを追加してしまう- 異なるソースから両方の引数を受け取るモジュール呼び出し
修正手順
ステップ1:問題のあるブロックを見つける
Terraformのエラーには正確なファイルと行番号が含まれています。そのファイルを開き、両方の引数が並んでいるブロックを探します:
resource "aws_instance" "web" {
count = 3 # ← どちらか一方を削除する
for_each = toset(["a", "b", "c"]) # ← どちらか一方だけ残す
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
}
ステップ2:適切なメタ引数を選択する
状況に合った方を選びます。
**count を使うケース:**N個の同一(またはほぼ同一)のコピーが必要で、キーが重要でない場合:
resource "aws_instance" "web" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "web-${count.index}"
}
}
**for_each を使うケース:**各インスタンスが web、api、db のように個別の役割やアイデンティティを持つ場合、または後から名前で参照する必要がある場合:
locals {
instances = {
web = "t3.micro"
api = "t3.small"
db = "t3.medium"
}
}
resource "aws_instance" "servers" {
for_each = local.instances
ami = "ami-0c55b159cbfafe1f0"
instance_type = each.value
tags = {
Name = each.key
}
}
特定のインスタンスを後から参照する際もシンプルで読みやすいです:
output "web_ip" {
value = aws_instance.servers["web"].public_ip
}
ステップ3:不要なメタ引数を削除する
使わない方を削除します。ブロック全体を注意深くスキャンしてください — 末尾に隠れている余分な count = ... や for_each = ... があるだけでエラーが発生します。各リソースにはどちらか一方のみ(単一インスタンスだけ必要な場合はどちらも不要)を指定してください。
モジュールブロックも同じルールに従う
モジュール呼び出しも同様の問題が発生します:
# 壊れている状態
module "vpc" {
source = "./modules/vpc"
count = 2
for_each = toset(["us-east-1", "eu-west-1"])
}
# 修正済み — どちらか一方を選ぶ:
module "vpc" {
source = "./modules/vpc"
for_each = toset(["us-east-1", "eu-west-1"])
region = each.value
}
修正の確認
まず terraform validate を実行します — フルプランより高速で、構文の問題をすぐに検出できます:
terraform validate
次のように表示されれば成功です:
Success! The configuration is valid.
続いてプランが正しいことを確認します:
terraform plan
注意点として、既存のリソースを count から for_each に切り替える場合、Terraformはすべてのインスタンスを破棄して再作成するプランを立てます。ステートキーの形式が resource[0] から resource["key"] に変わり、Terraformはそれらを別々のオブジェクトとして扱うためです。再作成を避けたい場合は、terraform state mv を使って手動でステートを移行してください。
クイックヒント
- 実際のインフラではほとんどの場合
for_eachを優先してください。countベースのリストの途中からアイテムを削除すると、Terraformはそれ以降のすべてのインデックスをシフトさせ、触れていないリソースまで破棄・再作成する可能性があります。for_eachでは削除したキーのみが消えます。 - 空のマップやセットは問題ありません —
for_each = {}はエラーなしでリソースをゼロ個作成します。 - プレーンなリストを
for_eachに渡すことはできません —toset()でラップしてください:for_each = toset(var.names)。リストは重複を許可しますが、for_eachは許可しません。 - ダイナミックブロックは別の話です — すでに
countを使っているリソース内のdynamicブロックでfor_eachを使うことは完全に有効です。それはTerraformが問題にしている競合ではありません。

