7 septembre 2026
Ma découverte de l'infrastructure as code, avec Terraform
J'avais déjà de l'expérience avec Docker et la conteneurisation, un vrai atout dans mon profil. En discutant de l'hébergement de ce site avec un collègue, il m'a conseillé d'aller plus loin avec Terraform : c'est lui qui m'a fait découvrir l'infrastructure as code.
Le déclic
J'avais déjà une base solide en infrastructure grâce à Docker et à la conteneurisation, mais je ne connaissais rien à l'infrastructure as code. En discutant de l'hébergement de ce site avec un collègue, il m'a suggéré Terraform : décrire l'infrastructure dans des fichiers versionnés plutôt que de la construire à la main, pour que la configuration devienne un vrai artefact du projet, au même titre que le code de l'application.
Ça m'a tout de suite parlé comme un prolongement naturel de ce que je savais déjà faire avec Docker : là où un Dockerfile décrit un conteneur, un fichier Terraform décrit une infrastructure entière.
La logique derrière plan et apply
Terraform réconcilie en permanence trois choses : les fichiers .tf (ce que je veux), le fichier d'état terraform.tfstate (ce que Terraform pense qui existe), et la réalité chez AWS ou Cloudflare (ce qui existe vraiment).
terraform plancompare les trois et affiche le différentiel sans rien modifier.terraform applyexécute ce différentiel, puis met à jour son fichier d'état.
Toute la mécanique du reste du projet découle de cette boucle.
1. Le provider AWS
Tout fichier Terraform commence par déclarer quels plugins (providers) il utilise, et avec quelle version.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "eu-west-1"
}Je fige la version pour éviter qu'une mise à jour du provider ne change silencieusement le comportement d'une ressource entre deux apply. Le region du provider devient ensuite la région par défaut de tout ce qui suit.
2. Le bucket S3
resource "aws_s3_bucket" "website" {
bucket = "baptistev-portfolio"
}
resource "aws_s3_bucket_public_access_block" "website" {
bucket = aws_s3_bucket.website.id
block_public_acls = false
block_public_policy = false
ignore_public_acls = false
restrict_public_buckets = false
}
resource "aws_s3_bucket_website_configuration" "website" {
bucket = aws_s3_bucket.website.id
index_document { suffix = "index.html" }
error_document { key = "404.html" }
}Il faut ici trois ressources pour un seul bucket, parce qu'un bucket S3 bloque tout accès public par défaut, volontairement. Un site statique a besoin de l'inverse, donc lever ce blocage et activer le mode hébergement de site sont deux actes explicites et séparés, pas une case à cocher unique.
3. Le domaine bloqué, et la bascule vers Cloudflare
En essayant d'acheter baptistev.dev via Route 53, AWS a refusé l'achat à cause de sa politique anti-fraude sur les nouveaux comptes. J'ai basculé vers Cloudflare comme registrar, ce qui verrouille aussi le domaine sur les serveurs DNS de Cloudflare : impossible de les déléguer ailleurs. Cloudflare est donc devenu le fournisseur DNS du projet, pas seulement le registrar.
data "cloudflare_zone" "website" {
name = "baptistev.dev"
}J'utilise ici un data et non un resource, parce que la zone existe déjà, créée automatiquement par Cloudflare à l'achat du domaine. Un resource dirait à Terraform de créer une zone qui existe déjà. data se contente de la lire, pour pouvoir y référencer des enregistrements ensuite.
4. Le certificat HTTPS, obligatoirement en us-east-1
provider "aws" {
alias = "us_east_1"
region = "us-east-1"
}
resource "aws_acm_certificate" "website" {
provider = aws.us_east_1
domain_name = "baptistev.dev"
validation_method = "DNS"
}
resource "cloudflare_record" "cert_validation" {
for_each = {
for dvo in aws_acm_certificate.website.domain_validation_options :
dvo.domain_name => {
name = dvo.resource_record_name
type = dvo.resource_record_type
value = dvo.resource_record_value
}
}
zone_id = data.cloudflare_zone.website.id
name = each.value.name
type = each.value.type
content = each.value.value
}
resource "aws_acm_certificate_validation" "website" {
provider = aws.us_east_1
certificate_arn = aws_acm_certificate.website.arn
}J'ai ajouté un second provider parce que les certificats ACM utilisables par CloudFront doivent obligatoirement être créés dans us-east-1, quelle que soit la région du reste de l'infrastructure. Le bloc provider avec un alias permet à une seule ressource de cibler cette région sans changer la région par défaut du projet.
Le for_each sert à transformer une liste dynamique en autant d'enregistrements Cloudflare, sans écrire un bloc par enregistrement à la main : ACM ne connaît les enregistrements DNS à créer pour prouver la propriété du domaine qu'une fois le certificat demandé.
5. CloudFront devant S3
resource "aws_cloudfront_distribution" "website" {
enabled = true
aliases = ["baptistev.dev"]
origin {
domain_name = aws_s3_bucket_website_configuration.website.website_endpoint
origin_id = "s3-website"
custom_origin_config {
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1.2"]
http_port = 80
https_port = 443
}
}
default_cache_behavior {
target_origin_id = "s3-website"
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
forwarded_values {
query_string = false
cookies { forward = "none" }
}
}
viewer_certificate {
acm_certificate_arn = aws_acm_certificate_validation.website.certificate_arn
ssl_support_method = "sni-only"
}
}J'ai mis CloudFront devant, parce qu'un point d'hébergement de site S3 ne sert que du HTTP brut, jamais de HTTPS. L'origine parle donc à S3 en HTTP simple (origin_protocol_policy = "http-only"), mais viewer_protocol_policy = "redirect-to-https" force chaque visiteur vers HTTPS, CloudFront assurant la terminaison TLS avec le certificat créé à l'étape précédente.
6. Le dernier fil : pointer le domaine
resource "cloudflare_record" "root" {
zone_id = data.cloudflare_zone.website.id
name = "baptistev.dev"
type = "CNAME"
content = aws_cloudfront_distribution.website.domain_name
}Cette dernière ressource referme la chaîne : navigateur, Cloudflare, CloudFront, S3. Tant qu'elle n'existe pas, tout le reste fonctionne, mais reste injoignable depuis le nom de domaine réel.
Ce que ça m'a confirmé
La différence entre resource et data, et le fait qu'un projet Terraform puisse piloter plusieurs fournisseurs cloud à la fois et les faire communiquer, ne sont pas restés des notions abstraites après avoir construit ça : ce sont littéralement les étapes 3 et 4 ci-dessus.
Où ça en est aujourd'hui
Le site tourne derrière ce montage complet : Cloudflare pour le DNS, CloudFront pour le HTTPS et le cache, S3 pour le contenu statique. Terraform ne gère que l'infrastructure : publier un changement de contenu passe par un script séparé (npm run deploy) qui reconstruit le site, le synchronise vers S3, puis invalide le cache CloudFront.
La suite
Pour l'instant, ce déploiement se lance à la main. La prochaine étape est une pipeline CI/CD qui le déclenche automatiquement à chaque push sur la branche principale. Je reviendrai compléter ce billet une fois que ce sera en place et vérifié.