Skill v1.0.0
currentAutomated scan100/100version: "1.0.0" name: terraform-guide description: Guide Terraform pour l'Infrastructure as Code, modules, state management, workspaces et bonnes pratiques. À utiliser quand l'utilisateur écrit du Terraform, conçoit des modules ou gère de l'infrastructure cloud. Se déclenche aussi avec "terraform", "infrastructure as code", "terraform plan", "terraform apply", "module terraform", "tfstate", "HCL".
Guide Terraform
1. Workflow opérationnel (étapes numérotées)
- Initialiser, configurer le backend et télécharger les providers.
``bash terraform init -upgrade # première fois ou mise à jour providers terraform init -reconfigure # changer de backend sans migrer l'état ``
- Valider, vérifier la syntaxe avant de planifier.
``bash terraform validate terraform fmt -recursive # formater tout le répertoire ``
- Planifier, toujours sauvegarder le plan pour un apply déterministe.
``bash terraform plan -out=tfplan.bin terraform show -json tfplan.bin | jq '.resource_changes[] | select(.change.actions != ["no-op"])' ``
- Appliquer, uniquement depuis le plan sauvegardé.
``bash terraform apply tfplan.bin ``
- Vérifier le drift, détecter les divergences entre state et réalité.
``bash terraform plan -refresh-only # voir ce qui a changé hors Terraform terraform apply -refresh-only # re-synchroniser le state sans modifier les ressources ``
- Détruire proprement, cibler d'abord, jamais en masse sans review.
``bash terraform destroy -target=module.networking.azurerm_subnet.main ``
2. Structure de projet recommandée
infrastructure/├── environments/│ ├── dev/│ │ ├── main.tf # appels aux modules│ │ ├── variables.tf│ │ ├── terraform.tfvars│ │ └── backend.tf│ ├── staging/│ └── production/├── modules/│ ├── networking/ # un module = une responsabilité│ │ ├── main.tf│ │ ├── variables.tf│ │ └── outputs.tf│ ├── database/│ └── app-service/└── shared/└── versions.tf # contraintes de version providers
Critère de découpage : un module par "domaine fonctionnel" (réseau, stockage, compute). Éviter les modules à 1 ressource (sur-découpage) et les modules "tout en un" (couplage fort).
3. Module réutilisable, exemple complet
# modules/app-service/variables.tfvariable "app_name" { type = string }variable "environment" {type = stringvalidation {condition = contains(["dev", "staging", "production"], var.environment)error_message = "Valeurs acceptées : dev, staging, production."}}variable "sku" { type = string; default = "B1" }variable "location" { type = string }variable "resource_group_name"{ type = string }variable "app_settings" { type = map(string); default = {} }# modules/app-service/main.tflocals {common_tags = {Environment = var.environmentManagedBy = "terraform"Application = var.app_name}}resource "azurerm_service_plan" "this" {name = "plan-${var.app_name}-${var.environment}"location = var.locationresource_group_name = var.resource_group_nameos_type = "Linux"sku_name = var.skutags = local.common_tags}resource "azurerm_linux_web_app" "this" {name = "app-${var.app_name}-${var.environment}"location = var.locationresource_group_name = var.resource_group_nameservice_plan_id = azurerm_service_plan.this.idtags = local.common_tagssite_config {always_on = var.environment == "production"application_stack { dotnet_version = "8.0" }}app_settings = var.app_settings}# modules/app-service/outputs.tfoutput "app_url" { value = "https://${azurerm_linux_web_app.this.default_hostname}" }output "app_id" { value = azurerm_linux_web_app.this.id }output "plan_id" { value = azurerm_service_plan.this.id }
Appel depuis un environnement :
module "api" {source = "../../modules/app-service"app_name = "myapi"environment = "production"sku = "P1v3"location = azurerm_resource_group.main.locationresource_group_name = azurerm_resource_group.main.name}
4. State management
Backend distant avec locking (Azure)
# environments/production/backend.tfterraform {backend "azurerm" {resource_group_name = "rg-terraform-state"storage_account_name = "stterraformstprod"container_name = "tfstate"key = "myapp.production.tfstate"use_azuread_auth = true # évite les clés de compte (2025+)}}
Backend S3 + DynamoDB (AWS)
terraform {backend "s3" {bucket = "myco-tfstate-prod"key = "myapp/production/terraform.tfstate"region = "eu-west-1"encrypt = trueuse_lockfile = true # verrouillage natif S3 (Terraform 1.10+)}}
dynamodb_tableest déprécié depuis Terraform 1.11 : le verrouillage passe désormais par unfichier de lock dans le bucket lui-même. Une table DynamoDB de moins à provisionner et à payer.
Commandes de gestion du state
terraform state list # toutes les ressourcesterraform state show azurerm_linux_web_app.this # détail d'une ressourceterraform state mv module.old.res module.new.res # renommer sans recréerterraform state rm azurerm_resource_group.legacy # retirer du state sans détruireterraform import azurerm_resource_group.legacy /subscriptions/.../rg-name
5. Versions et contraintes providers
# shared/versions.tf, à copier dans chaque environnementterraform {required_version = ">= 1.7, < 2.0"required_providers {azurerm = {source = "hashicorp/azurerm"version = "~> 5.1"}random = {source = "hashicorp/random"version = "~> 3.6"}}}
Verrouiller le .terraform.lock.hcl dans Git, il garantit la reproductibilité des builds.
Migration 3.x → 5.x : azurerm a passé deux majeures depuis la 3. La 4.0 rendsubscription_idobligatoire dans le bloc provider et remplaceskip_provider_registrationparresource_provider_registrations. Ne pas sauter ces deux marches sans lire les guides d'upgrade.
6. Garde-fous et pièges fréquents
| Piège | Symptôme | Remède | |
|---|---|---|---|
| State en local / commité dans Git | Conflits d'équipe, secrets exposés | Backend distant + .gitignore sur *.tfstate* | |
terraform apply sans plan sauvegardé | Apply incohérent si state a changé entre-temps | Toujours -out=tfplan.bin + apply tfplan.bin | |
| Hard-coding de secrets dans HCL | Secrets dans Git | var + Key Vault / Secrets Manager ou sensitive = true | |
| Modules trop fins (1 ressource) | Overhead de composition, appels imbriqués | Regrouper par domaine fonctionnel | |
terraform destroy sans -target | Destruction de toute l'infra | Toujours cibler ou utiliser des workspaces isolés | |
| Drift ignoré | État réel diverge du plan | plan -refresh-only en CI hebdomadaire | |
Pas de lifecycle.prevent_destroy sur ressources critiques | Suppression accidentelle BDD/stockage | Ajouter prevent_destroy = true sur les ressources stateful |
# Protéger une base de données critiqueresource "azurerm_postgresql_flexible_server" "main" {# ...lifecycle {prevent_destroy = true}}
7. Bonnes pratiques 2026
- Épingler les versions providers via
.terraform.lock.hcl(commité dans Git). - Secrets : ne jamais les mettre dans
.tfvars; utilisersensitive = true+ injection CI (env varsTF_VAR_*). - Tagging systématique : module
localsaveccommon_tagshérité par toutes les ressources. - Outputs sensibles : marquer
sensitive = truepour éviter les logs en clair. - CI/CD :
terraform planen PR (commentaire automatique),terraform applyuniquement sur merge main. - Workspaces : réservés aux environnements éphémères (feature branches), ne pas les utiliser pour prod/staging (préférer des dossiers séparés avec leur propre state).
- Tflint + Checkov : linting et scan de sécurité avant le plan.
``bash tflint --recursive checkov -d . --framework terraform ``
8. Commandes de référence rapide
# Init & formatterraform init -upgrade && terraform fmt -recursive && terraform validate# Plan sécuriséterraform plan -var-file=environments/prod.tfvars -out=tfplan.bin# Inspection du planterraform show tfplan.bin # lisible humainterraform show -json tfplan.bin | jq '.' # JSON pour scripts# State opsterraform state listterraform state show <resource_address>terraform state mv <src> <dst>terraform state rm <resource_address># Import ressource existanteterraform import <resource_address> <cloud_id># Driftterraform plan -refresh-only