
Six mois après l’avoir écrit, votre configuration Terraform devient
illisible : res1, my_vm_2, des blocs mélangés dans main.tf,
des noms de variables incohérents d’un module à l’autre. La review est
douloureuse, les diffs incompréhensibles.
Analogie : votre codebase Terraform est comme une maison partagée. Sans règles communes de nettoyage et d’organisation, elle devient vite un chaos où personne ne retrouve ses affaires. Les conventions HashiCorp jouent ce rôle : ce sont les règles d’hygiène collective qui rendent le code lisible pour n’importe quel membre de l’équipe qui le rejoindra.
Les conventions HashiCorp et les pratiques adoptées par les équipes
DevOps codifient les règles qui évitent ce scénario. Elles couvrent
le nommage des ressources, la structure des fichiers .tf, l’ordre des
blocs, et le formatage automatique avec terraform fmt. Ces règles
s’appliquent quel que soit le provider utilisé — libvirt, AWS, Kubernetes.
Les adopter dès le premier projet évite les refactors douloureux et rend
le code immédiatement compréhensible par n’importe quel membre de l’équipe.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Structure des fichiers :
main.tf,variables.tf,outputs.tf,versions.tf— ce qui va où - Nommage : conventions
snake_case, préfixes de ressources, noms locaux - Formatage :
terraform fmtet son usage en CI - Ordre des blocs dans une ressource :
lifecycle,depends_on,count/for_eachen tête - Commentaires : quand (et comment) documenter
Structure des fichiers
Section intitulée « Structure des fichiers »Fichiers minimaux par module
Section intitulée « Fichiers minimaux par module »module/├── main.tf # Ressources principales├── variables.tf # Déclarations de variables├── outputs.tf # Déclarations d'outputs├── versions.tf # required_providers + required_version└── README.md # Documentation du modulePour les modules complexes, découper main.tf par domaine :
module/├── versions.tf├── variables.tf├── outputs.tf├── network.tf # Ressources réseau├── compute.tf # VMs, instances├── storage.tf # Volumes, buckets└── iam.tf # Rôles, permissionsÀ éviter
Section intitulée « À éviter »terraform.tfvarsdans les modules réutilisables (c’est au projet appelant de fournir les valeurs)- multiplier les fichiers trop tôt dans un module minuscule sans gain réel de lisibilité
locals.tf et providers.tf sont des conventions acceptables dès qu’elles
clarifient la structure. Sur un petit projet, main.tf peut suffire ; sur un
projet plus large, séparer locals, providers ou les domaines fonctionnels
est souvent plus lisible.
Nommage des ressources
Section intitulée « Nommage des ressources »Règle de base
Section intitulée « Règle de base »resource "aws_instance" "web_server" {}# TYPE NOM- Type : imposé par le provider
- Nom : snake_case, descriptif, sans répéter le type
# ✅ Bonresource "aws_instance" "web" {}resource "libvirt_volume" "base_disk" {}resource "aws_security_group" "allow_http" {}
# ❌ Mauvaisresource "aws_instance" "aws_instance" {} # répétition inutileresource "libvirt_volume" "LibvirtVolume" {} # PascalCaseresource "aws_security_group" "sg1" {} # non descriptifRessource unique dans un module
Section intitulée « Ressource unique dans un module »Si le module ne crée qu’une seule ressource d’un type, appeler le nom this :
resource "aws_vpc" "this" { cidr_block = var.cidr_block}Ressources similaires en groupe
Section intitulée « Ressources similaires en groupe »Pour un groupe logique fonctionnel, utiliser des noms descriptifs :
resource "aws_subnet" "public" {}resource "aws_subnet" "private" {}resource "aws_subnet" "database" {}Nommage des variables
Section intitulée « Nommage des variables »variable "instance_type" {} # ✅ snake_casevariable "instanceType" {} # ❌ camelCasevariable "InstanceType" {} # ❌ PascalCase- Noms clairs et descriptifs — pas d’abréviations cryptiques
- Toujours avec
descriptionettype
# ✅ Completvariable "vm_memory_mib" { type = number description = "Mémoire allouée à la VM en MiB." default = 512
validation { condition = var.vm_memory_mib >= 256 error_message = "La mémoire doit être au moins 256 MiB." }}
# ❌ Incompletvariable "mem" { default = 512}Nommage des outputs
Section intitulée « Nommage des outputs »Même règle que les variables — snake_case, descriptif.
Pour les modules réutilisables, préfixer par le type de ressource quand c’est ambigu :
output "instance_id" {} # ✅output "instance_public_ip" {} # ✅output "id" {} # ❌ ambigu dans un moduleFormatage HCL
Section intitulée « Formatage HCL »terraform fmt
Section intitulée « terraform fmt »Toujours utiliser terraform fmt avant de commiter. Cela normalise
automatiquement l’indentation et l’alignement des =.
# Formater récursivementterraform fmt -recursive
# Vérifier sans appliquer (pour CI)terraform fmt -check -recursiveRègles de formatage clés
Section intitulée « Règles de formatage clés »# Alignement des = dans un bloc (appliqué par terraform fmt)resource "libvirt_domain" "vm" { name = "web" memory = 512 memory_unit = "MiB" vcpu = 1}
# Pas d'espace dans les accolades de maps inlineos = { type = "hvm", type_arch = "x86_64" } # ✅os = {type = "hvm", type_arch = "x86_64"} # ❌
# Listes multi-lignes : virgule finaledefault = [ "dev", "staging", "prod", # ← virgule finale : diffs git plus propres]Ordre des blocs dans une ressource
Section intitulée « Ordre des blocs dans une ressource »Ordre recommandé par HashiCorp :
resource "type" "nom" { # 1. Meta-arguments (count, for_each, provider) for_each = var.vms
# 2. Attributs requis / identifiants name = "vm-${each.key}"
# 3. Attributs optionnels memory = each.value.memory memory_unit = "MiB"
# 4. Blocs imbriqués devices = { ... }
# 5. lifecycle (toujours dernier) lifecycle { ignore_changes = [memory] }}Ordre des blocs dans un fichier
Section intitulée « Ordre des blocs dans un fichier »Ordre conseillé dans main.tf :
datasources (lecture de l’infrastructure existante)locals(valeurs dérivées)resource(de la plus dépendante à la moins dépendante)
Variables : valeurs sensibles
Section intitulée « Variables : valeurs sensibles »variable "db_password" { type = string sensitive = true # ← masqué dans les outputs et logs}Ne jamais hardcoder un mot de passe ou une clé dans le code HCL.
Utiliser des variables sensitive = true et les injecter via l’environnement :
export TF_VAR_db_password="mon_mot_de_passe"terraform applyCommentaires
Section intitulée « Commentaires »# Commentaire sur une seule ligne — préféré en HCL
/* Commentaire multi-lignes pour des blocs entiers*/Commenter le pourquoi, pas le quoi :
# ❌ Inutile# Crée un réseau NATresource "libvirt_network" "lab" { ... }
# ✅ Utile# Réseau isolé du trafic externe — les VMs sortent via NAT# mais ne sont pas accessibles depuis le LAN de lab.resource "libvirt_network" "lab" { ... }Modules locaux
Section intitulée « Modules locaux »Structure
Section intitulée « Structure »projet/├── main.tf├── variables.tf├── outputs.tf├── versions.tf└── modules/ └── vm/ ├── main.tf ├── variables.tf └── outputs.tfmodule "web" { source = "./modules/vm"
vm_name = "web" memory = 512}Versionnement des modules publics
Section intitulée « Versionnement des modules publics »module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "~> 5.0" # ← toujours fixer la version}Checklist projet
Section intitulée « Checklist projet »✅ versions.tf avec required_version et required_providers✅ terraform fmt -check passe en CI✅ terraform validate passe✅ Pas de credentials dans le code (sensitive = true ou env vars)✅ README.md avec les inputs/outputs documentés✅ Variables avec description et type✅ Un fichier par domaine fonctionnel (network.tf, compute.tf...)✅ Nommage snake_case cohérentÀ retenir
Section intitulée « À retenir »terraform fmt -recursive: formater avant chaque commit- snake_case partout — ressources, variables, outputs
- Nom
thispour la ressource unique d’un module - Variables avec
type,description, optionnellementvalidation sensitive = truepour les secrets — jamais de valeurs en dur dans le code- Structure fichiers :
versions.tf+variables.tf+outputs.tf+ domaines fonctionnels