
Un state local suffit tant que vous travaillez seul. Dès qu’une deuxième personne lance terraform apply depuis son poste, vous n’avez plus une vérité unique : chacun possède son propre fichier terraform.tfstate, les plans divergent et les risques d’écrasement augmentent. Un backend S3 existe précisément pour résoudre ce problème en centralisant le state dans AWS, tandis que terraform_remote_state permet à une autre configuration de lire les outputs publiés par cette première stack.
Dans ce guide, vous allez mettre en place un workflow simple et réaliste en trois blocs : un projet de bootstrap qui prépare le bucket S3, une configuration producer qui stocke son state dans ce backend distant, puis une configuration consumer qui lit ses outputs. Vous comprendrez surtout pourquoi ces trois blocs sont séparés : le backend Terraform doit être configuré avec des valeurs déjà connues, il ne peut pas dépendre d’une ressource créée dans la même configuration.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Créer un backend S3 dédié avec versioning, chiffrement et blocage de l’accès public
- Activer le verrouillage natif (
use_lockfile) pour éviter les accès concurrents au state - Initialiser une configuration Terraform directement sur un backend S3
- Partager des outputs entre deux configurations avec
terraform_remote_state - Comprendre l’enchaînement bootstrap → producer → consumer sans copier-coller aveugle
Pourquoi ce workflow existe
Section intitulée « Pourquoi ce workflow existe »Le state Terraform mémorise ce que Terraform a créé. Sans lui, Terraform ne peut pas comparer votre code avec la réalité AWS. En local, ce state reste un simple fichier sur votre machine. C’est pratique pour apprendre, mais insuffisant dès que plusieurs personnes interviennent.
Un backend S3 joue le rôle d’un classeur partagé : tout le monde lit et met à jour le même état central. Le verrouillage natif (use_lockfile) agit comme une pancarte “occupation en cours” posée sur le classeur pendant un apply : Terraform dépose un fichier .tflock dans le bucket S3. Enfin, terraform_remote_state agit comme un lecteur autorisé : il ne modifie rien, il vient seulement consulter les outputs publiés par une autre configuration.
Architecture du guide
Section intitulée « Architecture du guide »Vous allez manipuler trois répertoires distincts :
| Répertoire | Rôle | Pourquoi il existe |
|---|---|---|
bootstrap/ | Crée le bucket S3 | Le backend doit exister avant d’être utilisé |
producer/ | Stocke son state dans S3 et publie des outputs | C’est la configuration principale |
consumer/ | Lit les outputs du producer | Simule une seconde équipe ou un second projet |
Prérequis
Section intitulée « Prérequis »- Terraform >= 1.11 installé
- AWS CLI configuré avec un compte autorisé à créer un bucket S3
- Compréhension de base de
resource,dataetoutput
Préparation
Section intitulée « Préparation »Créez une arborescence claire pour ne pas mélanger les trois configurations :
mkdir -p ~/terraform-aws-state/{bootstrap,producer,consumer}cd ~/terraform-aws-state/bootstrapCette séparation est importante : elle vous aide à visualiser qu’un backend Terraform n’est pas “magique”. C’est une infrastructure comme une autre, avec son propre cycle de vie.
Étape 1 — Créer l’infrastructure du backend
Section intitulée « Étape 1 — Créer l’infrastructure du backend »Commencez par la configuration bootstrap. Son seul rôle est de créer le support du state distant : un bucket S3 pour stocker le state. Le verrouillage est géré nativement par Terraform (use_lockfile = true) — aucune table DynamoDB n’est nécessaire depuis Terraform 1.10.
Créez versions.tf :
terraform { required_version = ">= 1.11.0"
required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } }}
provider "aws" { region = var.aws_region}Créez variables.tf :
variable "aws_region" { description = "AWS region" type = string default = "us-east-1"}
variable "project_slug" { description = "Prefix used to name backend resources" type = string default = "terraform-aws-state-demo"}Créez main.tf :
data "aws_caller_identity" "current" {}
locals { backend_bucket_name = "${var.project_slug}-${data.aws_caller_identity.current.account_id}"}
resource "aws_s3_bucket" "terraform_state" { bucket = local.backend_bucket_name force_destroy = true
tags = { Name = local.backend_bucket_name ManagedBy = "terraform" Purpose = "terraform-backend" }}
resource "aws_s3_bucket_versioning" "terraform_state" { bucket = aws_s3_bucket.terraform_state.id
versioning_configuration { status = "Enabled" }}
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" { bucket = aws_s3_bucket.terraform_state.id
rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" } }}
resource "aws_s3_bucket_public_access_block" "terraform_state" { bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true block_public_policy = true ignore_public_acls = true restrict_public_buckets = true}Créez outputs.tf :
output "backend_bucket_name" { description = "S3 bucket name used by the Terraform backend" value = aws_s3_bucket.terraform_state.bucket}
output "aws_region" { description = "AWS region used by the backend" value = var.aws_region}Créez terraform.tfvars :
aws_region = "us-east-1"project_slug = "terraform-aws-state-demo"Pourquoi ces ressources sont séparées
Section intitulée « Pourquoi ces ressources sont séparées »Le bloc backend "s3" de Terraform est évalué avant que Terraform crée des ressources. C’est la raison pour laquelle vous ne pouvez pas écrire : “crée le bucket puis utilise-le comme backend” dans une seule configuration. Le bootstrap prépare l’infrastructure d’abord, puis les autres projets l’utilisent.
Vérification du bootstrap
Section intitulée « Vérification du bootstrap »-
Initialisez et validez la configuration :
Fenêtre de terminal terraform initterraform validateterraform plan -
Appliquez la configuration :
Fenêtre de terminal terraform apply -auto-approve -
Notez les outputs affichés à la fin. Vous en aurez besoin dans les étapes suivantes :
backend_bucket_name = "terraform-aws-state-demo-123456789012"aws_region = "us-east-1"
Étape 2 — Configurer le producer sur le backend S3
Section intitulée « Étape 2 — Configurer le producer sur le backend S3 »Passez dans le répertoire producer/. Cette configuration va écrire son state directement dans S3 et publier quelques outputs que le consumer pourra relire.
cd ~/terraform-aws-state/producerCréez versions.tf :
terraform { required_version = ">= 1.11.0"
backend "s3" { use_lockfile = true }
required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } }}
provider "aws" { region = var.aws_region}Le bloc backend "s3" contient uniquement use_lockfile = true pour activer le verrouillage natif. Les paramètres d’accès (bucket, clé, région) sont fournis au moment du terraform init via -backend-config, ce qui évite de figer des noms de bucket dans le dépôt.
Créez variables.tf :
variable "aws_region" { description = "AWS region" type = string default = "us-east-1"}
variable "application_name" { description = "Logical application name published as an output" type = string default = "payments-api"}
variable "environment" { description = "Environment published as an output" type = string default = "lab"}Créez main.tf :
data "aws_caller_identity" "current" {}
locals { naming_prefix = "${var.application_name}-${var.environment}-${data.aws_caller_identity.current.account_id}"}Créez outputs.tf :
output "application_name" { description = "Application name shared with downstream stacks" value = var.application_name}
output "environment" { description = "Environment shared with downstream stacks" value = var.environment}
output "naming_prefix" { description = "Computed prefix built by the producer" value = local.naming_prefix}
output "account_id" { description = "AWS account ID seen by the producer" value = data.aws_caller_identity.current.account_id}Créez terraform.tfvars :
aws_region = "us-east-1"application_name = "payments-api"environment = "lab"Initialiser le backend S3
Section intitulée « Initialiser le backend S3 »Remplacez les valeurs ci-dessous par les outputs du bootstrap :
terraform init \ -backend-config="bucket=terraform-aws-state-demo-123456789012" \ -backend-config="key=producer/terraform.tfstate" \ -backend-config="region=us-east-1" \ -backend-config="encrypt=true"Puis validez et appliquez :
terraform validateterraform apply -auto-approveComment savoir que le state est bien distant
Section intitulée « Comment savoir que le state est bien distant »Si tout est correct, Terraform doit mentionner le backend S3 dès l’initialisation. Ensuite, la commande suivante doit lister un fichier de state dans votre bucket :
aws s3 ls s3://terraform-aws-state-demo-123456789012/producer/Vous devez y voir terraform.tfstate. C’est la preuve que le state n’est plus seulement local.
Étape 3 — Lire les outputs avec terraform_remote_state
Section intitulée « Étape 3 — Lire les outputs avec terraform_remote_state »Le répertoire consumer/ simule une seconde équipe ou une seconde stack Terraform. Son objectif n’est pas de recréer les mêmes données, mais de consommer les outputs déjà publiés par le producer.
cd ~/terraform-aws-state/consumerCréez versions.tf :
terraform { required_version = ">= 1.11.0"}Le consumer n’utilise ni ressource ni data source AWS : la data source terraform_remote_state est fournie par un provider intégré (terraform.io/builtin/terraform). Aucun provider externe n’est nécessaire ici.
Créez variables.tf :
variable "aws_region" { description = "AWS region used to locate the S3 backend" type = string default = "us-east-1"}
variable "backend_bucket_name" { description = "S3 bucket where the producer state is stored" type = string}
variable "producer_state_key" { description = "Object key of the producer state in S3" type = string default = "producer/terraform.tfstate"}Créez main.tf :
data "terraform_remote_state" "producer" { backend = "s3"
config = { bucket = var.backend_bucket_name key = var.producer_state_key region = var.aws_region }}Cette data source ne crée aucune ressource. Elle agit comme un lecteur de state : Terraform ouvre le fichier stocké dans S3 et expose ses outputs sous la forme data.terraform_remote_state.producer.outputs.<nom>.
Créez outputs.tf :
output "producer_application_name" { description = "Application name published by the producer" value = data.terraform_remote_state.producer.outputs.application_name}
output "producer_environment" { description = "Environment published by the producer" value = data.terraform_remote_state.producer.outputs.environment}
output "producer_naming_prefix" { description = "Naming prefix computed by the producer" value = data.terraform_remote_state.producer.outputs.naming_prefix}
output "producer_account_id" { description = "AWS account ID seen by the producer" value = data.terraform_remote_state.producer.outputs.account_id}Créez terraform.tfvars :
aws_region = "us-east-1"backend_bucket_name = "terraform-aws-state-demo-123456789012"producer_state_key = "producer/terraform.tfstate"Vérification du consumer
Section intitulée « Vérification du consumer »terraform initterraform validateterraform apply -auto-approveSortie attendue :
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:producer_account_id = "123456789012"producer_application_name = "payments-api"producer_environment = "lab"producer_naming_prefix = "payments-api-lab-123456789012"Le point important est le suivant : le consumer ne sait pas comment le producer calcule naming_prefix. Il consomme seulement une valeur déjà publiée. C’est précisément ce qui rend terraform_remote_state utile pour relier deux stacks sans dupliquer leur logique interne.
Anatomie
Section intitulée « Anatomie »Backend S3 vs terraform_remote_state
Section intitulée « Backend S3 vs terraform_remote_state »| Élément | Rôle | Exemple |
|---|---|---|
| Backend S3 | Emplacement où Terraform stocke son propre state | backend "s3" {} |
terraform_remote_state | Lecture d’un state déjà existant | data "terraform_remote_state" "producer" |
La confusion la plus fréquente vient de là : un backend sert à écrire et maintenir votre state courant ; terraform_remote_state sert à lire les outputs d’un autre state.
Pourquoi le verrouillage compte
Section intitulée « Pourquoi le verrouillage compte »Sans verrouillage, deux personnes peuvent lancer terraform apply au même moment et écrire dans le même state. Le résultat ressemble à deux personnes qui modifient le même document partagé sans coordination. Avec use_lockfile = true, Terraform dépose un fichier .tflock dans le bucket S3 via une écriture conditionnelle : tant que ce fichier existe, un seul opérateur peut modifier le state.
Bonnes pratiques
Section intitulée « Bonnes pratiques »1. Dédier un backend par projet ou domaine
Section intitulée « 1. Dédier un backend par projet ou domaine »network/terraform.tfstatesecurity/terraform.tfstateapplications/payments-api/terraform.tfstateNe mettez pas tous les projets dans la même clé S3. Un chemin clair vous aide à savoir qui possède quoi.
2. Chiffrer et versionner le bucket
Section intitulée « 2. Chiffrer et versionner le bucket »Le chiffrement protège les données stockées. Le versioning permet de revenir en arrière si un state a été écrasé ou corrompu par erreur.
3. Publier seulement des outputs utiles
Section intitulée « 3. Publier seulement des outputs utiles »Un output doit exposer une donnée utile à une autre stack : un nom, un identifiant, une URL, un préfixe de nommage. Ne publiez pas tout “au cas où”.
4. Attention à l’accès au state complet via terraform_remote_state
Section intitulée « 4. Attention à l’accès au state complet via terraform_remote_state »terraform_remote_state n’expose que les outputs du module racine, mais son lecteur doit avoir accès au snapshot complet du state, qui peut contenir des données sensibles (mots de passe, clés). En production, préférez publier les données à partager via un mécanisme dédié comme SSM Parameter Store, AWS Secrets Manager ou une autre source de vérité adaptée à votre organisation.
Dépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Solution |
|---|---|---|
Error: Failed to get existing workspaces | Bucket, clé ou région incorrects | Vérifier les -backend-config du producer |
Error acquiring the state lock | Un autre apply détient déjà le verrou (fichier .tflock) | Attendre la fin de l’opération en cours, ou terraform force-unlock si orphelin |
No stored state was found for the given workspace | Le producer n’a pas encore écrit son state dans S3 | Exécuter terraform apply dans producer/ |
Unsupported attribute sur terraform_remote_state | L’output demandé n’existe pas côté producer | Vérifier le nom exact dans outputs.tf du producer |
BucketAlreadyExists | Le nom de bucket choisi n’est pas unique globalement | Changer project_slug puis recréer le bootstrap |
Nettoyage
Section intitulée « Nettoyage »Commencez toujours par les consommateurs, puis remontez vers le backend :
# 1. Détruire le consumercd ~/terraform-aws-state/consumerterraform destroy -auto-approverm -rf .terraform terraform.tfstate terraform.tfstate.backup
# 2. Détruire le producercd ~/terraform-aws-state/producerterraform destroy -auto-approverm -rf .terraform terraform.tfstate terraform.tfstate.backup
# 3. Détruire le bootstrap en derniercd ~/terraform-aws-state/bootstrapterraform destroy -auto-approverm -rf .terraform terraform.tfstate terraform.tfstate.backupLe backend doit disparaître en dernier, sinon les autres configurations ne pourront plus lire ou mettre à jour leur state.
À retenir
Section intitulée « À retenir »- Le backend S3 stocke votre state courant dans AWS ; ce n’est pas la même chose que
terraform_remote_state. - Le bootstrap existe parce qu’un backend doit être déjà créé avant d’être utilisé.
use_lockfile = trueactive le verrouillage natif S3 — plus besoin de table DynamoDB depuis Terraform 1.10.terraform_remote_statesert à relire des outputs publiés par une autre stack, pas à partager toute sa logique interne.- Ne réutilisez jamais un backend de production pour un exercice ou un lab.
- Le nettoyage se fait dans l’ordre inverse : consumer, producer, puis backend.