devops

Introduction à Terraform avec Docker

Découvre Terraform en configurant un environnement local avec Docker : syntaxe, providers, modules et bonnes pratiques.

4 minutes de lecture

Tu veux découvrir Terraform sans te prendre la tête avec un cloud provider ? Bonne nouvelle : on peut tout apprendre en local avec Docker. C'est exactement ce qu'on va faire dans cet article.

Installer Terraform

Direction le site officiel pour télécharger le binaire qui correspond à ton système.

Pour vérifier que tout est bon :

1$ terraform --version
2Terraform v1.9.x
3on darwin_arm64

La syntaxe en 2 minutes

Avant de se lancer, voici les bases de la syntaxe Terraform.

Assigner une valeur

1image_id = "1234"

Définir une ressource

1resource "container" "example" {
2    name = "1234"
3    network_interface {
4        # ...
5    }
6}

Les types de variables

1"1234"                      // String
21234                        // Number
3true / false                // Boolean
4[0, 5, 2, 4]                // List
5{ "key" = "value" }         // Map

Rien de sorcier. On passe à la pratique.

Configurer le provider Docker

On va créer deux fichiers :

1|-- main.tf
2|-- provider.tf

Dans provider.tf, on dit à Terraform d'utiliser le provider Docker :

1terraform {
2  required_providers {
3    docker = {
4      source  = "kreuzwerker/docker"
5      version = "~> 3.0"
6    }
7  }
8}
9
10provider "docker" {
11  host = "unix:///var/run/docker.sock"
12}

On initialise le projet (ça télécharge le provider) :

1$ terraform init

Lancer un premier container

Dans main.tf, on définit une image et un container :

1resource "docker_image" "ubuntu" {
2  name = "ubuntu:precise"
3}
4
5resource "docker_container" "ubuntu" {
6  name  = "foo"
7  image = docker_image.ubuntu.latest
8}

C'est assez lisible : on pull une image Ubuntu, puis on crée un container basé dessus.

Pour appliquer :

1$ terraform apply

Terraform te montre ce qu'il va faire. Tape yes pour confirmer.

Astuce : terraform plan te montre les changements sans rien appliquer. Pratique pour vérifier avant de lancer.

Le fichier state

Terraform crée un fichier terraform.tfstate qui garde en mémoire l'état de ton infra. C'est grâce à lui qu'il sait ce qu'il doit créer, modifier ou supprimer.

Règle d'or : ne touche jamais à ce fichier manuellement. Si tu bosses en équipe, stocke-le sur un backend distant.

Un exemple plus concret : PostgreSQL + Adminer

On va monter un vrai setup avec PostgreSQL et Adminer (une interface web pour gérer la DB). Et on va le faire proprement avec des modules.

Nouvelle structure :

1|-- main.tf
2|-- provider.tf
3|-- modules
4|   |-- postgres
5|   |   |-- provider.tf
6|   |   |-- postgres.tf
7|   |   |-- adminer.tf

Le module postgres

Dans main.tf, on appelle notre module :

1module "postgres" {
2  source = "./modules/postgres"
3}

Pense à relancer terraform init quand tu ajoutes un module.

postgres.tf

1resource "docker_image" "postgres" {
2  name         = "postgres"
3  keep_locally = true
4}
5
6resource "docker_container" "postgres" {
7  name     = "postgres"
8  image    = docker_image.postgres.latest
9  shm_size = 4000
10  ports {
11    external = 5432
12    internal = 5432
13  }
14  volumes {
15    container_path = "/var/lib/postgresql/data"
16    host_path      = abspath("tmp/postgres")
17  }
18  env = [
19    "POSTGRES_USER=user",
20    "POSTGRES_PASSWORD=password"
21  ]
22}

Si tu connais Docker, tu retrouves tes marques :

  • shm_size : mémoire partagée (obligatoire pour Postgres en local)
  • ports : mapping de ports classique
  • volumes : persistance des données
  • env : variables d'environnement

adminer.tf

1resource "docker_image" "adminer" {
2  name         = "adminer"
3  keep_locally = true
4}
5
6resource "docker_container" "adminer" {
7  name  = "adminer"
8  image = docker_image.adminer.latest
9  ports {
10    external = 8080
11    internal = 8080
12  }
13  env = [
14    "ADMINER_DEFAULT_SERVER=${docker_container.postgres.name}"
15  ]
16  depends_on = [
17    docker_container.postgres
18  ]
19}

Le truc cool ici : on référence le container Postgres avec docker_container.postgres.name. Terraform gère les dépendances automatiquement, mais depends_on permet d'être explicite.

Les outputs

Tu peux exposer des valeurs depuis un module. Crée un fichier outputs.tf :

1output "postgres_container_name" {
2  value = docker_container.postgres.name
3}

Cette valeur sera accessible via module.postgres.postgres_container_name.

Aller plus loin : les variables

Pour rendre ton module configurable, utilise des variables.

Dans main.tf, on crée un network et on le passe au module :

1resource "docker_network" "default" {
2  name = "local"
3}
4
5module "postgres" {
6  source       = "./modules/postgres"
7  network_name = docker_network.default.name
8}

Dans le module, crée variables.tf :

1variable "network_name" {
2  type = string
3}

Et utilise-la dans postgres.tf :

1resource "docker_container" "postgres" {
2  name     = "${var.network_name}-postgres"
3  # ... reste de la config ...
4  networks_advanced {
5    name = var.network_name
6  }
7}

La syntaxe ${var.network_name} permet d'insérer la variable dans une string.


Tu maîtrises maintenant les bases de Terraform : providers, ressources, modules, variables et outputs. Tout ça sans avoir dépensé un centime en cloud.

Prêt à passer aux choses sérieuses ? J'ai écrit un article sur le déploiement d'infrastructure Scaleway avec Terraform qui applique ces concepts sur un vrai cloud provider.