
Pour séparer dev, staging et prod, on écrit le code d'infrastructure une fois
dans un module, puis un répertoire par environnement qui appelle ce module
avec ses propres valeurs. Chaque répertoire est une configuration racine : il
a son terraform init, son backend et son état. C'est la structure que
recommande la documentation pour des déploiements distincts, et la seule qui
permette deux jeux de droits.
Tout ce qui suit a été exécuté sur Terraform v1.15.4 : sorties, codes de
retour et fichiers produits compris. Deux idées répandues n'y survivent pas,
celle du bloc backend qu'on paramètre par une variable, et celle du message
No configuration files qu'on attribue à terraform init.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Ce qu'est une configuration racine, et pourquoi trois dossiers font trois états
- Appeler un module commun depuis plusieurs racines, sans le recopier
- Vérifier quel état une racine pilote vraiment, sans le deviner
- Donner un backend par environnement en gardant un code identique partout
- Migrer un état existant vers un nouveau backend sans recréer les ressources
- Faire circuler une valeur d'une racine à l'autre par les outputs
- Prouver qu'un
destroyne franchit pas la frontière
Prérequis
Section intitulée « Prérequis »- Terraform 1.11 ou plus récent (installer Terraform). Les mesures de cette page viennent de la branche stable du moment, 1.15.x.
- Savoir ce qu'est un backend (backends Terraform).
- Avoir vu les workspaces (les workspaces Terraform), auxquels cette page oppose une alternative structurelle.
Une racine, un état
Section intitulée « Une racine, un état »Un répertoire qui contient des ressources et sur lequel vous lancez
terraform init est une configuration racine. La racine est l'unité de
travail de Terraform : elle porte un backend, donc un état, et un
terraform apply n'agit jamais que sur le sien.
La conséquence est immédiate et souvent découverte trop tard. Trois répertoires
envs/dev, envs/staging et envs/prod ne sont pas trois dossiers de
rangement, ce sont trois racines, donc trois états indépendants. Aucune
expression ne traverse cette frontière : module.reseau.id fonctionne entre un
appelant et son module, jamais entre deux racines.
C'est précisément ce qu'on cherche. Un environnement isolé n'est pas un environnement rangé à part, c'est un environnement dont l'état, le backend et les droits d'accès sont les siens.
Le montage : un module commun, deux racines
Section intitulée « Le montage : un module commun, deux racines »Le code d'infrastructure vit dans un module, écrit une seule fois. Chaque environnement se réduit alors à un appel de ce module avec ses valeurs, ce qui supprime la duplication que cette organisation pourrait faire craindre.
depot/├── modules/plaque/ le code commun, écrit UNE fois├── envs/dev/ racine : son init, son backend, son état├── envs/prod/ racine : son init, son backend, son état└── etats/ les deux fichiers d'état-
Écrire le module commun
Il ne contient aucune valeur en dur : tout ce qui varie d'un environnement à l'autre devient une variable.
modules/plaque/main.tf terraform {required_providers {local = {source = "hashicorp/local"version = ">= 2.5"}random = {source = "hashicorp/random"version = ">= 3.6"}}}variable "environnement" {description = "Nom de l'environnement servi."type = string}variable "repliques" {description = "Nombre de plaques à produire."type = number}resource "random_pet" "temoin" {length = 2}resource "local_file" "plaque" {count = var.repliquesfilename = "${path.root}/plaques/${var.environnement}-${count.index}.txt"content = "plaque ${var.environnement} numero ${count.index}\n"}output "temoin" {value = random_pet.temoin.id} -
Écrire la racine
devLe
sourceest un chemin relatif, ce qui garde le module dans le même paquet que son appelant. Le blocbackendreste vide, et la section suivante explique pourquoi.envs/dev/main.tf terraform {required_version = ">= 1.11.0"backend "local" {}}module "plaques" {source = "../../modules/plaque"environnement = "dev"repliques = 1}output "temoin" {value = module.plaques.temoin}Le plancher
>= 1.11.0est ouvert : il énonce la version minimale exigée, pas la version employée. Les sorties de cette page viennent de 1.15.4. -
Écrire la racine
prodC'est le même appel, avec d'autres valeurs. Ajouter
stagingplus tard ne demandera ni de toucher au module, ni de toucher aux deux racines existantes.envs/prod/main.tf module "plaques" {source = "../../modules/plaque"environnement = "prod"repliques = 3}
Chaque racine s'initialise et s'applique séparément. Les deux sorties ci-dessous ont été obtenues l'une après l'autre, dans deux répertoires différents, et leurs comptes de ressources diffèrent :
cd envs/dev && terraform apply -auto-approvemodule.plaques.random_pet.temoin: Creating...module.plaques.random_pet.temoin: Creation complete after 0s [id=game-dane]module.plaques.local_file.plaque[0]: Creating...module.plaques.local_file.plaque[0]: Creation complete after 0s [id=98c1f556a7a612708aecf5110d8fb18c7c039588]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
temoin = "game-dane"cd ../prod && terraform apply -auto-approveApply complete! Resources: 4 added, 0 changed, 0 destroyed.
Outputs:
temoin = "elegant-bear"Le random_pet de chaque environnement joue le rôle de témoin : c'est une
valeur que Terraform ne sait pas recalculer. Si elle change, la ressource a
été recréée, et cette page s'en servira pour prouver l'isolation autrement
que par conviction.
Vérifier quel état chaque racine pilote
Section intitulée « Vérifier quel état chaque racine pilote »Terraform enregistre la configuration de backend retenue dans
.terraform/terraform.tfstate, à ne pas confondre avec l'état lui-même. Ce
fichier est le moyen de vérifier, sans deviner, ce qu'une racine pilote
réellement :
jq '{type: .backend.type, config: .backend.config}' .terraform/terraform.tfstate{ "type": "local", "config": { "path": "../../etats/dev.tfstate", "workspace_dir": null }}La même commande dans envs/prod rend ../../etats/prod.tfstate. Deux
chemins distincts, donc deux états distincts : la séparation est
constatée, pas supposée.
Un backend par environnement, sans dupliquer le code
Section intitulée « Un backend par environnement, sans dupliquer le code »Le réflexe naturel consiste à paramétrer le chemin de l'état par une variable, pour n'écrire le bloc qu'une fois. Terraform le refuse, et c'est la contrainte qui explique tout le reste de cette section :
variable "env" { type = string default = "dev"}
terraform { backend "local" { path = "../etats/${var.env}.tfstate" }}Error: Variables not allowed
on main.tf line 8, in terraform: 8: path = "../etats/${var.env}.tfstate"
Variables may not be used here.L'init sort en 1. La documentation énonce la règle sans détour : « A
backend block cannot refer to named values (like input variables, locals, or data
source attributes). » Le backend est lu avant l'évaluation des variables, il
ne peut donc rien en connaître.
Deux issues existent. Dupliquer le fichier de backend dans chaque racine, ce qui fonctionne mais laisse les copies diverger, ou employer la configuration partielle, qui est la réponse officielle.
La configuration partielle
Section intitulée « La configuration partielle »Le principe est de laisser le bloc incomplet, donc identique dans toutes les racines, et de fournir les arguments manquants au moment de l'initialisation :
terraform { backend "local" {}}La première forme est un fichier de configuration, une paire par ligne, sans bloc autour :
path = "../../etats/dev.tfstate"terraform init -backend-config=backend-dev.hclInitializing the backend...
Successfully configured the backend "local"! Terraform will automaticallyuse this backend unless the backend configuration changes.
Terraform has been successfully initialized!La seconde forme passe les paires directement sur la ligne de commande, ce qui convient bien à une chaîne d'intégration où la valeur vient d'une variable du runner :
terraform init -backend-config="path=../../etats/prod.tfstate"Les deux ont été mesurées : elles produisent le même
Successfully configured the backend "local"!, et la trace
backend.config.path de la section précédente confirme que le chemin a bien été
retenu.
Migrer un état existant vers un nouveau backend
Section intitulée « Migrer un état existant vers un nouveau backend »Le scénario est celui de tout projet qui grandit : l'état est local au départ, et il doit rejoindre un backend partagé. Terraform ne le fait pas en silence, il l'annonce à chaque étape.
Au départ, la racine n'a aucun bloc backend et son état est le
terraform.tfstate du répertoire. Le témoin vaut ici exact-antelope. On
ajoute alors le bloc, puis on lance un plan sans réinitialiser :
Error: Backend initialization required, please run "terraform init"
Reason: Initial configuration of the requested backend "local"
Changes to backend configurations require reinitialization. This allowsTerraform to set up the new configuration, copy existing state, etc. Please run"terraform init" with either the "-reconfigure" or "-migrate-state" flags touse the current configuration.Le message nomme les deux options, qui ne font pas la même chose.
-migrate-state emporte l'état vers le nouveau backend, -reconfigure
l'abandonne et repart d'un état vide. Le choix par défaut n'existe pas : en
non interactif, Terraform refuse plutôt que de trancher pour vous.
terraform init -input=falseError: Can't ask approval for state migration when interactive input is disabled.Please remove the "-input=false" option and try again.C'est le comportement qu'il faut connaître avant de câbler un pipeline : une
migration d'état ne se fait pas dans un init -input=false. La commande qui
aboutit sans interaction porte les deux options :
terraform init -migrate-state -force-copyInitializing the backend...
Successfully configured the backend "local"! Terraform will automaticallyuse this backend unless the backend configuration changes.
Terraform has been successfully initialized!Le témoin vaut toujours exact-antelope après l'opération, et un
plan -detailed-exitcode sort en 0 : l'état a changé de place sans que la
moindre ressource soit recréée. Détail utile pour le ménage, l'ancien
terraform.tfstate n'est pas supprimé, il est vidé, mesuré à 0 octet,
et un terraform.tfstate.backup apparaît à côté.
Faire circuler une valeur entre deux racines
Section intitulée « Faire circuler une valeur entre deux racines »Puisque aucune expression ne traverse la frontière, la question devient : comment
prod consomme une valeur produite par une racine socle ? La voie officielle
est la lecture de l'état distant de l'autre racine, par ses outputs.
data "terraform_remote_state" "socle" { backend = "local"
config = { path = "../socle/terraform.tfstate" }}
output "reseau_consomme" { value = data.terraform_remote_state.socle.outputs.nom_reseau}L'init de la racine consommatrice signale que le mécanisme est intégré,
sans provider à installer, puis la valeur du socle traverse bien :
- terraform.io/builtin/terraform is built in to Terraform
reseau_consomme = "sharing-redfish"Le contrat entre deux racines, ce sont donc les outputs déclarés, et rien d'autre. Une valeur que le socle n'expose pas reste inaccessible, ce que Terraform dit très précisément :
Error: Unsupported attribute
on sonde.tf line 2, in output "fuite": 2: value = data.terraform_remote_state.socle.outputs.inexistant
data.terraform_remote_state.socle.outputs is object with 1 attribute "nom_reseau"This object does not have an attribute named "inexistant".Pourquoi les workspaces ne remplacent pas deux racines
Section intitulée « Pourquoi les workspaces ne remplacent pas deux racines »L'argument qu'on entend le plus est ergonomique : un
terraform workspace select oublié, et l'apply part dans le mauvais
environnement. Il est vrai, mais il est secondaire, et il se corrige par de
la discipline ou un garde-fou de pipeline.
L'argument de la documentation est structurel, et il ne se corrige pas :
Workspaces are not appropriate for system decomposition or deployments requiring separate credentials and access controls.
La raison tient en une phrase, « CLI workspaces within a working directory use
the same backend ». Un seul backend, donc un seul jeu d'accès : une
production qui doit s'authentifier avec un rôle différent de dev ne peut
pas vivre dans un workspace CLI, quelle que soit la rigueur de l'équipe.
Cela se constate. Dans la racine prod de cette page, on crée un workspace
puis on relit la configuration de backend :
terraform workspace new bac-a-sablejq '.backend.config' .terraform/terraform.tfstate{ "path": "../../etats/prod.tfstate", "workspace_dir": null}La configuration est inchangée, parce qu'elle appartient au répertoire,
pas au workspace. Deux répertoires, au contraire, ce sont deux blocs backend
distincts, donc deux cibles et deux jeux de droits possibles.
Les pièges mesurés
Section intitulée « Les pièges mesurés »Le mécanisme est en place. Voici ce qui casse en pratique, chaque cas ayant été rejoué.
Deux racines qui pointent le même état
Section intitulée « Deux racines qui pointent le même état »C'est le piège le plus coûteux, parce que la structure de répertoires paraît
correcte. Deux racines dont la configuration de backend désigne le même
fichier, ou le même couple bucket et key, partagent un seul état. Mesuré en
faisant pointer dev et prod sur un etats/commun.tfstate :
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.temoin = "close-redfish"
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.temoin = "close-redfish"Le témoin de prod est celui de dev : la seconde racine a repris l'état de
la première. Et le mal est réciproque, puisqu'un plan dans dev veut
ensuite recréer ce que prod a effacé :
Plan: 1 to add, 0 to change, 0 to destroy.Le contrôle est celui de la section « vérifier », lancé dans chaque racine :
deux backend.config identiques sont un défaut, pas une coïncidence.
Un backend d'équipe sans verrou
Section intitulée « Un backend d'équipe sans verrou »Deux personnes qui appliquent la même racine en même temps ont besoin d'un verrou, sans quoi l'état se corrompt. Le point à connaître sur le backend S3 est que ce verrou n'est pas acquis par défaut.
terraform { backend "s3" { bucket = "terraform-state-prod" key = "reseau/terraform.tfstate" region = "eu-west-1" use_lockfile = true }}La documentation est explicite sur les deux points : use_lockfile contrôle le
verrouillage et vaut false par défaut, et le verrouillage par table
DynamoDB « is deprecated and will be removed in a future minor version ». Un
exemple de backend partagé sans use_lockfile est donc incomplet, et le
détail se traite dans
verrouillage du state.
Le path du backend local ne vaut que pour le workspace default
Section intitulée « Le path du backend local ne vaut que pour le workspace default »Celui-ci surprend, et il se voit très bien sur le montage de cette page. La
racine prod déclare path = "../../etats/prod.tfstate". Après un
terraform workspace new bac-a-sable puis un apply, l'état du nouveau
workspace n'atterrit pas dans etats/ :
find . -name '*.tfstate' -not -path './.terraform/*'./terraform.tfstate.d/bac-a-sable/terraform.tfstateLe fichier déclaré par path sert au workspace default seul, les autres allant
sous terraform.tfstate.d/<nom>/, dans la racine. Un .gitignore calé sur le
seul chemin déclaré laisse donc fuiter un état, et c'est une raison de plus de
ne pas mélanger les deux mécanismes d'isolation.
Dépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Correction |
|---|---|---|
Error: Variables not allowed dans un bloc backend | une variable ou un local dans le bloc | passer en configuration partielle, bloc vide plus -backend-config |
Error: No configuration files | plan ou apply lancé hors d'une racine ; init, lui, réussit dans un répertoire vide | se placer dans envs/<env>/ avant la commande |
Error: Backend initialization required | bloc backend ajouté ou modifié sans init | terraform init -migrate-state pour emporter l'état, -reconfigure pour l'abandonner |
Can't ask approval for state migration | migration demandée avec -input=false | ajouter -migrate-state -force-copy, ou migrer hors du pipeline |
Error: address argument is required | argument requis absent d'un backend partiel | le fournir par -backend-config, aucune invite ne le demandera |
| deux environnements partagent leurs ressources | même path, ou même bucket et key | comparer backend.config des deux racines, ils doivent différer |
un état apparaît dans terraform.tfstate.d/ | un workspace non default dans une racine | ne pas cumuler workspaces et répertoires, ou étendre le .gitignore |
Mettre en pratique
Section intitulée « Mettre en pratique »Le lab deux répertoires, deux backends livre un module partagé et deux
racines à câbler, avec un bloc backend volontairement vide : à vous d'écrire
les fichiers de configuration partielle qui donnent à chaque environnement son
état. Les tests ne lisent pas votre HCL, ils lisent ce que Terraform écrit,
backend.config et modules.json, puis détruisent dev dans une copie du
travail pour vérifier que prod n'a pas bougé. Il se joue hors ligne, avec le
backend local.
À retenir
Section intitulée « À retenir »- Un répertoire sur lequel on lance
initest une racine : un backend, un état, unapplyqui n'agit que sur le sien. - Le code commun vit dans un module appelé par chaque racine, ce qui évite la duplication que la séparation pourrait faire craindre.
- Un bloc
backendn'accepte aucune valeur nommée : mesuré,Error: Variables not allowed, code de retour 1. - La configuration partielle est la réponse : bloc vide identique partout,
arguments fournis par
-backend-config, en fichier ou enKEY=VALUE. - La saisie interactive annoncée par la doc n'a pas prompté en 1.15.4, même avec un TTY : fournissez la valeur.
.terraform/terraform.tfstateporte la configuration retenue : c'est là qu'on vérifie qu'une racine ne pilote pas l'état d'une autre.- Changer de backend impose un
init, avec-migrate-statepour emporter l'état ou-reconfigurepour l'abandonner, et l'ancien fichier local est vidé, pas supprimé. - La seule passerelle entre deux racines, ce sont les outputs déclarés d'un
état distant : le reste rend
Error: Unsupported attribute. - Les workspaces d'un répertoire partagent son backend, donc ses credentials : c'est l'argument officiel, et il ne se corrige pas par la discipline.
- Sur un backend partagé,
use_lockfilevautfalsepar défaut, et le verrou DynamoDB est déprécié.
FAQ : questions fréquentes
Section intitulée « FAQ : questions fréquentes »Les questions ci-dessous portent sur ce qui bloque en pratique : le bloc
backend qui refuse une variable, la migration d'un état vers un backend
partagé, et les deux racines qui finissent par écrire au même endroit.
L'argument est structurel, pas ergonomique
On lit souvent que les workspaces sont risqués parce qu'unterraform workspace select oublié envoie l'apply au mauvais endroit. C'est vrai, mais secondaire.La raison qui compte est dans la documentation :Workspaces are not appropriate for system decomposition or deployments requiring separate credentials and access controls.Parce que « CLI workspaces within a working directory use the same backend ». Un seul backend, donc un seul jeu d'accès.
Mesuré sur 1.15.4
Aprèsterraform workspace new bac-a-sable dans une racine, la configuration de backend est inchangée :{
"path": "../../etats/prod.tfstate",
"workspace_dir": null
}
Elle appartient au répertoire, pas au workspace. Deux répertoires, au contraire, portent deux blocs backend distincts, donc deux cibles et deux jeux de droits.Le message exact
terraform {
backend "local" {
path = "../etats/${var.env}.tfstate"
}
}
Error: Variables not allowed
on main.tf line 8, in terraform:
8: path = "../etats/${var.env}.tfstate"
Variables may not be used here.
L'init sort en 1.La règle et sa raison
« A backend block cannot refer to named values (like input variables, locals, or data source attributes). »Terraform doit savoir où lire l'état avant de pouvoir évaluer quoi que ce soit : les variables n'existent pas encore à ce moment. Ce n'est donc pas une limite arbitraire, c'est l'ordre des opérations.La réponse officielle est la configuration partielle : un bloc vide, et les arguments fournis à l'initialisation.Un bloc vide, identique partout
terraform {
backend "local" {}
}
Première forme : un fichier
# envs/dev/backend-dev.hcl
path = "../../etats/dev.tfstate"
terraform init -backend-config=backend-dev.hcl
Successfully configured the backend "local"! Terraform will automatically
use this backend unless the backend configuration changes.
Seconde forme : des paires sur la ligne de commande
terraform init -backend-config="path=../../etats/prod.tfstate"
Pratique quand la valeur vient d'une variable du runner.La troisième forme annoncée n'a pas prompté
La documentation cite aussi une saisie interactive. Mesuré sur 1.15.4 avecbackend "http" {}, dont address est requis, aucune invite n'apparaît, ni dans un tube ni avec un TTY :Error: address argument is required
En automatisation, fournissez donc la valeur explicitement.Terraform bloque jusqu'à ce que vous choisissiez
Unplan après ajout du bloc, sans réinitialiser :Error: Backend initialization required, please run "terraform init"
Reason: Initial configuration of the requested backend "local"
Changes to backend configurations require reinitialization. This allows
Terraform to set up the new configuration, copy existing state, etc. Please run
"terraform init" with either the "-reconfigure" or "-migrate-state" flags to
use the current configuration.
Les deux options ne font pas la même chose : -migrate-state emporte l'état, -reconfigure l'abandonne.Le piège en pipeline
terraform init -input=false
Error: Can't ask approval for state migration when interactive input is disabled.
Please remove the "-input=false" option and try again.
La commande qui aboutit sans interaction :terraform init -migrate-state -force-copy
Vérification
Mesuré avec unrandom_pet comme témoin : sa valeur est identique avant et après, et plan -detailed-exitcode sort en 0. Aucune ressource n'a été recréée. L'ancien terraform.tfstate n'est pas supprimé, il est vidé à 0 octet, avec un .backup à côté.La trace de l'init
jq '{type: .backend.type, config: .backend.config}' .terraform/terraform.tfstate
{
"type": "local",
"config": {
"path": "../../etats/dev.tfstate",
"workspace_dir": null
}
}
À ne pas confondre avec l'état des ressources : ce fichier ne contient que la configuration du backend.Pourquoi c'est le bon contrôle
Avec une configuration partielle, lepath ne figure dans aucun fichier .tf : il a été passé à l'init. Lire le HCL ne dit donc rien, et c'est cette trace qui répond.C'est aussi le contrôle qui détecte deux racines pointant le même état : lancez-le dans chaque racine, les backend.config doivent différer.La seule passerelle officielle
data "terraform_remote_state" "socle" {
backend = "local"
config = {
path = "../socle/terraform.tfstate"
}
}
output "reseau_consomme" {
value = data.terraform_remote_state.socle.outputs.nom_reseau
}
Le mécanisme est intégré à Terraform, sans provider à installer :- terraform.io/builtin/terraform is built in to Terraform
Le contrat, ce sont les outputs déclarés
Une valeur que l'autre racine n'expose pas reste inaccessible :Error: Unsupported attribute
data.terraform_remote_state.socle.outputs is object with 1 attribute "nom_reseau"
This object does not have an attribute named "inexistant".
Une réserve de sécurité
Lire un état distant demande un accès en lecture à tout cet état, valeurs de ressources comprises. Entre périmètres à droits différents, une source de vérité tierce est souvent plus saine que d'ouvrir l'état de production.Mesuré : deux racines sur un état commun
En faisant pointerdev et prod sur le même etats/commun.tfstate :Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
temoin = "close-redfish"
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
temoin = "close-redfish"
Le témoin de prod est celui de dev : la seconde racine a repris l'état de la première. Le mal est réciproque, un plan dans dev veut ensuite recréer ce que prod a effacé :Plan: 1 to add, 0 to change, 0 to destroy.
Le contrôle
jq '.backend.config' .terraform/terraform.tfstate
Lancé dans chaque racine, il doit rendre des valeurs différentes. Sur un backend S3, cela signifie un key distinct au minimum, et de préférence un bucket et des credentials distincts.Ce que dit la documentation
use_lockfile : « Whether to use a lockfile for locking the state file. Defaults to false. »Et sur l'ancienne méthode : le verrouillage par DynamoDB « is deprecated and will be removed in a future minor version ».L'exemple à jour
terraform {
backend "s3" {
bucket = "terraform-state-prod"
key = "reseau/terraform.tfstate"
region = "eu-west-1"
use_lockfile = true
}
}
Ce bloc n'est pas rejouable hors ligne, il demande un compte et un bucket.Pourquoi cela compte
Deux personnes qui appliquent la même racine au même moment, sans verrou, corrompent l'état. Un exemple de backend partagé par une équipe qui ne mentionne pas le verrouillage est donc incomplet, même s'il fonctionne à une seule personne.Pour aller plus loin
Section intitulée « Pour aller plus loin »- Organiser un dépôt Terraform : le socle de fichiers et le
.gitignorequi rendent cette structure tenable. - Monorepo vs un repo par stack : la question posée juste au-dessus, un seul dépôt ou un dépôt par périmètre.
- Quiz Organiser les environnements Terraform : un contrôle des acquis sur les stratégies d'isolation de la section.
- Le bloc backend : la référence officielle : configuration partielle, valeurs nommées interdites et recommandation sur les credentials.
- Le backend S3 :
use_lockfile, préfixe de workspace et dépréciation du verrou DynamoDB. - Les workspaces CLI : les cas d'usage prévus, et la limite du backend partagé.