Terraform for_each vs count vs for
À la fin de ce tutoriel, vous saurez pourquoi boucler des ressources similaires, utiliser
count(index et pièges de réordonnancement), préférerfor_eachsur map/set à clés stables, distinguer l’expressionfor(locals / outputs) du meta-argument, migrer count → for_each sans tout détruire (moved), et valider un lab léger en Regionca-central-1avec destroy systématique.Niveau : Débutant → Intermédiaire · Temps estimé : 40–50 min · Versions testées : Terraform 1.16.2 (série ≥ 1.9), AWS provider ~> 5.0 · Dernière vérification : 2026-09-10
Slug proposé :
terraform-boucles-for-each· Série : Terraform · Remplace / fusionne : #1650 (P6 bonus #1 : count vs for_each vs for)
Prérequis
- Avoir suivi Data sources (
terraform-data-sources) : lire l’existant, timing plan - Avoir suivi Variables (
terraform-variables) et Locals (terraform-locals) : maps, sets, valeurs dérivées - Avoir suivi Providers et ressources (
terraform-providers-ressources) : adresseTYPE.NAME - Terraform ≥ 1.9, AWS CLI v2, profil de lab (
aws sts get-caller-identityOK)
Coût estimé : quasi 0 € si vous créez seulement des buckets S3 vides tagués (quelques octets) ou des utilisateurs IAM de lab sans clés d’accès. Destroy obligatoire en fin de lab. Aucun secret dans le HCL ni les tfvars commités.
Auth sans secrets. Utilisez
AWS_PROFILE/ SSO. Jamais d’access_key/secret_keydans le provider. Pour ce lab, une politique limitée (S3 + IAM users de test) suffit.
Ce que nous allons construire
Lab boucles Terraform (ca-central-1)
├── versions.tf / providers.tf → TF ≥ 1.9, aws ~> 5.0, region ca-central-1
├── variables.tf → map de buckets (ou users IAM) + tags
├── main.tf → aws_s3_bucket / tags via for_each
├── outputs.tf → map id → bucket (expression for)
├── (option) moved.tf → aperçu migration count → for_each
└── init → plan → apply → destroy
(Alt : « Terraform for_each vs count vs for : boucler des ressources AWS sans réordonnancement destructeur ».)
Ce guide remplace le post WordPress #1650 (« Terraform Loops ») et aligne le parcours P2 sur le sujet count vs for_each vs for (bonus P6 #1 du plan), actualisé 2026 (TF ≥ 1.9, provider ~> 5.0, Region ca-central-1).
Étape 1 — Pourquoi boucler (N ressources similaires)
En IaC, vous créez souvent plusieurs objets du même type : buckets de logs par environnement, utilisateurs IAM d’équipe, sous-réseaux par AZ. Dupliquer le bloc resource N fois est fragile (copier-coller, dérive de tags, oubli d’un output).
Terraform propose trois outils proches mais distincts :
| Outil | Rôle | Où |
|---|---|---|
Meta-argument count |
Créer N instances indexées 0…N-1 |
Sur resource / module |
Meta-argument for_each |
Créer une instance par clé (map ou set) | Sur resource / module |
Expression for |
Transformer listes / maps en mémoire | Dans locals, output, arguments |
Objectif : une déclaration, N objets dans le state — avec des adresses stables pour éviter les destroy/create inutiles.
Étape 2 — count : index et limites (réordonnancement destructeur)
count prend un entier. Chaque instance s’adresse TYPE.NAME[i] :
variable "bucket_suffixes" {
type = list(string)
default = ["alpha", "beta", "gamma"]
}
resource "aws_s3_bucket" "lab_count" {
count = length(var.bucket_suffixes)
bucket = "lab-tf-count-${var.bucket_suffixes[count.index]}-${data.aws_caller_identity.current.account_id}"
tags = {
Name = var.bucket_suffixes[count.index]
Index = tostring(count.index)
}
}
Avantage : simple pour « exactement N copies identiques ». Limite critique : l’identité repose sur l’index. Si vous retirez "beta" au milieu de la liste, Terraform voit :
[1](ex-beta) → devient gamma → remplacement[2]→ disparaît → destroy
Le cloud peut donc détruire et recréer des buckets (ou instances) pourtant « encore voulus », uniquement parce que les indices ont glissé. count reste utile pour count = var.enable ? 1 : 0 (présence/absence) — sujet du tuto Conditionnels.
Étape 3 — for_each : map/set et clés stables (préféré)
for_each itère une map ou un set. L’adresse devient TYPE.NAME["clé"] : retirer une clé n’affecte pas les autres.
variable "lab_buckets" {
type = map(object({
purpose = string
}))
description = "Buckets de lab indexés par clé stable."
default = {
alpha = { purpose = "logs" }
beta = { purpose = "artifacts" }
gamma = { purpose = "backup" }
}
}
data "aws_caller_identity" "current" {}
resource "aws_s3_bucket" "lab" {
for_each = var.lab_buckets
# Préfixe unique au compte — évite les collisions de nom S3 global
bucket = "lab-tf-fe-${each.key}-${data.aws_caller_identity.current.account_id}"
tags = {
Name = each.key
Purpose = each.value.purpose
ManagedBy = "terraform"
Lab = "for-each"
}
}
Dans le bloc : each.key (clé) et each.value (valeur). Avec un toset(["a","b"]), each.value vaut aussi la clé. Préférez une map dès que vous avez des attributs par élément (tags, taille, rôle).
Règle pratique P2 : for_each par défaut pour N objets nommés ; count pour le toggle 0/1 ou les copies strictement anonymes.
Étape 4 — Expression for ≠ meta-argument
L’expression for ne crée pas de ressources. Elle construit listes ou maps dans locals / outputs / arguments :
locals {
bucket_arns = {
for k, b in aws_s3_bucket.lab : k => b.arn
}
}
output "bucket_ids" {
description = "Map clé → id des buckets for_each."
value = {
for k, b in aws_s3_bucket.lab : k => b.id
}
}
output "bucket_names_list" {
description = "Liste des noms (expression for → list)."
value = [for b in aws_s3_bucket.lab : b.bucket]
}
Syntaxe typique : { for k, v in map : k => v.attr } ou [for x in list : x.name if x.enabled]. Confusion fréquente : écrire for_each = [for …] correctement, mais croire que for remplace for_each sur le resource — non : sans meta-argument, une seule ressource.
Étape 5 — Migrer count → for_each sans tout détruire
Passer de aws_s3_bucket.lab[0] à aws_s3_bucket.lab["alpha"] sans moved = Terraform prévoit destroy + create. Depuis Terraform 1.1+, les blocs moved réécrivent l’adresse dans le state sans toucher au cloud :
# Après avoir remplacé count par for_each sur les mêmes objets AWS
moved {
from = aws_s3_bucket.lab_count[0]
to = aws_s3_bucket.lab["alpha"]
}
moved {
from = aws_s3_bucket.lab_count[1]
to = aws_s3_bucket.lab["beta"]
}
moved {
from = aws_s3_bucket.lab_count[2]
to = aws_s3_bucket.lab["gamma"]
}
Workflow : (1) inventaire terraform state list, (2) bascule HCL vers for_each + clés stables, (3) un moved par instance, (4) terraform plan → 0 destroy attendu sur ces objets, (5) apply, puis retirez les moved au commit suivant si vous le souhaitez (ils restent inoffensifs). Alternative historique : terraform state mv — moins traçable en Git.
Étape 6 — Lab ca-central-1 (buckets via for_each + destroy)
Lab léger : trois buckets S3 tagués (ou, variante, des aws_iam_user sans aws_iam_access_key). Region ca-central-1.
# versions.tf
terraform {
required_version = ">= 1.9.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# providers.tf
provider "aws" {
region = "ca-central-1"
# Auth : AWS_PROFILE / SSO — jamais de clés ici
}
# variables.tf — voir map lab_buckets (étape 3)
# main.tf — data + resource for_each (étape 3)
# outputs.tf — expressions for (étape 4)
Commandes :
mkdir -p ~/terraform-foreach-lab && cd ~/terraform-foreach-lab
# collez versions.tf, providers.tf, variables.tf, main.tf, outputs.tf
export AWS_PROFILE=lab
export AWS_REGION=ca-central-1
terraform init
terraform fmt
terraform validate
terraform plan
terraform apply -auto-approve
terraform output
terraform destroy -auto-approve
Vérifiez dans la console S3 (ou aws s3api list-buckets) les noms lab-tf-fe-alpha-…, etc., puis destroy. Variante IAM : resource "aws_iam_user" "lab" { for_each = toset(["alice-lab","bob-lab"]) ; name = each.key } — jamais de clés secrètes dans le code.
Étape 7 — Checklist
- Boucler = une déclaration, N objets — éviter le copier-coller.
count→ index ; retirer un élément au milieu = risque de réordonnancement destructeur.for_each→ map/set ; clés stables = préféré pour N ressources nommées.- Expression
for= transformation ; pas un meta-argument de création. - Migration : blocs
moved(oustate mv) avant le plan. - Lab
ca-central-1+ destroy systématique. - Aucun secret ; profil SSO.
Nettoyage
cd ~/terraform-foreach-lab
terraform destroy -auto-approve
rm -rf ~/terraform-foreach-lab
Confirmez l’absence des buckets / users de lab dans le compte.
Erreurs fréquentes
| Symptôme | Cause | Correction |
|---|---|---|
for_each refuses list / unknown until apply |
Liste ordinaire ou valeur inconnue au plan | toset(...), map littérale / variable typée ; éviter les IDs cloud encore inconnus comme clés |
| Destroy/create en cascade après edit de liste | count + suppression au milieu |
Passer à for_each + clés ; moved si state déjà indexé |
each.key / each.value hors bloc |
Référence en dehors de la ressource for_each |
Utiliser aws_s3_bucket.lab["alpha"].id ou une expression for |
| Noms S3 déjà pris | Namespace global + collision | Inclure account_id (data) dans le nom |
Confusion for / for_each |
Attendre N resources d’un seul for |
Ajouter le meta-argument for_each sur le resource |
Quiz (3 questions)
1. Pourquoi for_each est-il en général préférable à count pour N buckets nommés ?
- A. Il est plus rapide à appliquer
- B. Les clés stables évitent le réordonnancement destructeur
- C.
countne fonctionne pas avec le provider AWS 5.x
2. Que fait l’expression for dans un output ?
- A. Elle crée autant de ressources que d’éléments
- B. Elle transforme une collection en liste ou map sans meta-argument de ressource
- C. Elle remplace obligatoirement
moved
3. Comment migrer resource.x[0] vers resource.x["alpha"] sans détruire l’objet cloud ?
- A. Supprimer le state puis re-apply
- B. Déclarer un bloc
moved { from = … to = … }puis plan/apply - C. Changer seulement le nom local du resource
Réponses : 1‑B · 2‑B · 3‑B
Pourquoi / quand boucler avec for_each
Dès que vous créez N ressources similaires (buckets, sous-réseaux, files d’attente), dupliquer des blocs resource est une dette. for_each sur une map ou un set donne des adresses stables (aws_s3_bucket.this["logs"]) : ajouter/retirer une clé ne réordonne pas tout, contrairement à count mal maîtrisé.
count vs for_each — guide de choix
count: prototypage rapide, N identique, index numérique acceptable. Risque : supprimer l’index 0 décale tout et peut forcer des replaces.for_each: préféré en production pédagogique et réelle dès que chaque instance a une identité (nom, AZ, rôle).- Expression
for: transforme des collections dans une expression ; ce n’est pas le meta-argumentfor_each.
Pièges des boucles Terraform
- Convertir une liste en
for_eachsanstoset()/ map : Terraform exige une map ou un set de strings. - Clés dépendantes d’une valeur connue seulement après apply (unknown at plan) : cas avancé, à éviter en lab.
- Migrer
count→for_eachsansmoved/state mv: plan plein de destroys. - Nommer les buckets avec un préfixe non unique globalement : choc S3 au apply.
- Laisser tourner N buckets après le lab : destroy systématique en Region
ca-central-1, TF ≥ 1.9, sans secrets dans le HCL.
Quand ne pas boucler
Une seule resource suffit. Ou la variation est mieux modelée par un module appelé plusieurs fois avec des inputs clairs (lisibilité pour les débutants du module).
FAQ for_each & count
Comment inspecter une instance ? terraform state show 'aws_s3_bucket.this["logs"]' (guillemets selon le shell).
Puis-je combiner count et for_each sur la même resource ? Non : un seul de ces meta-arguments par resource.
for_each sur un module ? Oui (Terraform moderne) : très puissant ; voyez Modules bases après ce tuto.
Pourquoi des clés stables ? Le state indexe par clé : une clé stable = lifecycle stable quand la collection change.
Les tags doivent-ils inclure la clé ? Bonne pratique : Name = each.key ou tag Item = each.key pour retrouver la resource dans la console.
Pour aller plus loin
- Doc : for_each, count, for expressions, moved
- Sur ce site : Data sources, Conditionnels ; aussi Dynamic blocks, Modules
- Suite : activer / désactiver une ressource avec
countconditionnel et expressions ternaires
Maillage série Terraform (P2)
| ← Précédent | Data sources (terraform-data-sources) |
| → Suivant | Conditionnels (terraform-conditionnels) |
| Aussi | Dynamic blocks (terraform-dynamic-blocks) · Modules (terraform-modules-bases) |
| Carte | Overview · Install · Workflow · Blocks · Providers · Variables · Locals · Outputs · State · Data sources · Loops · Conditionals · Dynamic blocks · Modules ×2 · Workspaces · Provisioners · EBS · ELB/ALB · IAM · RDS · VPC · Auto Scaling · Route 53 |
Cas réel — count vs for_each après une suppression au milieu
Vous aviez count = 3 subnets. On retire celui du milieu : les index glissent, Terraform remplace les suivants. Avec for_each sur des AZ nommées, seule la clé supprimée disparaît. C’est la raison pour laquelle ce cours insiste sur for_each dès qu’il y a une identité métier.
Piège : une map dont les clés sont des timestamps ou des UUID régénérés à chaque plan. Le for_each croit à un destroy/create massif. Clés stables (nom d’env, AZ, nom de bucket). En lecture, each.key / each.value doivent apparaître dans les tags pour debugger.
À retenir : la clé est plus importante que la valeur. Une mauvaise clé coûte un replace ; une mauvaise valeur se corrige souvent in-place.
Retour parcours Terraform — hub de la série et leçons sœurs.



