Pourquoi ce projet ?
Je voulais voir du cloud pour de vrai. Pas un tutoriel, pas un sandbox — un vrai service, utilisé en entreprise, déployé sur une vraie infrastructure avec de vrais coûts.
L'objectif : déployer une application conteneurisée sur AWS, de manière entièrement automatisée, en utilisant Terraform pour le provisioning et Ansible pour la configuration. Le tout en restant dans le Free Tier AWS pour ne pas recevoir de facture surprise.
Ça reste un projet modeste. Il y a des dizaines de façons de monter ce type d'infra — ALB, HTTPS avec ACM, base RDS managée, ECS pour l'orchestration. J'ai volontairement ignoré tout ça. Le but n'était pas de construire une architecture production-ready, c'était de comprendre les fondamentaux : la programmation déclarative, le provisioning, la configuration automatisée, et comment ces briques s'assemblent.
Deux outils, deux rôles
Un point qui m'a pris du temps à bien intégrer : Terraform et Ansible ne font pas la même chose.
Terraform crée l'infrastructure. Il déclare l'état souhaité — deux instances EC2, un security group, une région — et AWS s'occupe de le rendre réel. C'est de la programmation déclarative : on décrit le "quoi", pas le "comment". Si une ressource existe déjà et correspond à la déclaration, Terraform ne la recrée pas. Si elle diffère, il la modifie ou la remplace.
Ansible configure ce qui tourne dessus. Une fois les machines créées et accessibles en SSH, Ansible s'y connecte et exécute des playbooks : installer Docker, déployer les conteneurs, configurer Nginx. C'est de la gestion de configuration. Les playbooks sont idempotents — on peut les rejouer sans casser ce qui est déjà en place.
Le flow complet ressemble à ça :
terraform apply → EC2 créées → inventaire Ansible généré → attente SSH → Ansible installe Docker → déploie l'application
Une seule commande, et tout se chaîne. Concrètement dans le main.tf, ça donne :
resource "aws_instance" "wordpress_server" {
count = 2
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
key_name = var.key_name
vpc_security_group_ids = [aws_security_group.wordpress_sg.id]
root_block_device {
volume_size = 8
volume_type = "gp3"
}
tags = {
Name = "WordPress-Server-${count.index + 1}"
Project = var.project_name
}
}
resource "null_resource" "wait_ssh" {
count = 2
depends_on = [aws_instance.wordpress_server, local_file.ansible_inventory]
provisioner "local-exec" {
command = <<-EOT
until ssh -i ~/.ssh/aws -o ConnectTimeout=2 \
-o StrictHostKeyChecking=no \
ubuntu@${aws_instance.wordpress_server[count.index].public_ip} exit; do
sleep 5
done
EOT
}
}
resource "null_resource" "run_ansible" {
depends_on = [null_resource.wait_ssh]
provisioner "local-exec" {
command = <<-EOT
cd ${path.module}/../ansible
ansible-playbook -i inventory/aws.yml playbooks/01-docker-installation.yml
ansible-playbook -i inventory/aws.yml playbooks/02-deploy-wordpress.yml
EOT
}
}
Terraform crée les instances, attend que le SSH soit prêt, puis lance Ansible automatiquement.
L'architecture
Deux instances EC2 (Ubuntu 24.04) dans le VPC par défaut, région eu-west-3. Un security group qui autorise le SSH (port 22) et le HTTP (port 80).
Pas d'HTTPS : ça nécessite soit un ALB (Application Load Balancer) avec un certificat ACM, soit une Elastic IP fixe pour pointer un domaine — les deux sont hors Free Tier. Le projet reste en HTTP uniquement.
Sur chaque instance, quatre conteneurs Docker orchestrés avec Docker Compose :
- Nginx — reverse proxy en façade, écoute sur le port 80 et route le trafic
- WordPress — l'application, derrière Nginx
- MariaDB — la base de données, isolée dans un réseau Docker privé
- PHPMyAdmin — interface DB, accessible via
/phpmyadmin/à travers Nginx
Internet → Nginx (:80)
├─ / → WordPress → MariaDB (réseau privé)
└─ /phpmyadmin → PHPMyAdmin → MariaDB (réseau privé)
Nginx agit comme reverse proxy : il reçoit toutes les requêtes HTTP et les redirige vers le bon conteneur en fonction du chemin. MariaDB n'est jamais exposé directement — il vit dans un réseau Docker interne, accessible uniquement par WordPress et PHPMyAdmin.
Les deux instances sont indépendantes. Pas de synchronisation de données entre elles. C'est une limite assumée du scope Free Tier.
IAM : la première vraie leçon
Au début, je faisais tout avec le compte root AWS. Terraform, la console, tout. Ça fonctionnait, mais c'est exactement ce qu'il ne faut pas faire.
Le compte root a les pleins pouvoirs sur l'ensemble du compte AWS. L'utiliser pour des opérations courantes, c'est comme travailler en permanence en sudo sur un serveur Linux — ça marche jusqu'au jour où une mauvaise commande fait des dégâts.
Le problème s'est manifesté concrètement : des erreurs de caller identity, des permissions mal définies, des groupes et users IAM qui n'existaient pas ou qui n'avaient pas les bonnes policies attachées.
La correction :
- Créer un user IAM dédié (
terraform-user) en CLI only, avec uniquement les permissions nécessaires (EC2, VPC, Security Groups) - Un autre user pour la console GUI et les manipulations manuelles ou CLI au besoin
- Assigner chaque user à un groupe avec une policy claire
- Ne plus jamais toucher au compte root pour les opérations courantes
- Utiliser un profil AWS CLI séparé
IAM, c'est hyper important. C'est la première chose à configurer proprement sur un compte AWS, avant même de créer la moindre ressource. On verra le reste après.
Les galères
Les credentials. Configurer correctement le provider Terraform avec un profil AWS CLI dédié au lieu du compte root n'a pas été immédiat. Comprendre la chaîne d'authentification — profil CLI, access key, secret key, caller identity — a nécessité plusieurs essais. C'est une zone où je ne fais qu'effleurer la surface, il me faut plus de pratique pour vraiment maîtriser.
Les healthchecks et l'ordre de démarrage. Terraform crée les instances EC2, mais elles ne sont pas immédiatement prêtes à recevoir des connexions SSH. Si Ansible se lance trop tôt, tout échoue. Il a fallu ajouter un mécanisme d'attente : une boucle qui teste la connexion SSH avec un timeout et un intervalle de retry avant de déclencher les playbooks.
Même problème côté conteneurs. WordPress dépend de MariaDB, Nginx dépend de WordPress. Si un service démarre avant que sa dépendance ne soit prête, il crash ou reste bloqué. Docker Compose gère les depends_on, mais ça ne garantit pas que le service est réellement opérationnel — juste que le conteneur est démarré. Il a fallu ajouter des checks HTTP dans Ansible pour s'assurer que WordPress répondait vraiment avant de passer à l'étape suivante :
- name: Pause play until a URL is reachable from this host
ansible.builtin.uri:
url: "http://{{ ansible_host }}:{{ nginx_port }}"
status_code: [200, 302]
method: GET
register: _result
until: _result.status in [200, 302]
retries: 30
delay: 5
30 tentatives, 5 secondes d'intervalle. Si WordPress ne répond pas en 2 minutes et demie, le playbook échoue proprement au lieu de continuer à l'aveugle.
Le réseau. La partie que je maîtrise le moins. J'ai configuré les réseaux Docker (un public pour Nginx/WordPress, un privé pour MariaDB), le security group AWS, le routing Nginx. Ça fonctionne. Mais je sais qu'il y a des zones d'ombre sur les couches réseau AWS (subnets, route tables, NAT gateways) que je n'ai pas eu besoin de toucher ici grâce au VPC par défaut. C'est clairement un axe d'approfondissement pour la suite.
Attention aux coûts
Un point à anticiper dès le départ : AWS, c'est réel. Chaque terraform apply crée des ressources qui coûtent de l'argent. Le Free Tier offre 750 heures/mois de t2.micro, 30 Go d'EBS — mais si on dépasse, ou si on oublie de détruire ses ressources, la facture arrive.
Quelques réflexes que j'ai pris :
- Toujours
terraform destroyquand je ne travaille pas sur le projet - Mettre un budget cap et une alarme de facturation dès la création du compte
- Vérifier régulièrement la console de facturation AWS
- Utiliser
t2.microet nont3.micropour rester dans le Free Tier (une erreur que j'ai d'ailleurs dans ma config actuelle) - Ne jamais laisser tourner des instances "pour voir"
Ce que j'ai compris, ce qu'il me reste à creuser
- Terraform et Ansible sont complémentaires, pas interchangeables. Terraform gère le cycle de vie des ressources cloud. Ansible gère ce qui tourne sur ces ressources. Mélanger les deux rôles, c'est créer de la confusion.
- L'idempotence, c'est concret. Pouvoir relancer
terraform applyou un playbook Ansible sans tout casser, c'est ce qui rend l'automatisation fiable. - Chaîner Terraform → Ansible en une commande, c'est possible. Les
null_resourceetlocal-execpermettent de lancer les playbooks directement après le provisioning, sans intervention manuelle. - Le Free Tier est un bon cadre d'apprentissage. Il force à faire des choix, à comprendre ce qu'on utilise et pourquoi.
Ce projet m'a permis de poser les bases de l'IaC et de comprendre comment Terraform et Ansible s'articulent sur un cas réel. Le réseau AWS — subnets, route tables, NAT, peering — reste à approfondir, le VPC par défaut masque encore beaucoup de choses. Pareil pour Kubernetes, qui est la suite logique après Docker Compose pour l'orchestration. Il y a encore beaucoup à explorer, et c'est ce qui rend ce domaine intéressant.