State Terraform distant : S3 + verrouillage
À la fin : pourquoi le state local échoue en équipe/CI ; backend
s3(ca-central-1, key,encrypt) ;use_lockfile(TF ≥ 1.10) vs DynamoDB legacy ; bootstrap bucket ;init -migrate-state; naming keys, IAM least-privilege, flags CI, recovery versioning — sans casser la prod.Niveau : Débutant → Intermédiaire · Temps : 45–60 min · Versions : Terraform 1.16.2 (≥ 1.9 ; lock ≥ 1.10), AWS provider ~> 5.0 · Vérifié : 2026-09-10
Slug live / canonical :
terraform-state-file-on-s3(WP #1630) · Alias draft :terraform-state-backend-s3· Série : Terraform
Prérequis
- Workflow :
init→plan→apply→destroy - Providers et Variables / outputs
- Terraform ≥ 1.9 (idéalement ≥ 1.10 pour
use_lockfile), AWS CLI v2, profil lab (aws sts get-caller-identityOK)
Coût : bucket S3 quasi nul en lab + quelques API. Supprimez le bucket de lab en fin. Aucun secret dans HCL, tfvars commités ni logs CI.
Auth sans secrets.
AWS_PROFILE/ SSO uniquement. Jamais d’access_key/secret_keydans provider ou backend. State sensible :encrypt+ IAM restreint.
Ce que nous allons construire
Lab backend S3 (ca-central-1)
├── bootstrap (hors backend) → versioning + encrypt + BPA
├── backend.tf → s3 + use_lockfile = true
├── versions / providers → TF ≥ 1.9 (≥ 1.10 lock), aws ~> 5.0
├── .gitignore → *.tfstate*, .terraform/
└── init -migrate-state → plan → state list/show → nettoyage
(Schéma QA : 09-state-s3.png — alt : « Backend Terraform S3 : state distant, chiffrement et use_lockfile ».)
Enrichit le post live #1630 (/terraform-state-file-on-s3/) : state distant S3 + verrouillage moderne 2026.
Étape 1 — Pourquoi un state distant (équipe, CI, pas de .tfstate en Git)
terraform.tfstate est la source de vérité (IDs, ARNs, attributs). En local, chaque poste/job a sa copie → désync et écrasements.
| Contexte | Risque state local |
|---|---|
| Équipe | Deux apply divergents → orphelins / destructions |
| CI/CD | Runner sans state → Terraform veut tout (re)créer |
| Git | Committer .tfstate = fuites + conflits de merge |
Le backend S3 partage un state. Versionnez le HCL, jamais le state. Les workspaces segmentent ensuite env/stacks.
Étape 2 — Contenu sensible du state
Même avec sensitive = true, le state reste en clair sans chiffrement objet : mots de passe, tokens, chaînes de connexion, ARNs, IPs.
Conséquences : encrypt = true + SSE-S3/KMS ; IAM sur le préfixe key ; BPA ; versioning ; jamais de state pull dans un ticket. sensitive masque la CLI — il ne chiffre pas le state.
Étape 3 — Backend s3 : bucket, key, region, encrypt
Le bloc backend est dans terraform { } (souvent backend.tf). Pas d’interpolation de variables : littéraux, ou partiel + -backend-config.
# backend.tf — moderne (TF ≥ 1.10)
terraform {
required_version = ">= 1.10.0"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
backend "s3" {
bucket = "deh-tfstate-LAB-SUFFIXE-UNIQUE"
key = "labs/terraform-state-backend-s3/terraform.tfstate"
region = "ca-central-1"
encrypt = true
use_lockfile = true
}
}
| Argument | Rôle |
|---|---|
bucket |
Bucket state (nom globalement unique) |
key |
Chemin objet (1 stack/env = 1 key) |
region |
ca-central-1 |
encrypt |
Chiffre state (+ lockfile) à l’upload |
use_lockfile |
Verrouillage natif S3 (étape 4) |
Provider séparé : region = "ca-central-1", aucune clé. Backend via AWS_PROFILE / rôle.
Convention de naming des keys
Hiérarchie stable : <component>/<env>/terraform.tfstate — ex. network/prod, platform-eks/staging, app-billing/dev. Une key par stack limite le blast radius et accélère les plans. Évitez une key monolithique pour tout le compte.
Étape 4 — Verrouillage : use_lockfile vs DynamoDB (legacy)
Sans verrou, deux apply simultanés corrompent le state. Historiquement : DynamoDB (dynamodb_table). Depuis TF 1.10, HashiCorp recommande le lock natif S3 : use_lockfile = true crée un .tflock (conditional writes). Plus de DynamoDB pour les nouveaux projets.
| Approche | Versions | Statut 2026 |
|---|---|---|
use_lockfile = true |
≥ 1.10 (≥ 1.11 idéal) | Moderne / recommandé |
dynamodb_table |
Ancien standard | Legacy — encore vu en prod |
Migration douce : (1) upgrade équipe+CI ≥ 1.10 ; (2) ajoutez use_lockfile = true en gardant dynamodb_table, puis init -reconfigure (double lock) ; (3) après apply OK, retirez dynamodb_table, init -reconfigure, supprimez la table si plus de références. Encore en 1.9 ? Gardez DynamoDB (LockID String, on-demand).
# Legacy — à migrer vers use_lockfile
backend "s3" {
bucket = "deh-tfstate-exemple"
key = "prod/app/terraform.tfstate"
region = "ca-central-1"
encrypt = true
dynamodb_table = "deh-tfstate-locks"
}
Lab : TF ≥ 1.10 et use_lockfile seul.
Étape 5 — Bootstrap du bucket (chicken-egg)
Le backend ne crée pas le bucket. Créez-le hors backend (CLI ou mini projet state local jetable).
export AWS_PROFILE=lab AWS_REGION=ca-central-1
BUCKET="deh-tfstate-lab-$(whoami)-$(date +%Y%m%d)"
aws s3api create-bucket --bucket "$BUCKET" --region ca-central-1
--create-bucket-configuration LocationConstraint=ca-central-1
aws s3api put-public-access-block --bucket "$BUCKET"
--public-access-block-configuration
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
aws s3api put-bucket-versioning --bucket "$BUCKET"
--versioning-configuration Status=Enabled
aws s3api put-bucket-encryption --bucket "$BUCKET"
--server-side-encryption-configuration
'{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'
echo "Bucket : $BUCKET"
Checklist : BPA, versioning, chiffrement défaut, nom unique → backend.tf (sans credentials). Optionnel : lifecycle 90 j sur versions non courantes.
Étape 6 — terraform init -migrate-state
mkdir -p ~/terraform-state-s3-lab && cd ~/terraform-state-s3-lab
# HCL + backend.tf (bucket réel, use_lockfile = true)
export AWS_PROFILE=lab AWS_REGION=ca-central-1
terraform init -migrate-state
Confirmez bucket/key. Projet neuf : init ; sinon -migrate-state ou -reconfigure. Vérifiez S3, puis plan (aucun changement attendu).
Étape 7 — Aperçu terraform state list / show
terraform state list
terraform state show aws_s3_bucket.lab_example
# state pull → JSON sensible : local contrôlé uniquement
| Commande | Usage |
|---|---|
state list |
Inventaire des adresses |
state show ADDR |
Détail d’une ressource |
state pull |
JSON complet — ne pas logger |
mv / rm / import ailleurs ; ici : lire avant de toucher.
Étape 8 — .gitignore tfstate
*.tfstate
*.tfstate.*
.terraform/
crash.log
crash.*.log
override.tf
*_override.tf
*.tfvars
!example.tfvars
Committez le HCL — jamais .tfstate. Secrets via SSO / variables CI.
Étape 9 — IAM least-privilege (state + .tflock)
CI et ingénieurs : ListBucket sur le bucket ; GetObject/PutObject/DeleteObject seulement sur la key state et le .tflock adjacent.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::BUCKET"
},
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
"Resource": [
"arn:aws:s3:::BUCKET/network/prod/terraform.tfstate",
"arn:aws:s3:::BUCKET/network/prod/terraform.tfstate.tflock"
]
}
]
}
Runners via OIDC (rôle court), jamais de clés longues en CI.
Étape 10 — CI/CD : -input=false, -lock-timeout, plan artifact
Pas d’invite clavier ; tolérez une contention de lock brève :
terraform init -input=false -backend-config=env/prod.backend.hcl
terraform plan -input=false -lock-timeout=5m -out=tfplan
terraform apply -input=false -lock-timeout=5m tfplan
Gardez tfplan en artifact : l’apply déploie exactement le plan reviewé. Voir workflow.
Étape 11 — Recovery versioning et partage d’outputs
Rollback : listez les versions S3, téléchargez une version antérieure, puis terraform state push — prudence (écrase le state courant ; aucun apply en cours ; testez en lab).
aws s3api list-object-versions --bucket "$BUCKET" --prefix network/prod/terraform.tfstate
aws s3api get-object --bucket "$BUCKET" --key network/prod/terraform.tfstate
--version-id VERSION_ID previous.tfstate
# terraform state push previous.tfstate # après revue seulement
Partage inter-stacks : terraform_remote_state lit les outputs d’une autre key — pratique, mais expose le state entier. Plus strict : publiez peu de valeurs en SSM et lisez via aws_ssm_parameter (data sources).
Étape 12 — Checklist
- State S3
ca-central-1;encrypt = true. - Lock :
use_lockfile = true(TF ≥ 1.10) ; DynamoDB = legacy. - Bucket bootstrapé avant backend (versioning, BPA, encrypt).
- Key
component/env/terraform.tfstate; IAM = state +.tflock. init -migrate-state→ S3 →plan..gitignore:.tfstate,.terraform/.- CI :
-input=false,-lock-timeout, artifacttfplan; pas de clés HCL. - Lab ≠ bucket prod ; recovery versioning avec prudence.
Nettoyage
Ne détruisez jamais un bucket state prod sans procédure (backup versioning, freeze CI).
cd ~/terraform-state-s3-lab
terraform destroy -auto-approve
aws s3 rm "s3://${BUCKET}" --recursive
aws s3api delete-bucket --bucket "$BUCKET" --region ca-central-1
rm -rf ~/terraform-state-s3-lab
Avec versioning : videz les versions avant delete-bucket. Lock orphelin (.tflock) : confirmez qu’aucun apply n’est en cours.
Erreurs fréquentes
| Symptôme | Cause | Correction |
|---|---|---|
Error acquiring the state lock |
Apply/CI concurrent ou lock orphelin | Attendez ; force-unlock seulement si certitude |
| Accès S3 / workspaces | IAM, region, bucket absent | Profil, ca-central-1, nom bucket |
use_lockfile inconnu |
TF < 1.10 | Upgrade ≥ 1.10 |
| Chicken-egg | Bucket absent | Bootstrap CLI / state local (étape 5) |
| State encore local | Pas de -migrate-state |
init -migrate-state |
| Fuite Git | *.tfstate commités |
.gitignore (+ purge historique) |
Quiz (3 questions)
1. Pourquoi éviter de committer terraform.tfstate ?
- A. Fichier trop volumineux pour Git
- B. Données sensibles + conflits / désync en équipe
- C. Terraform refuse d’appliquer si le state est versionné
2. Quelle option active le verrouillage natif S3 (TF 1.10+) ?
- A.
dynamodb_table = "locks" - B.
use_lockfile = true - C.
locking = "s3"
3. Comment résoudre le chicken-egg du bucket de state ?
- A. Laisser le backend créer le bucket dès le premier init
- B. Créer le bucket hors backend (CLI / bootstrap local), puis
init - C. Désactiver
encryptjusqu’au second apply
Réponses : 1‑B · 2‑B · 3‑B
Pourquoi / quand passer un state distant S3
Le state local convient à un lab solo. Dès que deux personnes ou une CI appliquent le même root, vous avez besoin d’un state partagé, chiffré, versionné, avec verrouillage pour éviter deux apply concurrents. Ce tuto pose le backend s3 en Region ca-central-1, compatible Terraform ≥ 1.9 (use_lockfile moderne vs DynamoDB legacy).
Contenu sensible — rappel opérationnel
Le state peut contenir des IDs, des sorties, parfois des secrets si une resource mal conçue les y a placés. Conséquences :
- Ne jamais committer
.tfstateni*.tfstate.backup. - Chiffrer le bucket (
encrypt = true), bloquer l’accès public, restreindre l’IAM au least privilege. - Éviter de stocker des mots de passe en clair dans des resources ; préférez Secrets Manager / SSM et références.
Pièges backend S3
- Chicken-egg : créer le bucket de backend avant de migrer (bootstrap manuel ou script one-shot).
- Mauvaise
key(chemin d’objet) partagée entre projets : collision de state. - Oublier le lock : deux pipelines
apply= state corrompu possible. - Migrer avec
init -migrate-statesans backup ni versioning bucket. - Laisser des droits
s3:*sur tout le compte « pour que ça marche ».
Quand rester en state local
Ateliers courts, machines jetables, zéro partage. Passez distant dès le premier dépôt d’équipe.
FAQ state & backend
use_lockfile remplace-t-il DynamoDB ? Pour les versions récentes du backend S3, le lockfile natif est la voie recommandée ; DynamoDB reste documenté en legacy selon votre version.
Puis-je lire le state à la main ? terraform state list / show ; évitez d’éditer le JSON brut.
Que faire après un lock orphelin ? Diagnostiquer le process/CI bloqué ; ne forcez le unlock qu’en connaissance de cause.
Versioning S3 utile ? Oui : récupération après un apply catastrophique ou un overwrite.
Outputs et remote state ? terraform_remote_state data source pour partager des sorties entre roots — avec discipline (couplage).
Secrets dans le backend block ? Pas de clés en clair ; credentials via profil/instance role, comme pour le provider AWS.
Pour aller plus loin
- Doc : Backend S3, State, Sensitive Data
- Site : Workflow, Variables / outputs, Data sources, Workspaces, Overview, Providers
- 2026 :
use_lockfilepour nouveaux stacks ; DynamoDB = legacy à migrer
Maillage série Terraform (P2)
| ← Précédent | Variables / Locals / Outputs |
| → Suivant | Data sources |
| Aussi | Workflow · Providers · Workspaces · Overview |
| Carte | Overview · Install · Workflow · Blocks · Providers · Variables · State S3 · Data sources · Loops · Conditionals · Dynamic · Modules · Workspaces · labs AWS |
Cas réel — deux laptops, un state local, un apply qui écrase
Alice applique en local, Bob aussi : le dernier terraform.tfstate gagne, les IDs divergent, le compte AWS a des orphelins. Le backend S3 + lock (natif use_lockfile ou Dynamo selon votre version) existe pour ça. Activez le chiffrement bucket, bloquez le public access, et ne commitez jamais *.tfstate.
Piège : migrer le backend au milieu d’un lab sans terraform init -migrate-state. Terraform croit à un nouveau state vide et propose de tout recréer. Stop. Relisez le message d’init. En ca-central-1, le bucket de state n’est pas le bucket métier du tuto workflow — séparez-les dès le nommage.
À retenir : le state est plus sensible que le HCL. Traitez-le comme un secret d’infrastructure.
Retour parcours Terraform — hub de la série et leçons sœurs.


