
DynamoDB n'est plus nécessaire pour le verrouillage de state Terraform sur S3
Terraform 1.11 rend obsolète l’utilisation de DynamoDB pour le verrouillage de state S3. Grâce aux écritures …

Les sites web statiques connaissent un regain d’intérêt dans le monde du DevOps et du Cloud. Pourquoi ? Parce qu’ils offrent des performances exceptionnelles, une scalabilité quasi-illimitée et des coûts d’exploitation réduits. Dans cet article, nous allons détailler une architecture type sur AWS utilisant S3, CloudFront et Route 53.
Avant de plonger dans l’architecture, identifions les cas d’usage optimaux pour cette infrastructure :
Sites parfaitement adaptés :
Avantages techniques :
Limitations à considérer :
Tip
Cette architecture est idéale pour les sites nécessitant une disponibilité élevée avec un minimum d’opérations. Le temps de déploiement est quasi instantané et le rollback trivial (copie S3).
Voici le schéma d’architecture que nous allons mettre en place :

Amazon S3 est le socle de notre architecture. Configuration recommandée :
Bucket principal :
Nom : votredomaine.com
Région : eu-west-1 (ou la plus proche de vos utilisateurs)
Versioning : Activé (pour rollback facile)
Encryption : AES-256 (SSE-S3)
Block Public Access : Activé (bucket entièrement privé)
Politique de bucket : Le bucket S3 reste privé et CloudFront y accède via Origin Access Control (OAC). Il s’agit de la méthode moderne et recommandée par AWS pour sécuriser l’accès entre CloudFront et S3.
Info
Architecture avec OAC : Cette configuration utilise Origin Access Control pour un accès sécurisé de CloudFront vers S3. Le bucket reste entièrement privé et CloudFront accède au contenu via l’endpoint REST API de S3 (pas le website endpoint). OAC est la méthode recommandée par AWS depuis 2022, remplaçant l’ancienne Origin Access Identity (OAI).
Warning
Gestion des URLs avec sous-répertoires : Certains générateurs de sites statiques (Hugo, Jekyll, Next.js) créent des structures de répertoires comme /about/index.html. Avec OAC et l’endpoint REST API S3, les requêtes vers /about/ ne sont pas automatiquement réécrites en /about/index.html (contrairement à S3 Website Hosting).
Solution : Utilisez une CloudFront Function pour réécrire les URLs. Exemple de fonction :
function handler(event) {
var request = event.request;
var uri = request.uri;
// Ajouter index.html pour les chemins se terminant par /
if (uri.endsWith('/')) {
request.uri += 'index.html';
}
// Ajouter /index.html pour les chemins sans extension
else if (!uri.includes('.')) {
request.uri += '/index.html';
}
return request;
}
Cette fonction s’exécute à chaque requête avec un coût minimal (~0,10$ par million de requêtes).
CloudFront assure la distribution du contenu avec une latence minimale.
Configuration clé :
Optimisations performances :
Cache-Control: public, max-age=31536000, immutable # Pour JS/CSS versionnés
Cache-Control: public, max-age=3600 # Pour HTML
Cache-Control: no-cache # Pour index.html
Les invalidations CloudFront sont limitées à 1000 paths gratuits par mois. Au-delà : 0,005$ par path.
Stratégie recommandée :
Exemple de commande d’invalidation :
aws cloudfront create-invalidation \
--distribution-id E1234ABCD \
--paths "/index.html" "/*.html"
Service DNS managé d’AWS pour la résolution de noms de domaine.
Configuration :
Avantages Route 53 :
Gestion des certificats SSL/TLS gratuits pour HTTPS.
Points importants :
Warning
Région critique : Pour CloudFront, le certificat ACM doit être créé dans la région us-east-1 (N. Virginia), même si votre infrastructure est ailleurs. C’est une contrainte AWS pour les distributions CloudFront globales.
Bucket secondaire pour stocker les logs d’accès du bucket S3 principal.
Configuration du bucket de logs :
Nom : votredomaine-logs
Lifecycle Policy :
- Suppression après 365 jours
Voici une estimation des coûts mensuels pour un site typique (5 000 visiteurs/mois, 50 MB, 3 pages par visite) :
| Service | Coût mensuel | Coût annuel |
|---|---|---|
| S3 Storage | ~0,01 $ | ~0,12 $ |
| CloudFront | ~0,65 $ | ~7,80 $ |
| Route 53 | 0,50 $ | 6,00 $ |
| Certificate Manager | Gratuit* | Gratuit* |
| S3 Logs | ~0,01 $ | ~0,12 $ |
| TOTAL | ~1,20 $ | ~14,40 $ |
*Gratuit tant que le certificat est utilisé avec des services AWS intégrés (CloudFront, ALB, API Gateway)
Tip
Retour d’expérience réel : En production pour un site corporate standard (5 000 visiteurs/mois), les coûts se situent entre 2-3 $/mois, incluant tous les services.
| Trafic mensuel | S3 | CloudFront | Route 53 | Total mensuel | Total annuel |
|---|---|---|---|---|---|
| 5 000 visiteurs | ~0,01 $ | ~0,65 $ | 0,50 $ | ~1,20 $ | ~14,40 $ |
| 50 000 visiteurs | ~0,05 $ | ~6,50 $ | 0,50 $ | ~7 $ | ~84 $ |
| 200 000 visiteurs | ~0,10 $ | ~25 $ | 0,50 $ | ~26 $ | ~312 $ |
L’automatisation du déploiement est essentielle pour une approche DevOps.
Voici des exemples d’intégration CI/CD à adapter selon votre contexte (framework utilisé, outils CI/CD, stratégie de déploiement, etc…) :
stages:
- build
- deploy
- invalidate
variables:
S3_BUCKET: "votredomaine.com"
CLOUDFRONT_DISTRIBUTION_ID: "E1234ABCD"
build:
stage: build
image: node:18
script:
- npm install
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
only:
- main
deploy_s3:
stage: deploy
image: amazon/aws-cli:latest
script:
- aws s3 sync dist/ s3://${S3_BUCKET}/
--delete
--cache-control "public,max-age=31536000,immutable"
--exclude "*.html"
- aws s3 sync dist/ s3://${S3_BUCKET}/
--exclude "*"
--include "*.html"
--cache-control "public,max-age=3600"
dependencies:
- build
only:
- main
invalidate_cloudfront:
stage: invalidate
image: amazon/aws-cli:latest
script:
- aws cloudfront create-invalidation
--distribution-id ${CLOUDFRONT_DISTRIBUTION_ID}
--paths "/*.html" "/index.html"
dependencies:
- deploy_s3
only:
- main
name: Deploy to AWS
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Build
run: |
npm install
npm run build
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-west-1
- name: Deploy to S3
run: |
aws s3 sync dist/ s3://votredomaine.com/ --delete
- name: Invalidate CloudFront
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
--paths "/*.html" "/index.html"
# S3 Bucket
resource "aws_s3_bucket" "website" {
bucket = "votredomaine.com"
}
# Versioning
resource "aws_s3_bucket_versioning" "website" {
bucket = aws_s3_bucket.website.id
versioning_configuration {
status = "Enabled"
}
}
# Encryption
resource "aws_s3_bucket_server_side_encryption_configuration" "website" {
bucket = aws_s3_bucket.website.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
# Public Access Block - Bucket entièrement privé
resource "aws_s3_bucket_public_access_block" "website" {
bucket = aws_s3_bucket.website.id
block_public_acls = true
ignore_public_acls = true
block_public_policy = true
restrict_public_buckets = true
}
# Origin Access Control (OAC)
resource "aws_cloudfront_origin_access_control" "website" {
name = "oac-${aws_s3_bucket.website.id}"
description = "OAC for ${aws_s3_bucket.website.id}"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
# Bucket Policy - Autoriser uniquement CloudFront via OAC
resource "aws_s3_bucket_policy" "website" {
bucket = aws_s3_bucket.website.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Sid = "AllowCloudFrontServicePrincipal"
Effect = "Allow"
Principal = {
Service = "cloudfront.amazonaws.com"
}
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.website.arn}/*"
Condition = {
StringEquals = {
"AWS:SourceArn" = aws_cloudfront_distribution.website.arn
}
}
}]
})
depends_on = [aws_s3_bucket_public_access_block.website]
}
# CloudFront Function pour la réécriture d'URLs
resource "aws_cloudfront_function" "url_rewrite" {
name = "url-rewrite-${replace(aws_s3_bucket.website.id, ".", "-")}"
runtime = "cloudfront-js-2.0"
comment = "Rewrite URLs to add index.html for directory paths"
publish = true
code = <<-EOT
function handler(event) {
var request = event.request;
var uri = request.uri;
// Ajouter index.html pour les chemins se terminant par /
if (uri.endsWith('/')) {
request.uri += 'index.html';
}
// Ajouter /index.html pour les chemins sans extension
else if (!uri.includes('.')) {
request.uri += '/index.html';
}
return request;
}
EOT
}
# ACM Certificate (doit être dans us-east-1 pour CloudFront)
resource "aws_acm_certificate" "website" {
provider = aws.us_east_1
domain_name = "votredomaine.com"
subject_alternative_names = ["www.votredomaine.com"]
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
# CloudFront Distribution avec OAC
resource "aws_cloudfront_distribution" "website" {
enabled = true
is_ipv6_enabled = true
default_root_object = "index.html"
aliases = ["votredomaine.com", "www.votredomaine.com"]
origin {
# Utiliser le domain name du bucket (REST API endpoint)
domain_name = aws_s3_bucket.website.bucket_regional_domain_name
origin_id = "S3-${aws_s3_bucket.website.id}"
origin_access_control_id = aws_cloudfront_origin_access_control.website.id
}
default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-${aws_s3_bucket.website.id}"
viewer_protocol_policy = "redirect-to-https"
compress = true
# Association de la CloudFront Function
function_association {
event_type = "viewer-request"
function_arn = aws_cloudfront_function.url_rewrite.arn
}
cache_policy_id = "658327ea-f89d-4fab-a63d-7e88639e58f6" # CachingOptimized
# Alternative : configuration manuelle du cache
# forwarded_values {
# query_string = false
# cookies {
# forward = "none"
# }
# }
# min_ttl = 0
# default_ttl = 3600
# max_ttl = 86400
}
viewer_certificate {
acm_certificate_arn = aws_acm_certificate.website.arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
# Pages d'erreur personnalisées
custom_error_response {
error_code = 404
response_code = 404
response_page_path = "/404.html"
}
custom_error_response {
error_code = 403
response_code = 404
response_page_path = "/404.html"
}
}
# Route 53 Zone
resource "aws_route53_zone" "main" {
name = "votredomaine.com"
}
# Route 53 Records
resource "aws_route53_record" "website" {
zone_id = aws_route53_zone.main.zone_id
name = "votredomaine.com"
type = "A"
alias {
name = aws_cloudfront_distribution.website.domain_name
zone_id = aws_cloudfront_distribution.website.hosted_zone_id
evaluate_target_health = false
}
}
resource "aws_route53_record" "website_www" {
zone_id = aws_route53_zone.main.zone_id
name = "www.votredomaine.com"
type = "A"
alias {
name = aws_cloudfront_distribution.website.domain_name
zone_id = aws_cloudfront_distribution.website.hosted_zone_id
evaluate_target_health = false
}
}
# Outputs utiles
output "cloudfront_distribution_id" {
description = "CloudFront Distribution ID"
value = aws_cloudfront_distribution.website.id
}
output "cloudfront_domain_name" {
description = "CloudFront Domain Name"
value = aws_cloudfront_distribution.website.domain_name
}
output "s3_bucket_name" {
description = "S3 Bucket Name"
value = aws_s3_bucket.website.id
}
Tip
Conseil DevOps : Utilisez Terraform workspaces pour gérer plusieurs environnements (dev, staging, prod) avec la même configuration IaC. Stockez le state Terraform sur S3 avec DynamoDB pour le locking.
Performance
Scalabilité
Fiabilité
Sécurité
Coûts
Contenu dynamique
Latence première visite
Gestion d’état
SEO
Fonctionnalités avancées
Cette architecture S3 + CloudFront + Route 53 représente un pattern éprouvé pour l’hébergement de sites web statiques sur AWS. Elle combine haute performance, scalabilité illimitée et coûts maîtrisés.
Points clés à retenir :
Tip
Nous accompagnons les entreprises dans la mise en place de cette architecture :
Contactez-nous pour discuter de votre projet d’infrastructure cloud.
Prêt à moderniser votre infrastructure web ? Suivez-nous pour découvrir d’autres architectures types dans les prochains articles.

Terraform 1.11 rend obsolète l’utilisation de DynamoDB pour le verrouillage de state S3. Grâce aux écritures …

Vous avez déjà reçu une facture AWS qui vous a fait froid dans le dos ? Vous n’êtes pas seul. La facturation cloud …
Contactez-nous pour discuter de votre projet cloud et découvrir comment nous pouvons vous accompagner dans votre transformation digitale.
Nous contacter