Tu sais créer des ressources, gérer le state, utiliser des variables. Mais ton projet grossit et ton main.tf fait 500 lignes. C’est le moment de découper ton code en modules — des blocs réutilisables qui transforment ton Terraform artisanal en infrastructure industrielle.
Pourquoi les modules changent tout
Sans modules, tout vit dans un seul répertoire : le root module. Pour 10 ressources, ça passe. À 50, c’est ingérable. Les modules résolvent ça en apportant :
- L’encapsulation — un réseau, un cluster, une app = un module autonome
- La réutilisabilité — même module pour dev, staging et prod
- L’abstraction — tes collègues consomment le module sans connaître les détails
- Le versioning — chaque composant évolue indépendamment
💡 Pense “fonction” : un module prend des inputs (variables), crée des ressources, et expose des outputs. Exactement comme une fonction dans n’importe quel langage.
Anatomie d’un module
Un module Terraform, c’est simplement un répertoire avec des fichiers .tf. La convention standard :
modules/
└── web-app/
├── main.tf # Ressources principales
├── variables.tf # Variables d'entrée
├── outputs.tf # Valeurs de sortie
├── versions.tf # Contraintes providers
└── README.md # Documentation
Les variables d’entrée
C’est le contrat du module — ce que le consommateur doit (ou peut) fournir :
variable "app_name" {
description = "Nom de l'application"
type = string
validation {
condition = can(regex("^[a-z][a-z0-9-]{2,24}$", var.app_name))
error_message = "Minuscules, 3-25 chars, alphanumérique avec tirets."
}
}
variable "environment" {
description = "Environnement cible"
type = string
default = "dev"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Doit être dev, staging ou prod."
}
}
variable "instance_count" {
description = "Nombre d'instances à provisionner"
type = number
default = 1
}
🔥 Les validations sont ton filet de sécurité. Elles attrapent les erreurs au plan, pas après un déploiement raté en prod.
Les outputs
Ce que le module expose aux appelants — les données nécessaires pour câbler les modules entre eux :
output "instance_ids" {
description = "IDs des instances créées"
value = openstack_compute_instance_v2.app[*].id
}
output "instance_ips" {
description = "Adresses IP des instances"
value = openstack_compute_instance_v2.app[*].access_ip_v4
}
Appeler et composer des modules
Depuis ton root module, tu appelles un module avec le bloc module :
module "api" {
source = "./modules/web-app"
app_name = "api"
environment = "prod"
instance_count = 3
}
module "backoffice" {
source = "./modules/web-app"
app_name = "backoffice"
environment = "prod"
instance_count = 1
}
# Câbler les outputs
output "api_ips" {
value = module.api.instance_ips
}
La vraie puissance arrive avec la composition : un module app-stack qui orchestre un module network, un module compute et un module database. Les outputs du réseau deviennent les inputs du compute. C’est comme ça que les grandes organisations structurent leur infra.
⚠️ Ne sur-abstrais pas trop tôt. Commence par dupliquer, identifie les patterns récurrents, puis extrais en module. Un module prématuré est pire que du code dupliqué.
Sources distantes et Registry
Les modules locaux (source = "./modules/xxx") sont parfaits pour démarrer. Quand plusieurs équipes partagent les mêmes modules, passe à des sources distantes :
# Depuis un repo Git (bonne pratique : toujours pinner la version)
module "network" {
source = "git::https://gitlab.com/mon-org/tf-modules.git//network?ref=v2.1.0"
}
# Depuis le Terraform Registry (modules publics communautaires)
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "mon-vpc"
cidr = "10.0.0.0/16"
azs = ["eu-west-1a", "eu-west-1b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
}
Versioning sémantique
Applique le semver sur tes modules internes :
- MAJOR (v2.0.0) — breaking change : variable renommée, output supprimé
- MINOR (v1.1.0) — nouvelle feature rétrocompatible
- PATCH (v1.0.1) — bug fix
🎯 Règle d’or en prod : toujours pinner une version. Jamais de ref=main ou version = ">= 1.0". Un terraform init un lundi matin qui tire une version non testée, c’est la garantie d’un incident.
Cas entreprise : structurer un vrai projet
Voici comment une équipe de 5 devs organise un projet Terraform mature :
infra/
├── modules/ # Modules internes réutilisables
│ ├── network/
│ ├── compute/
│ └── database/
├── environments/ # Un dossier par env
│ ├── dev/
│ │ ├── main.tf
│ │ └── terraform.tfvars
│ ├── staging/
│ └── prod/
└── global/ # Ressources partagées (DNS, IAM)
Mono-repo vs multi-repo ? Commence en mono-repo. C’est plus simple pour le refactoring et la CI/CD. Migre vers du multi-repo uniquement quand plusieurs équipes se marchent dessus ou quand le blast radius devient critique.
Les conventions qui sauvent la vie à 6 mois :
- Nommage cloud :
<projet>-<env>-<composant>→monapp-prod-api-sg - Variables : snake_case, description obligatoire, pas de défaut qui correspond à la prod
- Fichiers : un par responsabilité (
main.tf,variables.tf,outputs.tf), pas un par ressource - Code review :
terraform planjoint à chaque PR,terraform fmten pre-commit
💡 Automatise le formatting avec pre-commit :
# .pre-commit-config.yaml
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.96.0
hooks:
- id: terraform_fmt
- id: terraform_validate
- id: terraform_tflint
Les pièges classiques
Module trop générique. Tu veux un module qui fait tout — réseau, compute, storage, monitoring. Résultat : 40 variables, des conditions partout, impossible à maintenir. Mieux vaut 3 modules simples qu’un monstre configurable.
Oublier les outputs. Un module sans outputs est une boîte noire. Les modules en aval ne peuvent pas se câbler. Expose systématiquement les IDs et les attributs réseau.
Modules circulaires. Le module A dépend de B qui dépend de A. Terraform ne sait pas résoudre ça. Solution : extrais la dépendance commune dans un module C.
Pas de moved block après un refactoring. Tu renommes un module ou une ressource et Terraform veut détruire/recréer. Utilise le bloc moved pour migrer proprement :
moved {
from = module.old_name
to = module.new_name
}
⚠️ Le Registry n’est pas un gage de qualité. Avant d’utiliser un module public, vérifie : dernière activité, nombre de contributeurs, présence de tests, et lis le code. Un module abandonné avec des failles, c’est de la dette technique importée.
Ce qu’on retient
🎯 Un module Terraform = un répertoire avec main.tf, variables.tf, outputs.tf. C’est une fonction pour ton infrastructure.
Les essentiels :
- Encapsule par domaine — réseau, compute, data, pas par ressource individuelle
- Valide les inputs — les blocs
validationévitent les erreurs en prod - Expose les outputs — c’est le contrat de ton module avec le monde extérieur
- Pine les versions — en prod, toujours une version exacte ou contrainte serrée
- Commence simple — duplique d’abord, module ensuite quand le pattern est clair
- Documente —
terraform-docsgénère la doc automatiquement depuis ton code
Tu maîtrises maintenant la structuration de projets Terraform. Dans le prochain chapitre, on passe à FastAPI pour construire des APIs modernes en Python — le pont entre ton infra et tes applications. 🚀
🖥️ Pratique sur ton propre serveur
Pour suivre Terraform en conditions réelles, tu as besoin d'un VPS. DigitalOcean offre 200$ de crédit gratuit pour démarrer.
Contenu réservé aux abonnés
Ce chapitre fait partie de la formation complète. Abonne-toi pour débloquer tous les contenus.
Débloquer pour 29 CHF/moisLe chapitre 1 de chaque formation est gratuit.
Série pas encore débloquée
Termine la série prérequise d'abord pour accéder à ce contenu.
Aller à la série prérequiseSur cette page
Articles liés
Init, Plan, Apply : le workflow Terraform
Maîtrise le workflow Terraform complet : init, plan, apply, destroy. Puis découvre les resources, data sources, le state et la structure de projet.
Pourquoi l'IaC ? Introduction à Terraform
Découvre l'Infrastructure as Code, installe Terraform, apprends la syntaxe HCL, les providers et réalise ton premier déploiement sur Hetzner.
Variables et types de données
Maîtrise les variables d'entrée Terraform, les différents types (string, number, list, map, object), l'assignation de valeurs et les outputs.