
Un state perdu, tronqué ou amputé ne détruit rien : l'infrastructure continue
de tourner, ce sont les enregistrements qui manquent. La réparation consiste
donc à remettre le bon state, jamais à relancer un apply qui recréerait ce
qui existe déjà. Ce guide s'adresse à qui doit mener cette réparation :
sauvegarder avec terraform state pull, choisir la bonne sauvegarde,
restaurer avec terraform state push, et prouver que l'opération a
réussi autrement qu'en lisant une sortie à l'œil.
Toutes les affirmations mesurables de ce guide, messages d'erreur, codes de
sortie et valeurs de serial, ont été rejouées sur Terraform v1.15.4 avec
le provider hashicorp/local, sur un state local.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Sauvegarder un state, et ce que la sauvegarde ne contient pas
- Reconnaître les deux filets que Terraform pose de lui-même
- Restaurer avec
state push, et la vraie portée de-force - Choisir entre plusieurs sauvegardes candidates
- Prouver la réussite par le code de sortie, pas par la lecture
Prérequis
Section intitulée « Prérequis »- Terraform 1.11 ou plus récent (installer Terraform).
- Savoir ce que contient un state, en particulier les champs
serialetlineage: comprendre le state les définit, ce guide les met en œuvre. - Sur un backend partagé, savoir lever un verrou resté en place :
verrouiller le state.
La procédure officielle de reprise place
terraform force-unlocken étape 1, avant toute sauvegarde, parce qu'une restauration fait souvent suite à unapplyinterrompu.
Les exemples reposent sur une configuration jetable, sans cloud :
terraform { # Version epinglee : les messages d'erreur cites plus bas sont ceux de la # branche 1.15.x, ils ont evolue au fil des versions. required_version = ">= 1.7" required_providers { local = { source = "hashicorp/local", version = ">= 2.5" } }}
# Un fichier sur le disque : il rend toute recreation immediatement visible.resource "local_file" "note" { filename = "${path.root}/note.txt" content = "note\n"}Que sauvegarde-t-on au juste ?
Section intitulée « Que sauvegarde-t-on au juste ? »Le state, et lui seul. terraform state pull écrit sur la sortie standard le
state courant, quel que soit le backend : c'est le geste de sauvegarde de
référence, y compris sur un backend distant où le fichier n'est pas
accessible directement.
terraform state pull > sauvegarde-2026-07-28.jsonQuels filets Terraform pose-t-il tout seul ?
Section intitulée « Quels filets Terraform pose-t-il tout seul ? »Deux, et ils ne couvrent pas les mêmes opérations. C'est la source de malentendu la plus fréquente sur ce sujet.
Le premier est terraform.tfstate.backup, propre au backend local. Il
contient l'état d'avant la dernière écriture, n'existe pas après le
premier apply, et n'est écrit qu'une fois par exécution.
Le second passe inaperçu. Une commande de manipulation du state ne touche pas à ce fichier : elle dépose un fichier horodaté.
terraform state rm local_file.notels terraform.tfstate*terraform.tfstateterraform.tfstate.1785262538.backupVérifié sur 1.15.4 : après ce state rm, terraform.tfstate.backup n'a pas
bougé, et c'est le fichier horodaté qui porte l'état complet d'avant la
commande. Le filet qui vous sauvera après une manipulation de state n'est donc
pas celui que vous surveillez. Comme ces fichiers sont presque toujours
gitignorés, un nettoyage de répertoire les emporte sans bruit.
L'option -backup redirige ce fichier vers le chemin de votre choix, ce qui
permet de l'archiver ailleurs que dans le répertoire de travail :
terraform state rm -backup=filet-avant-retrait.json local_file.noteRestaurer avec terraform state push
Section intitulée « Restaurer avec terraform state push »La commande fait le chemin inverse du pull et écrit un state complet à
la place du courant. Elle oppose deux refus, et ces refus sont exactement ce
qui protège l'infrastructure.
Le garde-fou du serial empêche d'écraser des écritures plus récentes :
Failed to write state: cannot import state with serial 3 over newer state with serial 5Le garde-fou du lineage empêche d'écraser un projet avec le state d'un
autre :
Failed to write state: cannot import state with lineage "7f3c1a90-2b48-4e5d-9c17-6ad0e2b4915f" over unrelated state with lineage "88580bd8-9e4c-4a0f-b71e-2f0d3c6ab145"Un troisième cas se produit quand deux states portent le même serial
mais un contenu différent, typiquement après une édition manuelle :
Failed to write state: cannot overwrite existing state with serial 1 with a different state that has the same serialCes trois refus sortent en code 1. À l'inverse, pousser exactement ce
qu'un pull vient de rendre est accepté en silence, en code 0 : lineage,
serial et contenu coïncident, il n'y a rien à signaler.
Pourquoi -force est plus dangereux qu'il n'en a l'air
Section intitulée « Pourquoi -force est plus dangereux qu'il n'en a l'air »Parce qu'il neutralise les deux garde-fous d'un coup, pas seulement celui du
serial. La documentation le formule sans détour, et l'avertissement est
explicite : « Both of these safety checks can be disabled with the -force
flag. This is not recommended. »
terraform state push -force sauvegarde-2026-07-28.jsonUne restauration légitime tombe forcément sur le refus du serial, puisqu'elle
remet un état antérieur : -force est donc inévitable, et c'est précisément
ce qui rend l'option piégeuse. Il n'y a ni confirmation ni question, et le
contrôle de lineage tombe en même temps. Vérifiez le lineage avant de
forcer, jamais après :
terraform state pull | jq -r .lineagejq -r .lineage sauvegarde-2026-07-28.jsonDeux valeurs identiques, et la sauvegarde appartient bien à ce projet. Deux valeurs différentes, et vous vous apprêtiez à écraser une infrastructure avec le state d'une autre.
Quel serial obtient-on après une restauration ?
Section intitulée « Quel serial obtient-on après une restauration ? »Celui de la sauvegarde, augmenté de un. Pas celui du state remplacé, ce qui surprend et porte des conséquences concrètes. Mesuré sur 1.15.4 :
| Fichier | serial |
|---|---|
| le state courant, avant | 15 |
| la sauvegarde poussée | 1 |
| le state courant, après | 2 |
La bonne nouvelle, c'est qu'un push incrémente toujours : le state
restauré porte donc un serial supérieur à celui de la sauvegarde. Une
copie de fichier posée à la main par-dessus terraform.tfstate laisserait le
serial identique : le contournement se lit dans le fichier longtemps après.
La mauvaise concerne les backends partagés. Votre state restauré peut
porter un serial très inférieur à celui que d'autres postes ont déjà vu. Un
collègue dont le cache connaît le serial 15 se retrouve devant un state à 2, et
son prochain push légitime écrasera votre réparation. Prévenez l'équipe
avant que quiconque ne pousse.
Comment prouver qu'une restauration a réussi ?
Section intitulée « Comment prouver qu'une restauration a réussi ? »Par le code de sortie, jamais par la lecture d'un message. Deux contrôles suffisent, et ils s'automatisent :
-
Le plan ne propose plus rien
Fenêtre de terminal terraform plan -detailed-exitcodeecho $?0 signifie convergence, 2 un changement en attente, 1 une erreur. Un code 2 après une restauration est le symptôme d'une sauvegarde périmée : ses attributs ne décrivent plus le réel, et Terraform propose un remplacement,
local_file.note must be replaced. -
Le state porte les ressources attendues
Fenêtre de terminal terraform show -json | jq '[.values.root_module.resources[]| select(.mode == "managed") | .address]'Le comptage se fait sur la sortie structurée, jamais sur
state list, dont la mise en forme s'adresse à un humain.
Restaurer une partie seulement du state
Section intitulée « Restaurer une partie seulement du state »terraform state push lit aussi son entrée sur stdin quand on lui passe un
tiret, ce qui autorise une réparation chirurgicale en une ligne :
terraform state pull | jq '.serial = 42' | terraform state push -La documentation précise que ces données sont « loaded completely into memory and
verified prior to being written » : la vérification a lieu avant
l'écriture. Deux précautions accompagnent cette forme. Le fichier doit être en
UTF-8 sans BOM, cause numéro un des restaurations qui échouent sous
Windows, où il faut passer par Set-Content. Et sur HCP Terraform ou le
backend remote, l'option -ignore-remote-version existe pour passer outre
le contrôle de version de l'espace de travail.
Dépannage
Section intitulée « Dépannage »Le réflexe qui évite la majorité de ces situations : photographier le state
avant toute écriture, puisqu'un push ne se rejoue pas.
| Symptôme | Cause probable | Solution |
|---|---|---|
cannot import state with serial N over newer state with serial M | La sauvegarde est antérieure au state courant | Normal pour une restauration : vérifier le lineage, puis -force |
cannot import state with lineage ... over unrelated state | La sauvegarde vient d'un autre projet | Ne pas forcer : chercher la sauvegarde du bon lineage |
cannot overwrite existing state with serial N with a different state that has the same serial | Deux states au même serial, au contenu différent | Repartir d'un state pull frais plutôt que d'un fichier édité |
| Le plan sort en 2 après la restauration | Sauvegarde périmée | Reprendre une sauvegarde postérieure au dernier apply |
Le state restauré porte un serial très bas | Comportement normal du push | Prévenir l'équipe avant tout nouveau push |
Aucun terraform.tfstate.backup après un state rm | Les commandes state écrivent un fichier horodaté | Chercher terraform.tfstate.<epoch>.backup |
| La restauration échoue sous Windows | Fichier encodé en UTF-8 avec BOM | Réécrire avec Set-Content, sans BOM |
Mettre en pratique
Section intitulée « Mettre en pratique »Le lab sauvegarder et restaurer le state remet un projet dont le state a été
amputé par un state rm malencontreux, trois sauvegardes candidates dont
une seule est restaurable, et une infrastructure parfaitement intacte. Les
tests comparent lineage et serial, exigent la photo préalable de l'état
endommagé, et vérifient la date de modification des fichiers : le raccourci
terraform apply, qui recrée l'objet manquant au lieu de réparer le state, y
échoue. Il se joue hors ligne.
À retenir
Section intitulée « À retenir »terraform state pullsauvegarde le state sur tous les backends, mais son champterraform_versionest réécrit à la version locale.- Le backup
terraform.tfstate.backupest local, absent après le premierapply, et non mis à jour par les commandesstate. - Ces commandes déposent un backup horodaté,
terraform.tfstate.<epoch>.backup, que-backup=cheminredirige. Mesuré sur 1.15.4,-backup=-ne le désactive pas. state pushrefuse unserialplus ancien, unlineageétranger, et un contenu différent àserialégal. Les trois sortent en code 1.-forceneutralise les deux garde-fous,lineagecompris : contrôlez ce champ avant de forcer.- Après un
push, leserialvaut celui de la sauvegarde plus un. Unserialinchangé signe une copie de fichier, pas une restauration. - La preuve d'une restauration réussie est
plan -detailed-exitcodeà 0, doublée d'un comptage surterraform show -json. terraform state push -lit le state sur stdin, en UTF-8 sans BOM.
FAQ : questions fréquentes
Section intitulée « FAQ : questions fréquentes »Les questions ci-dessous portent sur ce qui bloque le plus souvent : le push
refusé, le choix entre plusieurs sauvegardes, et la portée exacte de
-force.
Une commande, tous les backends
terraform state pull > sauvegarde-2026-07-28.json
Attention à ce que la sauvegarde ne contient pas fidèlement. La documentation prévient : « You cannot use this command to inspect the Terraform version of the remote state, as it will always be converted to the current Terraform version before output. »Vérifié sur Terraform 1.15.4 : un state portant "terraform_version": "1.5.0" ressort en 1.15.4 après un pull. Ce champ décrit donc le binaire qui a lu le state, pas celui qui l'a écrit.Trois messages, trois causes
Failed to write state: cannot import state with serial 3 over newer state with serial 5
Normal pour une restauration : vous remettez un état antérieur.Failed to write state: cannot import state with lineage "..." over unrelated state with lineage "..."
La sauvegarde vient d'un autre projet. Ne forcez pas, cherchez la bonne.Failed to write state: cannot overwrite existing state with serial 1 with a different state that has the same serial
Deux states au même serial mais au contenu différent, typiquement après une édition manuelle. À l'inverse, pousser exactement ce qu'un pull vient de rendre est accepté en silence, en code 0.Deux contrôles tombent, pas un
La documentation est explicite : « Both of these safety checks can be disabled with the-force flag. This is not recommended. »Le piège tient à ceci : une restauration remet toujours un état antérieur, donc elle bute forcément sur le contrôle du serial. On finit par taper -force par habitude, et le contrôle de lineage disparaît avec lui, sans un mot.terraform state pull | jq -r .lineage
jq -r .lineage sauvegarde.json
Deux valeurs identiques : la sauvegarde appartient à ce projet. Deux valeurs différentes : vous alliez écraser une infrastructure avec le state d'une autre.La sauvegarde plus un
| Fichier | serial |
|---|---|
| le state courant, avant | 15 |
| la sauvegarde poussée | 1 |
| le state courant, après | 2 |
push incrémente toujours, donc le state restauré porte un serial supérieur à celui de la sauvegarde. Une copie de fichier posée à la main par-dessus terraform.tfstate laisserait le serial inchangé, et le contournement se lit longtemps après.Conséquence risquée sur un backend partagé : votre state peut porter un serial bien inférieur à celui que d'autres postes ont déjà vu, et leur prochain push écrasera votre réparation.Deux filets, dont un discret
terraform state rm local_file.note
ls terraform.tfstate*
terraform.tfstate
terraform.tfstate.1785262538.backup
Vérifié sur 1.15.4 : après ce state rm, terraform.tfstate.backup n'a pas bougé. Le filet réel est le fichier horodaté. Comme ces fichiers sont presque toujours gitignorés, un nettoyage de répertoire les emporte sans bruit.L'option -backup=chemin redirige ce fichier. En revanche -backup=-, que la page du backend local présente comme désactivant les sauvegardes, n'empêche pas l'écriture du fichier horodaté : mesuré dans deux répertoires neufs.Le code de sortie, pas la lecture
terraform plan -detailed-exitcode
echo $?
0 signifie convergence, 2 un changement en attente, 1 une erreur. Après une restauration, un 2 est le symptôme d'une sauvegarde périmée : Terraform annonce alors un remplacement, local_file.note must be replaced.terraform show -json | jq '[.values.root_module.resources[]
| select(.mode == "managed") | .address]'
Le comptage se fait sur la sortie structurée, jamais sur state list, dont la mise en forme s'adresse à un humain.Le pipeline pull, jq, push
terraform state pull | jq '.serial = 42' | terraform state push -
La documentation précise que ces données sont « loaded completely into memory and verified prior to being written » : la vérification a lieu avant l'écriture.Deux précautions accompagnent cette forme :- le fichier doit être en UTF-8 sans BOM, cause numéro un des échecs sous Windows, où il faut passer par
Set-Content; - sur HCP Terraform ou le backend
remote, l'option-ignore-remote-versionpermet de passer outre le contrôle de version de l'espace de travail.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Quiz Gérer le state Terraform : Un contrôle des acquis sur les backends, le verrouillage et les opérations de state.
- Séparer dev, staging et prod : Le cloisonnement des states, pour qu'une restauration ne touche qu'un seul périmètre.
- Organiser un dépôt Terraform : La structure de dépôt qui rend chaque state identifiable et sa sauvegarde traçable.