Le VPC (Virtual Private Cloud) constitue la brique fondamentale de toute architecture AWS. Avant de déployer la moindre ressource, il faut définir le réseau dans lequel elle va vivre. Les ingénieurs cloud utilisent aujourd’hui de plus en plus l’Infrastructure as Code (IaC) pour provisionner ces ressources de façon reproductible et versionnée — et Terraform s’est imposé comme l’outil de référence.
Dans cet article, nous allons créer pas à pas un VPC complet sur AWS avec Terraform.
Architecture cible#
Nous allons construire un VPC avec des sous-réseaux publics et privés répartis sur deux zones de disponibilité (AZ) pour assurer la haute disponibilité.
- Sous-réseaux publics : hébergent les serveurs accessibles depuis Internet (serveurs web, bastions).
- Sous-réseaux privés : hébergent les serveurs backend et les bases de données.
- Une NAT Gateway permet aux ressources privées d’accéder à Internet en sortie, sans être exposées en entrée.

Ressources à créer :
- VPC avec CIDR
10.0.0.0/16
- 2 sous-réseaux publics (un par AZ) avec attribution d’IP publique automatique
- 2 sous-réseaux privés (un par AZ)
- Internet Gateway pour la connectivité Internet des subnets publics
- Table de routage publique pointant vers l’IGW
- NAT Gateway dans un subnet public
- Table de routage privée pointant vers la NAT Gateway
Un projet Terraform bien structuré sépare les responsabilités :
| Fichier |
Rôle |
provider.tf |
Version de Terraform et du provider AWS |
backend.tf (optionnel) |
Configuration du state file (S3 recommandé) |
main.tf |
Déclaration de toutes les ressources |
variables.tf |
Déclaration des variables |
terraform.tfvars |
Valeurs des variables |
outputs.tf |
Valeurs affichées après le déploiement |
provider.tf#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
|
variables.tf#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
variable "aws_region" {
description = "Région AWS"
type = string
}
variable "vpc_cidr" {
description = "CIDR du VPC"
type = string
}
variable "public_subnets" {
description = "Liste des CIDR des subnets publics"
type = list(string)
}
variable "private_subnets" {
description = "Liste des CIDR des subnets privés"
type = list(string)
}
variable "availability_zones" {
description = "AZs utilisées"
type = list(string)
}
|
1
2
3
4
5
|
aws_region = "eu-west-1"
vpc_cidr = "10.0.0.0/16"
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnets = ["10.0.3.0/24", "10.0.4.0/24"]
availability_zones = ["eu-west-1a", "eu-west-1b"]
|
main.tf#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
# VPC
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "demo-vpc"
terraform = "true"
}
}
# Internet Gateway
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.main.id
tags = {
Name = "demo-igw"
terraform = "true"
}
}
# Subnets publics
resource "aws_subnet" "public" {
for_each = toset(var.public_subnets)
vpc_id = aws_vpc.main.id
cidr_block = each.value
availability_zone = element(var.availability_zones, index(var.public_subnets, each.value))
map_public_ip_on_launch = true
tags = {
Name = "public-${each.value}"
terraform = "true"
}
}
# Subnets privés
resource "aws_subnet" "private" {
for_each = toset(var.private_subnets)
vpc_id = aws_vpc.main.id
cidr_block = each.value
availability_zone = element(var.availability_zones, index(var.private_subnets, each.value))
tags = {
Name = "private-${each.value}"
terraform = "true"
}
}
# Elastic IP pour la NAT Gateway
resource "aws_eip" "nat" {
domain = "vpc"
tags = {
Name = "demo-nat-eip"
terraform = "true"
}
}
# NAT Gateway
resource "aws_nat_gateway" "nat" {
allocation_id = aws_eip.nat.id
subnet_id = element(values(aws_subnet.public)[*].id, 0)
tags = {
Name = "demo-nat"
terraform = "true"
}
depends_on = [aws_internet_gateway.igw]
}
# Table de routage publique
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
tags = {
Name = "public-rt"
terraform = "true"
}
}
# Association subnets publics → table de routage publique
resource "aws_route_table_association" "public_assoc" {
for_each = aws_subnet.public
subnet_id = each.value.id
route_table_id = aws_route_table.public.id
}
# Table de routage privée
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat.id
}
tags = {
Name = "private-rt"
terraform = "true"
}
}
# Association subnets privés → table de routage privée
resource "aws_route_table_association" "private_assoc" {
for_each = aws_subnet.private
subnet_id = each.value.id
route_table_id = aws_route_table.private.id
}
|
outputs.tf#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
output "vpc_id" {
description = "ID du VPC"
value = aws_vpc.main.id
}
output "public_subnets" {
description = "Subnets publics"
value = [for s in aws_subnet.public : s.id]
}
output "private_subnets" {
description = "Subnets privés"
value = [for s in aws_subnet.private : s.id]
}
output "nat_gateway_ip" {
description = "IP publique de la NAT Gateway"
value = aws_eip.nat.public_ip
}
|
Déploiement#
Prérequis#
Configurez votre profil AWS :
Commandes#
1
2
3
4
5
6
7
8
|
# Initialise le projet et télécharge les providers
terraform init
# Affiche le plan d'exécution — vérifiez toujours avant d'appliquer
terraform plan
# Applique les changements
terraform apply --auto-approve
|
Attention : l’option --auto-approve supprime la confirmation manuelle. Ne l’utilisez que lorsque vous avez validé le plan et que vous êtes certain de ce qui va être créé.
Vérification dans la console AWS#
Une fois le terraform apply terminé, rendez-vous dans la console AWS → VPC pour vérifier la création de :
- Le VPC
demo-vpc
- Les 4 subnets (2 publics, 2 privés)
- L’Internet Gateway
demo-igw
- La NAT Gateway
demo-nat
- Les deux tables de routage avec leurs associations
Bonnes pratiques : le tagging#
Le tagging est souvent négligé mais il est indispensable pour la gestion et la facturation des ressources, surtout dans les grandes organisations.
Appliquez au minimum ces deux tags sur toutes vos ressources :
| Tag |
Valeur |
Utilité |
Name |
Nom lisible de la ressource |
Identification dans la console |
terraform |
"true" |
Identifier les ressources gérées par Terraform |
Vous pouvez étendre avec des tags Environment, Project, Owner, CostCenter selon votre organisation.
Conclusion#
Vous disposez maintenant d’un VPC complet, multi-AZ, déployé via Terraform et prêt pour accueillir vos workloads AWS. Ce pattern — subnets publics/privés, IGW, NAT Gateway — est la base de toute architecture cloud sérieuse.
Le code source complet est disponible sur GitHub.
Dans un prochain article, nous verrons comment aller plus loin en ajoutant des Security Groups et un bastion host pour sécuriser l’accès à vos ressources privées.