
Quand deux personnes lancent terraform apply en même temps sur le même projet,
la seconde écrase le travail de la première. Le verrouillage du state
l'empêche : Terraform pose un verrou avant toute opération susceptible
d'écrire, et le libère à la fin. Ce guide montre comment observer un verrou
réel, quelles commandes il rejette (le plan en fait partie, contrairement à
une idée répandue), lesquelles il laisse passer, et comment l'activer sur
un backend distant, où il n'est pas toujours automatique.
Tous les comportements mesurés de ce guide ont été exécutés sur Terraform v1.15.4, backend local, sorties et codes de retour compris. Les arguments du backend S3 proviennent de sa documentation officielle.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Observer un verrou réel et lire ses sept champs
- Où le fichier de verrou est écrit, et pourquoi ce n'est pas toujours la racine
- Ce que le verrou rejette et ce qu'il laisse passer, code de retour à l'appui
-lock-timeout: attendre le verrou au lieu de le désactiveruse_lockfile: activer le verrouillage sur S3, où il est opt-in
Prérequis
Section intitulée « Prérequis »- Terraform 1.11 ou plus récent (installer Terraform). Le verrouillage S3 natif date de la 1.10.
- Le rôle du state (comprendre le state) et la configuration d'un backend (backends Terraform).
À quoi sert le verrouillage du state
Section intitulée « À quoi sert le verrouillage du state »Le verrouillage (state locking) garantit qu'une seule opération à la fois peut écrire le state. Terraform pose un verrou au début de l'opération, le maintient pendant toute sa durée, et le libère à la fin. Une autre opération qui voudrait écrire pendant ce temps est rejetée, pas mise en attente.
Sans lui, voici ce qui arrive quand Alice et Bob travaillent sur le même projet :
| Étape | Alice | Bob | Résultat |
|---|---|---|---|
| 1 | Lit le state (serial 5) | ||
| 2 | Lit le state (serial 5) | Même point de départ | |
| 3 | apply crée VM-A | Serial 6 | |
| 4 | apply crée VM-B | Écrase le serial 6 d'Alice | |
| 5 | VM-A disparaît du state |
VM-A existe toujours sur l'hyperviseur mais plus dans le state : Terraform ne la gère plus, et personne ne la détruira. Le verrouillage supprime cette fenêtre en faisant échouer l'opération de Bob à l'étape 4.
Observer un verrou en action
Section intitulée « Observer un verrou en action »Un verrou ne dure que le temps de l'opération. Sur une configuration triviale,
l'apply se termine en une fraction de seconde et il n'y a rien à voir. Pour
l'observer, il faut une ressource lente. La ressource terraform_data est
intégrée à Terraform, donc aucun provider à télécharger :
# main.tf : ressource lente, pour rendre le verrou observableterraform { backend "local" { path = "etat/projet.tfstate" }}
resource "terraform_data" "lent" { input = "verrouillage"
provisioner "local-exec" { command = "sleep 20" }}-
Dans un premier terminal, lancez l'apply
Fenêtre de terminal terraform initterraform apply -auto-approveLe provisioner occupe l'apply pendant vingt secondes.
-
Dans un second terminal, trouvez le fichier de verrou
Ne devinez pas son nom, cherchez-le :
Fenêtre de terminal find . -name '*.lock.info'./etat/.projet.tfstate.lock.info -
Affichez son contenu
Fenêtre de terminal cat etat/.projet.tfstate.lock.info | python3 -m json.tool{"ID": "11015c79-a539-8dad-096b-b96778355839","Operation": "OperationTypeApply","Info": "","Who": "bob@master1","Version": "1.15.4","Created": "2026-07-27T19:51:22.084516333Z","Path": "etat/projet.tfstate"} -
Une fois l'apply terminé, vérifiez que le verrou a disparu
Fenêtre de terminal find . -name '*.lock.info' | wc -l0
Le cycle de vie est automatique : créé au début de l'opération, supprimé à la fin.
Où Terraform écrit le fichier de verrou
Section intitulée « Où Terraform écrit le fichier de verrou »Le fichier de verrou suit le state, il n'a pas d'emplacement fixe. Son nom se
dérive du chemin du state : le nom du fichier de state, préfixé d'un
point, suffixé de .lock.info, dans le répertoire du state.
Avec le path par défaut (terraform.tfstate à la racine), cela donne
.terraform.tfstate.lock.info à la racine du projet, d'où l'idée répandue que
c'est son emplacement. Ce n'est vrai que dans ce cas. Avec
path = "etat/projet.tfstate", le verrou est écrit en
etat/.projet.tfstate.lock.info, comme le montre le find ci-dessus. Un script
de supervision qui cherche .terraform.tfstate.lock.info à la racine ne trouvera
jamais rien sur un projet dont le state est ailleurs.
Attention à ne pas confondre les deux chemins : le champ Path du verrou
vaut etat/projet.tfstate, c'est-à-dire le state verrouillé, et non le
fichier de verrou lui-même.
Les sept champs du verrou
Section intitulée « Les sept champs du verrou »Le fichier de verrou est un objet JSON de sept champs. Ils répondent à trois questions de diagnostic : qui détient le verrou, pour quelle opération, depuis quand.
| Champ | Description | Exemple mesuré |
|---|---|---|
ID | Identifiant unique du verrou (UUID), sert de nonce à force-unlock | 11015c79-a539-8dad-096b-b96778355839 |
Operation | Opération en cours | OperationTypeApply |
Info | Message libre, vide en usage courant | "" |
Who | Utilisateur et machine détenteurs | bob@master1 |
Version | Version de la CLI qui a posé le verrou | 1.15.4 |
Created | Date de création, en UTC | 2026-07-27T19:51:22Z |
Path | State verrouillé | etat/projet.tfstate |
Le champ Info est souvent oublié dans les transcriptions, mais il fait bien
partie du verrou et apparaît dans le message d'erreur.
Ce que le verrou rejette, et ce qu'il laisse passer
Section intitulée « Ce que le verrou rejette, et ce qu'il laisse passer »Toute commande susceptible d'écrire le state est rejetée, terraform plan
compris. C'est le point le plus souvent faux dans les mémentos : le plan
rafraîchit le state, donc il peut l'écrire, donc il prend un verrou. Voici
les codes de retour relevés pendant qu'un apply tenait le verrou, sur Terraform
1.15.4 :
| Commande lancée pendant le verrou | Code de retour | Verdict |
|---|---|---|
terraform plan | 1 | rejetée |
terraform apply | 1 | rejetée |
terraform force-unlock <ID> | 1 | rejetée (backend local) |
terraform plan -lock=false | 0 | passe, sans protection |
terraform state list | 0 | passe |
terraform show -json | 0 | passe |
terraform output -json | 0 | passe |
terraform state pull | 0 | passe |
terraform validate | 0 | passe |
Les lectures pures passent. C'est un point de diagnostic direct : si
terraform state list échoue, le problème n'est pas le verrou, il faut
chercher ailleurs. La commande terraform state list n'accepte d'ailleurs même
pas l'option -lock, elle répond flag provided but not defined: -lock, ce qui
confirme qu'elle ne verrouille pas.
Voici le message complet renvoyé par un plan lancé pendant un apply :
Error: Error acquiring the state lock
Error message: resource temporarily unavailableLock Info: ID: 11015c79-a539-8dad-096b-b96778355839 Path: etat/projet.tfstate Operation: OperationTypeApply Who: bob@master1 Version: 1.15.4 Created: 2026-07-27 19:51:22.084516333 +0000 UTC Info:
Terraform acquires a state lock to protect the state from being writtenby multiple users at the same time. Please resolve the issue above and tryagain. For most commands, you can disable locking with the "-lock=false"flag, but this is not recommended.Attendre le verrou avec -lock-timeout
Section intitulée « Attendre le verrou avec -lock-timeout »Par défaut, une commande refusée par un verrou échoue immédiatement :
-lock-timeout vaut 0s. C'est le réglage qui produit le plus de faux
problèmes en intégration continue, où deux exécutions qui se suivent de près
se rejettent l'une l'autre pour quelques secondes de recouvrement.
La bonne réponse n'est pas de désactiver le verrouillage, c'est de patienter :
terraform apply -lock-timeout=5mTerraform réessaie alors d'acquérir le verrou jusqu'à l'expiration du délai.
Mesuré sur 1.15.4 : un plan -lock-timeout=10s lancé pendant un apply attend
bien dix secondes avant de sortir en erreur, au lieu d'échouer aussitôt.
Un fichier de verrou n'est pas un verrou
Section intitulée « Un fichier de verrou n'est pas un verrou »Voici le point qui coûte le plus cher en incident, et il est contre-intuitif. La
documentation du backend local dit qu'il verrouille le state « using system
APIs » : le verrou réel est un verrou système posé par le processus, que le
noyau libère à la mort de ce processus, quelle qu'en soit la cause. Le
fichier .lock.info n'est que la carte de visite de ce verrou : il dit qui
détient quoi, il ne détient rien lui-même.
Conséquence, vérifiée sur 1.15.4 : après un kill -9 en plein apply, le
fichier reste bien sur le disque, mais il ne bloque rien. Le plan suivant
s'exécute normalement et Terraform supprime le fichier lui-même :
# Le fichier est bien resté après l'arrêt brutalls etat/.projet.tfstate.lock.infoetat/.projet.tfstate.lock.info# Le plan suivant aboutit malgré lui, et fait le ménageterraform plan -input=false > /dev/null 2>&1; echo $?ls etat/.projet.tfstate.lock.info0ls: cannot access 'etat/.projet.tfstate.lock.info': No such file or directoryUn fichier .lock.info fabriqué à la main ne bloque pas davantage : le plan
passe et l'efface. Sur un backend local, un « verrou bloqué depuis des
heures » n'existe donc pas, et le rm que l'on voit souvent recommandé est un
rituel sans effet. Si une commande est réellement rejetée, c'est qu'un
processus Terraform tourne encore : cherchez-le avec ps, ne supprimez pas le
fichier.
force-unlock : à quoi il sert, et où il ne sert pas
Section intitulée « force-unlock : à quoi il sert, et où il ne sert pas »terraform force-unlock libère un verrou côté serveur, sur un backend
distant dont le détenteur a disparu (agent de CI tué, réseau coupé). Il prend
l'identifiant du verrou en argument :
terraform force-unlock 11015c79-a539-8dad-096b-b96778355839Cet identifiant joue le rôle de nonce : il garantit que vous levez bien le
verrou que vous croyez, et pas celui qu'un collègue vient de poser entre-temps.
C'est la raison pour laquelle on ne recopie jamais un ID trouvé ailleurs que dans
le message d'erreur du moment. La commande demande confirmation, et l'option
-force s'en passe, ce qui la rend utilisable en automation :
terraform force-unlock -force 11015c79-a539-8dad-096b-b96778355839Sur un backend local, la commande échoue dans tous les cas, avec un code de
retour 1, que le verrou soit tenu ou libre et que l'ID soit juste ou faux. Le
message diffère selon la forme employée :
| Forme | Message |
|---|---|
terraform force-unlock <ID> | Local state cannot be unlocked by another process |
terraform force-unlock -force <ID> | Failed to unlock state: LocalState not locked |
Ce n'est pas un défaut à contourner, c'est cohérent avec la section précédente : il n'y a rien à déverrouiller à distance sur un verrou que le noyau a déjà libéré.
Activer le verrouillage sur un backend S3
Section intitulée « Activer le verrouillage sur un backend S3 »Sur un backend distant, le verrouillage n'est pas automatique : il dépend du
backend et de sa configuration. Le backend S3 est explicite, « State locking
is an opt-in feature of the S3 backend », et son argument use_lockfile
« Defaults to false ». Autrement dit, une configuration S3 qui ne le pose pas
n'a aucun verrou, quelle que soit la version de Terraform :
terraform { backend "s3" { bucket = "mon-org-tfstate" key = "production/reseau.tfstate" region = "eu-west-3" use_lockfile = true # sans cette ligne, aucun verrou }}Le verrou est alors un objet <key>.tflock écrit à côté du state dans le
bucket, ici production/reseau.tfstate.tflock.
Trois points opérationnels que l'on découvre souvent trop tard :
- Les permissions IAM du verrou ne sont pas celles du state.
use_lockfileexiges3:GetObject,s3:PutObjectets3:DeleteObjectsur l'objet.tflock, alors ques3:DeleteObjectn'est pas nécessaire sur le state lui-même. Un verrou S3 qui ne se libère jamais vient presque toujours de cette permission manquante. - Le versioning du bucket est le dernier filet. La documentation S3 le recommande en tête de page : « It is highly recommended that you enable Bucket Versioning on the S3 bucket to allow for state recovery in the case of accidental deletions and human error. »
- DynamoDB est déprécié. L'ancien mécanisme par table (
dynamodb_table) est marqué « DynamoDB-based locking is deprecated and will be removed in a future minor version ». Pour migrer sans fenêtre sans verrou, les deux mécanismes peuvent être configurés simultanément le temps de la bascule, puis on retiredynamodb_table.
Le verrouillage selon le backend
Section intitulée « Le verrouillage selon le backend »Chaque backend implémente le verrouillage avec les primitives de son stockage, et
tous n'acceptent pas force-unlock. Le tableau ci-dessous résume ce que dit la
documentation de chacun.
| Backend | Mécanisme | force-unlock |
|---|---|---|
| local | Verrou système sur le fichier, plus un .lock.info descriptif | Non |
| s3 | Objet <key>.tflock, opt-in via use_lockfile | Oui |
| gcs | Objet de verrou dans le bucket | Oui |
| azurerm | Lease sur le blob | Oui |
| consul | Session Consul | Oui |
| pg | Advisory lock PostgreSQL | Non |
| kubernetes | Ressource Lease | Oui |
| oci | Objet de verrou via l'en-tête If-None-Match: * | Oui |
| http | Requêtes LOCK et UNLOCK, optionnel via lock_address | Oui |
Deux lignes méritent une explication. Le backend pg ne supporte pas
force-unlock : ses verrous sont des advisory locks PostgreSQL, « these
database-native locks will automatically unlock when the session is aborted or
the connection fails ». Le diagnostic passe par la vue pg_locks, pas par une
commande Terraform. Le backend http, lui, ne verrouille que si
lock_address est renseigné : comme S3, il faut le demander.
Dépannage
Section intitulée « Dépannage »Le réflexe utile est toujours le même : lire le code de retour et les champs du verrou avant d'agir, plutôt que de supprimer un fichier.
| Symptôme | Cause probable | Solution |
|---|---|---|
Error acquiring the state lock | Une opération est réellement en cours | Attendre, ou relancer avec -lock-timeout=5m. Les champs Who et Created disent qui et depuis quand |
| Le message revient alors que personne n'applique | Un processus Terraform survit (shell détaché, job CI zombie) | pgrep -a terraform et terminer le processus. Sur backend local, supprimer le .lock.info ne changerait rien |
| Deux jobs CI se rejettent en rafale | -lock-timeout vaut 0s par défaut | Poser -lock-timeout=5m dans le pipeline, jamais -lock=false |
Local state cannot be unlocked by another process | force-unlock sur un backend local | Normal : il n'y a rien à déverrouiller à distance. Chercher le processus vivant |
| Verrou S3 jamais libéré | s3:DeleteObject absent sur <key>.tflock | Ajouter la permission sur l'objet de verrou, puis terraform force-unlock <ID> |
terraform state list échoue pendant un apply | Ce n'est pas le verrou : les lectures passent | Chercher la vraie cause (state absent, droits, backend injoignable) |
Mettre en pratique
Section intitulée « Mettre en pratique »Le lab verrouillage du state fait relever ces mesures par l'apprenant plutôt que les lui donner : il déplace le state hors de la racine, rend l'apply assez lent pour observer un verrou, puis consigne les codes de retour de chaque geste. Les tests refont l'expérience de leur côté et comparent, y compris l'arrêt brutal et son fichier résiduel. Il se joue hors ligne, sans compte cloud.
À retenir
Section intitulée « À retenir »- Le verrouillage garantit une seule écriture du state à la fois ; il est posé au début de l'opération et libéré à la fin.
terraform planest verrouillé lui aussi : il rafraîchit, donc il peut écrire. Les lectures pures (state list,show -json,output) passent.- Le fichier de verrou suit le state : avec
path = "etat/projet.tfstate"il s'écrit enetat/.projet.tfstate.lock.info, et son champPathdésigne le state. - Le verrou porte sept champs,
Infocompris ;IDsert de nonce àforce-unlock. - En CI, la bonne réponse au rejet est
-lock-timeout=5m, jamais-lock=false. - Sur backend local, un
.lock.inforesté après un crash ne bloque rien : leplansuivant aboutit et efface le fichier. Lermmanuel est inutile. - Sur S3, le verrouillage est opt-in : sans
use_lockfile = true, aucun verrou. DynamoDB est déprécié, et le verrou exiges3:DeleteObject. - Les backends local et
pgn'acceptent pasforce-unlock.
FAQ : questions fréquentes
Section intitulée « FAQ : questions fréquentes »Les questions ci-dessous portent sur ce qui bloque le plus souvent : la commande qu'on ne croyait pas verrouillée, le fichier qu'on supprime pour rien, et le verrou S3 qu'on croit actif. Chaque réponse donne la vérification correspondante.
Oui, et c'est le point le plus souvent faux
terraform plan rafraichit le state avant de calculer les changements. Comme il peut l'ecrire, il prend un verrou au meme titre que apply.Mesure sur Terraform 1.15.4, backend local, pendant qu'un apply tient le verrou :terraform plan -input=false > /dev/null 2>&1; echo $?
# 1 -> Error: Error acquiring the state lock
La formulation officielle est « all operations that could write state », ce qui inclut le rafraichissement du plan. Les listes qui enumerent apply, destroy, import, state mv et state rm en oubliant plan sont incompletes.Les lectures pures ne verrouillent pas
Codes de retour releves sur Terraform 1.15.4 pendant qu'unapply tenait le verrou :| Commande | Code | Verdict |
|---|---|---|
terraform plan |
1 | rejetee |
terraform apply |
1 | rejetee |
terraform state list |
0 | passe |
terraform show -json |
0 | passe |
terraform output -json |
0 | passe |
terraform state pull |
0 | passe |
terraform validate |
0 | passe |
terraform state list echoue, le verrou n'y est pour rien. La commande n'accepte meme pas l'option -lock, elle repond flag provided but not defined: -lock.Il suit le state, il n'est pas a un emplacement fixe
Le nom se derive du chemin du state : nom du fichier de state, prefixe d'un point, suffixe de.lock.info, dans le repertoire du state.pathpar defaut (terraform.tfstate) :.terraform.tfstate.lock.infoa la racine, d'ou la confusion.path = "etat/projet.tfstate":etat/.projet.tfstate.lock.info.
find . -name '*.lock.info'
Ne confondez pas les deux chemins : le champ Path du verrou designe le state verrouille, pas le fichier de verrou.Le fichier n'est pas le verrou
La documentation du backend local dit qu'il verrouille le state « using system APIs » : le verrou reel est un verrou systeme que le noyau libere a la mort du processus. Le.lock.info n'est que sa carte de visite.Verifie sur Terraform 1.15.4 apres un kill -9 en plein apply :ls etat/.projet.tfstate.lock.info # le fichier est bien la
terraform plan -input=false > /dev/null 2>&1; echo $?
# 0 -> le plan aboutit malgre lui
ls etat/.projet.tfstate.lock.info
# No such file or directory -> Terraform l'a supprime
Si une commande est reellement rejetee, c'est qu'un processus Terraform tourne encore : cherchez-le avec pgrep -a terraform au lieu de supprimer le fichier.C'est le comportement attendu, pas une panne
force-unlock sert a lever un verrou cote serveur sur un backend distant. Sur un backend local il n'y a rien a lever : le noyau a deja fait le travail.Mesure sur Terraform 1.15.4, code de retour 1 dans les quatre cas (verrou tenu ou libre, identifiant juste ou faux) :| Forme | Message |
|---|---|
terraform force-unlock <ID> |
Local state cannot be unlocked by another process |
terraform force-unlock -force <ID> |
Failed to unlock state: LocalState not locked |
-force supprime la confirmation interactive, pour l'automation.Le verrouillage S3 est opt-in
La documentation est explicite : « State locking is an opt-in feature of the S3 backend », etuse_lockfile « Defaults to false ». Une configuration S3 sans cette ligne n'a aucun verrou, quelle que soit la version de Terraform.terraform {
backend "s3" {
bucket = "mon-org-tfstate"
key = "production/reseau.tfstate"
region = "eu-west-3"
use_lockfile = true
}
}
Le verrou est l'objet <key>.tflock, a cote du state. Deux pieges : il exige s3:DeleteObject en plus de GetObject et PutObject (un verrou qui ne se libere jamais vient presque toujours de la), et le versioning du bucket reste le dernier filet de securite.-lock-timeout attend, -lock=false expose
Par defaut,-lock-timeout vaut 0s : une commande refusee echoue immediatement. C'est ce qui fait que deux jobs de CI qui se suivent de pres se rejettent pour quelques secondes de recouvrement.terraform apply -lock-timeout=5m
Terraform reessaie alors jusqu'a l'expiration du delai. Verifie sur 1.15.4 : un plan -lock-timeout=10s lance pendant un apply attend bien dix secondes avant de sortir en erreur.-lock=false n'est pas une alternative : il ne fait pas attendre, il retire la protection et laisse deux ecritures se percuter. A reserver au debogage d'un backend casse, jamais a un pipeline.Pour aller plus loin
Section intitulée « Pour aller plus loin »- Sauvegarder et restaurer le state Terraform : le filet quand le verrouillage n'a pas suffi.
- Diagnostiquer le state Terraform : lire serial, lineage et derive apres une operation qui a mal tourne.
- Separer les environnements : un state par environnement, donc un verrou par environnement.
- State locking : la reference officielle : ce que verrouille chaque backend.
- terraform force-unlock : la commande, son nonce et l'option
-force. - Backend S3 :
use_lockfile, les permissions IAM du verrou et le versioning du bucket.