| name | devops-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". Also triggers on "Terraform module", "terraform state", "provision with Terraform". |
Guide Terraform
1. Workflow opérationnel (étapes numérotées)
- Initialiser — configurer le backend et télécharger les providers.
terraform init -upgrade
terraform init -reconfigure
- Valider — vérifier la syntaxe avant de planifier.
terraform validate
terraform fmt -recursive
- Planifier — toujours sauvegarder le plan pour un apply déterministe.
terraform plan -out=tfplan.bin
terraform show -json tfplan.bin | jq '.resource_changes[] | select(.change.actions != ["no-op"])'
- Appliquer — uniquement depuis le plan sauvegardé.
terraform apply tfplan.bin
- Vérifier le drift — détecter les divergences entre state et réalité.
terraform plan -refresh-only
terraform apply -refresh-only
- Détruire proprement — cibler d'abord, jamais en masse sans review.
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.tf
variable "app_name" { type = string }
variable "environment" {
type = string
validation {
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.tf
locals {
common_tags = {
Environment = var.environment
ManagedBy = "terraform"
Application = var.app_name
}
}
resource "azurerm_service_plan" "this" {
name = "plan-${var.app_name}-${var.environment}"
location = var.location
resource_group_name = var.resource_group_name
os_type = "Linux"
sku_name = var.sku
tags = local.common_tags
}
resource "azurerm_linux_web_app" "this" {
name = "app-${var.app_name}-${var.environment}"
location = var.location
resource_group_name = var.resource_group_name
service_plan_id = azurerm_service_plan.this.id
tags = local.common_tags
site_config {
always_on = var.environment == "production"
application_stack { dotnet_version = "8.0" }
}
app_settings = var.app_settings
}
# modules/app-service/outputs.tf
output "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.location
resource_group_name = azurerm_resource_group.main.name
}
4. State management
Backend distant avec locking (Azure)
# environments/production/backend.tf
terraform {
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 = true
dynamodb_table = "terraform-state-lock"
}
}
Commandes de gestion du state
terraform state list
terraform state show azurerm_linux_web_app.this
terraform state mv module.old.res module.new.res
terraform state rm azurerm_resource_group.legacy
terraform import azurerm_resource_group.legacy /subscriptions/.../rg-name
5. Versions et contraintes providers
# shared/versions.tf — à copier dans chaque environnement
terraform {
required_version = ">= 1.7, < 2.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.110"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
Verrouiller le .terraform.lock.hcl dans Git — il garantit la reproductibilité des builds.
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 critique
resource "azurerm_postgresql_flexible_server" "main" {
# ...
lifecycle {
prevent_destroy = true
}
}
7. Bonnes pratiques 2026
8. Commandes de référence rapide
terraform init -upgrade && terraform fmt -recursive && terraform validate
terraform plan -var-file=environments/prod.tfvars -out=tfplan.bin
terraform show tfplan.bin
terraform show -json tfplan.bin | jq '.'
terraform state list
terraform state show <resource_address>
terraform state mv <src> <dst>
terraform state rm <resource_address>
terraform import <resource_address> <cloud_id>
terraform plan -refresh-only
Communication Rules — MANDATORY
- Ultra-concise. No filler, no preamble, no pleasantries.
- Never say "happy to help", "sure!", "great question", "let me", or similar.
- Tool first, talk second. Act before explaining.
- Result first. Lead with outcome, not process.
- Stop when done. No summary, no recap, no trailing commentary.
- No politeness wrappers. Direct and blunt.
- Minimum words. If one word works, do not use ten.
- No unsolicited explanations.
- No emoji unless asked.