Aller au contenu
Infrastructure as Code medium
🔐 Alerte sécurité — Incident supply chain Trivy : lire mon analyse de l'attaque

Terraform AWS — Backend S3 et terraform_remote_state

18 min de lecture

logo terraform

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 et la table de verrouillage, 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.

  • Créer un backend S3 dédié avec versioning, chiffrement et blocage de l’accès public
  • Ajouter un verrouillage avec DynamoDB 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

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. La table DynamoDB sert de verrou, comme une pancarte “occupation en cours” posée sur le classeur pendant un apply. 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.

Vous allez manipuler trois répertoires distincts :

RépertoireRôlePourquoi il existe
bootstrap/Crée le bucket S3 et la table DynamoDBLe backend doit exister avant d’être utilisé
producer/Stocke son state dans S3 et publie des outputsC’est la configuration principale
consumer/Lit les outputs du producerSimule une seconde équipe ou un second projet
  • Terraform >= 1.11 installé
  • AWS CLI configuré avec un compte autorisé à créer un bucket S3 et une table DynamoDB
  • Compréhension de base de resource, data et output

Créez une arborescence claire pour ne pas mélanger les trois configurations :

Fenêtre de terminal
mkdir -p ~/terraform-aws-state/{bootstrap,producer,consumer}
cd ~/terraform-aws-state/bootstrap

Cette 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.

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 et une table DynamoDB pour empêcher deux apply simultanés.

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}"
lock_table_name = "${var.project_slug}-locks"
}
resource "aws_s3_bucket" "terraform_state" {
bucket = local.backend_bucket_name
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
}
resource "aws_dynamodb_table" "terraform_locks" {
name = local.lock_table_name
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
tags = {
Name = local.lock_table_name
ManagedBy = "terraform"
Purpose = "terraform-locks"
}
}

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 "lock_table_name" {
description = "DynamoDB table used to lock Terraform state"
value = aws_dynamodb_table.terraform_locks.name
}
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"

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.

  1. Initialisez et validez la configuration :

    Fenêtre de terminal
    terraform init
    terraform validate
    terraform plan
  2. Appliquez la configuration :

    Fenêtre de terminal
    terraform apply -auto-approve
  3. Notez les outputs affichés à la fin. Vous en aurez besoin dans les étapes suivantes :

    backend_bucket_name = "terraform-aws-state-demo-123456789012"
    lock_table_name = "terraform-aws-state-demo-locks"
    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.

Fenêtre de terminal
cd ~/terraform-aws-state/producer

Créez versions.tf :

terraform {
required_version = ">= 1.11.0"
backend "s3" {}
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}

Ce bloc backend "s3" {} reste volontairement vide dans le code. Vous fournirez les valeurs réelles au moment du terraform init. Cette approche évite de figer des noms de bucket dans le dépôt et vous permet de réutiliser le même exemple dans plusieurs comptes AWS.

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"

Remplacez les valeurs ci-dessous par les outputs du bootstrap :

Fenêtre de terminal
terraform init \
-backend-config="bucket=terraform-aws-state-demo-123456789012" \
-backend-config="key=producer/terraform.tfstate" \
-backend-config="region=us-east-1" \
-backend-config="dynamodb_table=terraform-aws-state-demo-locks" \
-backend-config="encrypt=true"

Puis validez et appliquez :

Fenêtre de terminal
terraform validate
terraform apply -auto-approve

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 :

Fenêtre de terminal
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.

Fenêtre de terminal
cd ~/terraform-aws-state/consumer

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 "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"
Fenêtre de terminal
terraform init
terraform validate
terraform apply -auto-approve

Sortie 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.

ÉlémentRôleExemple
Backend S3Emplacement où Terraform stocke son propre statebackend "s3" {}
terraform_remote_stateLecture d’un state déjà existantdata "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.

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. La table DynamoDB évite ce scénario en autorisant un seul écrivain à la fois.

network/terraform.tfstate
security/terraform.tfstate
applications/payments-api/terraform.tfstate

Ne mettez pas tous les projets dans la même clé S3. Un chemin clair vous aide à savoir qui possède quoi.

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.

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. Ne partagez pas les secrets via terraform_remote_state

Section intitulée « 4. Ne partagez pas les secrets via terraform_remote_state »

Le state peut contenir des informations sensibles. Si une valeur doit rester secrète, utilisez plutôt un service dédié comme AWS Secrets Manager ou SSM Parameter Store.

SymptômeCause probableSolution
Error: Failed to get existing workspacesBucket, clé ou région incorrectsVérifier les -backend-config du producer
Error acquiring the state lockUn autre apply détient déjà le verrouAttendre la fin de l’opération en cours
No stored state was found for the given workspaceLe producer n’a pas encore écrit son state dans S3Exécuter terraform apply dans producer/
Unsupported attribute sur terraform_remote_stateL’output demandé n’existe pas côté producerVérifier le nom exact dans outputs.tf du producer
BucketAlreadyExistsLe nom de bucket choisi n’est pas unique globalementChanger project_slug puis recréer le bootstrap

Commencez toujours par les consommateurs, puis remontez vers le backend :

Fenêtre de terminal
# 1. Détruire le consumer
cd ~/terraform-aws-state/consumer
terraform destroy -auto-approve
rm -rf .terraform .terraform.lock.hcl terraform.tfstate terraform.tfstate.backup
# 2. Détruire le producer
cd ~/terraform-aws-state/producer
terraform destroy -auto-approve
rm -rf .terraform .terraform.lock.hcl terraform.tfstate terraform.tfstate.backup
# 3. Détruire le bootstrap en dernier
cd ~/terraform-aws-state/bootstrap
terraform destroy -auto-approve
rm -rf .terraform .terraform.lock.hcl terraform.tfstate terraform.tfstate.backup

Le backend doit disparaître en dernier, sinon les autres configurations ne pourront plus lire ou mettre à jour leur state.

  1. Le backend S3 stocke votre state courant dans AWS ; ce n’est pas la même chose que terraform_remote_state.
  2. Le bootstrap existe parce qu’un backend doit être déjà créé avant d’être utilisé.
  3. DynamoDB ajoute un verrou pour éviter deux écritures simultanées sur le même state.
  4. terraform_remote_state sert à relire des outputs publiés par une autre stack, pas à partager toute sa logique interne.
  5. Ne réutilisez jamais un backend de production pour un exercice ou un lab.
  6. Le nettoyage se fait dans l’ordre inverse : consumer, producer, puis backend.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn