🔁 DevOps

devops-terraform-guide

Guide Terraform pour l'Infrastructure as Code — modules, state management, workspaces et bonnes pratiques.

⚡ Installation & lancement en 1 commande

Copiez-collez dans votre terminal : le skill s'installe dans ~/.claude/skills et Claude Code se lance directement dessus.

macOS / Linux
curl -fsSL https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.sh | sh -s -- devops-terraform-guide --launch
Windows (PowerShell)
iex "& { $(iwr -useb https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.ps1) } devops-terraform-guide -Launch"

🚀 Déjà installé ?

claude "/devops-terraform-guide"

Ou tapez /devops-terraform-guide dans une session Claude Code, ou décrivez simplement votre besoin — le skill se déclenche automatiquement via le skill-router.

🔑 Déclencheurs automatiques

Le skill s'active automatiquement quand votre demande contient :

terraforminfrastructure as codeterraform planterraform applymodule terraformtfstateHCL

📦 Installation manuelle

git clone https://github.com/khalilbenaz/claude-skills-collection.git cp -r claude-skills-collection/skills/devops-terraform-guide ~/.claude/skills/

Payload du plugin : skills/devops-terraform-guide · source éditable : devops-skills/terraform-guide

📖 Manuel

Guide Terraform

1. Workflow opérationnel (étapes numérotées)

  1. 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 ```

  1. Valider — vérifier la syntaxe avant de planifier.

```bash terraform validate terraform fmt -recursive # formater tout le répertoire ```

  1. 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"])' ```

  1. Appliquer — uniquement depuis le plan sauvegardé.

```bash terraform apply tfplan.bin ```

  1. 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 ```

  1. 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.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                              # toutes les ressources
terraform state show azurerm_linux_web_app.this   # détail d'une ressource
terraform state mv  module.old.res module.new.res # renommer sans recréer
terraform state rm  azurerm_resource_group.legacy # retirer du state sans détruire
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ègeSymptômeRemède
State en local / commité dans GitConflits d'équipe, secrets exposésBackend distant + .gitignore sur *.tfstate*
terraform apply sans plan sauvegardéApply incohérent si state a changé entre-tempsToujours -out=tfplan.bin + apply tfplan.bin
Hard-coding de secrets dans HCLSecrets dans Gitvar + Key Vault / Secrets Manager ou sensitive = true
Modules trop fins (1 ressource)Overhead de composition, appels imbriquésRegrouper par domaine fonctionnel
terraform destroy sans -targetDestruction de toute l'infraToujours cibler ou utiliser des workspaces isolés
Drift ignoréÉtat réel diverge du planplan -refresh-only en CI hebdomadaire
Pas de lifecycle.prevent_destroy sur ressources critiquesSuppression accidentelle BDD/stockageAjouter 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

```bash tflint --recursive checkov -d . --framework terraform ```


8. Commandes de référence rapide

# Init & format
terraform init -upgrade && terraform fmt -recursive && terraform validate

# Plan sécurisé
terraform plan -var-file=environments/prod.tfvars -out=tfplan.bin

# Inspection du plan
terraform show tfplan.bin                   # lisible humain
terraform show -json tfplan.bin | jq '.'   # JSON pour scripts

# State ops
terraform state list
terraform state show <resource_address>
terraform state mv   <src> <dst>
terraform state rm   <resource_address>

# Import ressource existante
terraform import <resource_address> <cloud_id>

# Drift
terraform plan -refresh-only