Terraform et Docker : Gérer vos Conteneurs avec l'Infrastructure as Code

Gérez vos conteneurs Docker avec Terraform et l'Infrastructure as Code. Provider Docker, images, réseaux, volumes et déploiement d'une stack complète Nginx + App + PostgreSQL + Redis.

Docker a révolutionné la façon dont nous empaquetions et déployions nos applications. Mais lorsqu'il s'agit de gérer des environnements Docker complexes avec plusieurs conteneurs, réseaux et volumes, les commandes manuelles et même Docker Compose atteignent leurs limites. Et si vous pouviez gérer votre infrastructure Docker avec la même rigueur et la même reproductibilité que votre infrastructure cloud ? C'est exactement ce que permet Terraform avec le provider Docker.

Dans cet article, nous allons découvrir comment utiliser Terraform pour gérer des images Docker, créer des conteneurs, configurer des réseaux et des volumes, et déployer une stack multi-conteneurs complète (Nginx + Application + Base de données). Vous verrez comment l'approche Infrastructure as Code apporte une valeur ajoutée considérable même dans le monde des conteneurs.

Diagramme - Terraform et Docker : Gérer vos Conteneurs avec l'Infrastructure as Code

Pourquoi Utiliser Terraform avec Docker ?

Avant de plonger dans le code, posons la question légitime : pourquoi utiliser Terraform pour Docker alors que Docker Compose existe déjà ? Voici les raisons principales :

  • Cohérence avec le reste de l'infrastructure : Si vous gérez déjà votre infrastructure cloud avec Terraform, utiliser le même outil pour Docker réduit la complexité et unifie vos workflows.
  • Gestion d'état : Terraform maintient un fichier d'état (state) qui suit exactement les ressources créées. Vous savez toujours ce qui est déployé et ce qui a changé.
  • Plan d'exécution : Le terraform plan vous montre exactement ce qui va changer avant d'appliquer les modifications, contrairement à Docker Compose qui applique directement.
  • Intégration multi-providers : Terraform peut gérer simultanément Docker, AWS, DNS, et d'autres services. Un seul outil pour tout orchestrer.
  • Modularité : Les modules Terraform permettent de créer des composants Docker réutilisables à travers vos projets.
  • Gestion du cycle de vie : Terraform gère la création, la mise à jour et la destruction des ressources de manière déclarative et prévisible.

Le Provider Docker pour Terraform

Le provider Docker est un plugin Terraform maintenu par kreuzwerker qui permet d'interagir avec l'API Docker. Il supporte la gestion des images, conteneurs, réseaux, volumes, et plus encore.

Installation et configuration

Commencez par configurer le provider dans votre fichier Terraform :

# versions.tf
terraform {
  required_version = ">= 1.0.0"

  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "~> 3.0"
    }
  }
}

# provider.tf
provider "docker" {
  # Par défaut, se connecte au socket Docker local
  # Sur Linux : unix:///var/run/docker.sock
  # Sur macOS : unix:///Users//.docker/run/docker.sock

  # Pour un hôte Docker distant (optionnel)
  # host = "tcp://192.168.1.100:2376/"

  # Avec TLS (optionnel)
  # host    = "tcp://192.168.1.100:2376/"
  # ca_material   = file("ca.pem")
  # cert_material = file("cert.pem")
  # key_material  = file("key.pem")
}

Initialisez ensuite votre projet :

terraform init

Terraform téléchargera automatiquement le provider Docker et sera prêt à l'utiliser.

Gérer des Images Docker avec Terraform

La première étape consiste à gérer les images Docker que vos conteneurs utiliseront. La ressource docker_image permet de télécharger (pull) des images depuis un registry :

# Télécharger une image depuis Docker Hub
resource "docker_image" "nginx" {
  name         = "nginx:1.25-alpine"
  keep_locally = false  # Supprimer l'image locale lors du destroy
}

resource "docker_image" "postgres" {
  name         = "postgres:16-alpine"
  keep_locally = false
}

resource "docker_image" "redis" {
  name         = "redis:7-alpine"
  keep_locally = false
}

# Image depuis un registry privé
resource "docker_image" "mon_app" {
  name = "registry.example.com/monapp:latest"

  build {
    context    = "${path.module}/app"
    dockerfile = "Dockerfile"
    tag        = ["monapp:latest"]

    label = {
      maintainer = "equipe@example.com"
    }
  }
}

La ressource docker_image supporte également la construction d'images à partir d'un Dockerfile grâce au bloc build. C'est particulièrement utile pour les images personnalisées de votre application.

Data source pour les images

Vous pouvez aussi utiliser le data source docker_image pour référencer une image déjà présente localement :

data "docker_image" "ubuntu" {
  name = "ubuntu:22.04"
}

output "ubuntu_image_id" {
  value = data.docker_image.ubuntu.id
}

Créer des Conteneurs Docker

La ressource docker_container est le cœur du provider Docker. Elle permet de créer et configurer des conteneurs avec toutes les options habituelles :

# Conteneur simple
resource "docker_container" "web" {
  name  = "web-server"
  image = docker_image.nginx.image_id

  ports {
    internal = 80
    external = 8080
  }

  restart = "unless-stopped"

  env = [
    "NGINX_HOST=example.com",
    "NGINX_PORT=80"
  ]

  labels {
    label = "app"
    value = "web"
  }

  labels {
    label = "environment"
    value = "development"
  }
}

Options avancées des conteneurs

resource "docker_container" "app" {
  name  = "application"
  image = docker_image.mon_app.image_id

  # Mapping de ports
  ports {
    internal = 3000
    external = 3000
    protocol = "tcp"
  }

  # Variables d'environnement
  env = [
    "NODE_ENV=production",
    "DATABASE_URL=postgresql://user:pass@db:5432/mydb",
    "REDIS_URL=redis://cache:6379"
  ]

  # Montage de volumes
  volumes {
    volume_name    = docker_volume.app_data.name
    container_path = "/app/data"
  }

  volumes {
    host_path      = "/home/deploy/config"
    container_path = "/app/config"
    read_only      = true
  }

  # Connexion réseau
  networks_advanced {
    name    = docker_network.app_network.name
    aliases = ["app", "application"]
  }

  # Limites de ressources
  memory = 512  # Mo
  cpu_shares = 512

  # Politique de redémarrage
  restart = "unless-stopped"

  # Healthcheck
  healthcheck {
    test         = ["CMD", "curl", "-f", "http://localhost:3000/health"]
    interval     = "30s"
    timeout      = "10s"
    retries      = 3
    start_period = "40s"
  }

  # Dépendances
  depends_on = [
    docker_container.db,
    docker_container.cache
  ]
}

Gérer les Réseaux Docker

Les réseaux Docker permettent aux conteneurs de communiquer entre eux de manière isolée. Terraform gère les réseaux via la ressource docker_network :

# Réseau bridge personnalisé
resource "docker_network" "app_network" {
  name   = "app-network"
  driver = "bridge"

  ipam_config {
    subnet  = "172.20.0.0/16"
    gateway = "172.20.0.1"
  }

  labels {
    label = "project"
    value = "monapp"
  }
}

# Réseau pour la base de données (isolé)
resource "docker_network" "db_network" {
  name     = "db-network"
  driver   = "bridge"
  internal = true  # Pas d'accès Internet

  ipam_config {
    subnet  = "172.21.0.0/16"
    gateway = "172.21.0.1"
  }
}

# Réseau overlay pour Docker Swarm
resource "docker_network" "overlay_network" {
  name     = "overlay-network"
  driver   = "overlay"
  attachable = true
}

Les conteneurs peuvent être connectés à plusieurs réseaux simultanément, ce qui permet de créer des architectures réseau sophistiquées. Par exemple, un conteneur applicatif peut être sur le réseau app_network (accessible depuis l'extérieur) et sur le réseau db_network (pour communiquer avec la base de données), tandis que la base de données n'est que sur db_network.

Gérer les Volumes Docker

Les volumes Docker permettent de persister les données au-delà du cycle de vie des conteneurs. Terraform les gère via la ressource docker_volume :

# Volume nommé simple
resource "docker_volume" "app_data" {
  name = "app-data"

  labels {
    label = "project"
    value = "monapp"
  }
}

# Volume pour la base de données
resource "docker_volume" "db_data" {
  name = "postgres-data"

  labels {
    label = "project"
    value = "monapp"
  }
  labels {
    label = "type"
    value = "database"
  }
}

# Volume pour les logs
resource "docker_volume" "logs" {
  name = "app-logs"

  driver = "local"
  driver_opts = {
    type   = "none"
    device = "/var/log/monapp"
    o      = "bind"
  }
}

# Volume pour le cache Redis
resource "docker_volume" "redis_data" {
  name = "redis-data"
}

Docker Compose vs Terraform : Comparaison

Comprendre les différences entre Docker Compose et Terraform pour Docker vous aidera à choisir le bon outil selon votre contexte :

Docker Compose excelle dans les cas suivants :

  • Développement local et prototypage rapide
  • Projets simples avec peu de conteneurs
  • Quand l'équipe connaît déjà Docker mais pas Terraform
  • Pour des fichiers de configuration simples et lisibles

Terraform avec le provider Docker est préférable quand :

  • Vous gérez déjà de l'infrastructure cloud avec Terraform
  • Vous avez besoin d'un suivi d'état précis
  • Vous voulez un plan d'exécution avant chaque changement
  • Vous devez intégrer Docker avec d'autres providers (AWS, DNS, etc.)
  • Vous avez besoin de modularité et de réutilisabilité avancées
  • Vous déployez sur des hôtes Docker distants

Voici un comparatif rapide de la même configuration dans les deux formats :

Version Docker Compose

# docker-compose.yml
version: '3.8'
services:
  web:
    image: nginx:1.25-alpine
    ports:
      - "8080:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    networks:
      - frontend
    depends_on:
      - app

  app:
    image: node:20-alpine
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/mydb
    networks:
      - frontend
      - backend
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_PASSWORD=pass
    volumes:
      - db_data:/var/lib/postgresql/data
    networks:
      - backend

volumes:
  db_data:

networks:
  frontend:
  backend:
    internal: true

Version Terraform équivalente

# La même stack en Terraform
resource "docker_network" "frontend" {
  name = "frontend"
}

resource "docker_network" "backend" {
  name     = "backend"
  internal = true
}

resource "docker_volume" "db_data" {
  name = "db_data"
}

resource "docker_image" "nginx" {
  name = "nginx:1.25-alpine"
}

resource "docker_image" "node" {
  name = "node:20-alpine"
}

resource "docker_image" "postgres" {
  name = "postgres:16-alpine"
}

resource "docker_container" "db" {
  name  = "db"
  image = docker_image.postgres.image_id

  env = ["POSTGRES_PASSWORD=pass"]

  volumes {
    volume_name    = docker_volume.db_data.name
    container_path = "/var/lib/postgresql/data"
  }

  networks_advanced {
    name = docker_network.backend.name
  }

  restart = "unless-stopped"
}

resource "docker_container" "app" {
  name  = "app"
  image = docker_image.node.image_id

  env = [
    "DATABASE_URL=postgresql://user:pass@db:5432/mydb"
  ]

  networks_advanced {
    name = docker_network.frontend.name
  }

  networks_advanced {
    name = docker_network.backend.name
  }

  depends_on = [docker_container.db]
  restart    = "unless-stopped"
}

resource "docker_container" "web" {
  name  = "web"
  image = docker_image.nginx.image_id

  ports {
    internal = 80
    external = 8080
  }

  volumes {
    host_path      = "${path.cwd}/nginx.conf"
    container_path = "/etc/nginx/nginx.conf"
    read_only      = true
  }

  networks_advanced {
    name = docker_network.frontend.name
  }

  depends_on = [docker_container.app]
  restart    = "unless-stopped"
}

Exemple Complet : Déployer une Stack Multi-Conteneurs

Passons maintenant à un exemple réaliste et complet. Nous allons déployer une stack composée de :

  • Nginx comme reverse proxy (port 80)
  • Une application Node.js (API REST)
  • PostgreSQL comme base de données
  • Redis comme cache
  • Adminer comme interface d'administration de la base de données

Structure du projet

docker-terraform-stack/
├── main.tf              # Configuration principale
├── variables.tf         # Variables
├── outputs.tf           # Outputs
├── versions.tf          # Versions des providers
├── terraform.tfvars     # Valeurs des variables
└── config/
    └── nginx.conf       # Configuration Nginx

versions.tf

terraform {
  required_version = ">= 1.0.0"

  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "~> 3.0"
    }
  }
}

provider "docker" {}

variables.tf

variable "project_name" {
  description = "Nom du projet"
  type        = string
  default     = "mystack"
}

variable "environment" {
  description = "Environnement de déploiement"
  type        = string
  default     = "development"
}

# Configuration PostgreSQL
variable "postgres_version" {
  description = "Version de l'image PostgreSQL"
  type        = string
  default     = "16-alpine"
}

variable "postgres_db" {
  description = "Nom de la base de données"
  type        = string
  default     = "appdb"
}

variable "postgres_user" {
  description = "Utilisateur PostgreSQL"
  type        = string
  default     = "appuser"
}

variable "postgres_password" {
  description = "Mot de passe PostgreSQL"
  type        = string
  sensitive   = true
}

# Configuration Redis
variable "redis_version" {
  description = "Version de l'image Redis"
  type        = string
  default     = "7-alpine"
}

# Configuration Nginx
variable "nginx_version" {
  description = "Version de l'image Nginx"
  type        = string
  default     = "1.25-alpine"
}

variable "nginx_external_port" {
  description = "Port externe pour Nginx"
  type        = number
  default     = 80
}

# Configuration de l'application
variable "app_image" {
  description = "Image Docker de l'application"
  type        = string
  default     = "node:20-alpine"
}

variable "app_port" {
  description = "Port interne de l'application"
  type        = number
  default     = 3000
}

variable "app_replicas" {
  description = "Nombre de répliques de l'application"
  type        = number
  default     = 2
}

# Adminer
variable "adminer_external_port" {
  description = "Port externe pour Adminer"
  type        = number
  default     = 8080
}

main.tf

# ============================================
# IMAGES DOCKER
# ============================================
resource "docker_image" "nginx" {
  name = "nginx:${var.nginx_version}"
}

resource "docker_image" "app" {
  name = var.app_image
}

resource "docker_image" "postgres" {
  name = "postgres:${var.postgres_version}"
}

resource "docker_image" "redis" {
  name = "redis:${var.redis_version}"
}

resource "docker_image" "adminer" {
  name = "adminer:latest"
}

# ============================================
# RÉSEAUX
# ============================================

# Réseau frontend : accessible depuis l'extérieur
resource "docker_network" "frontend" {
  name   = "${var.project_name}-frontend"
  driver = "bridge"

  ipam_config {
    subnet  = "172.28.0.0/16"
    gateway = "172.28.0.1"
  }

  labels {
    label = "project"
    value = var.project_name
  }
}

# Réseau backend : isolé, pour la communication interne
resource "docker_network" "backend" {
  name     = "${var.project_name}-backend"
  driver   = "bridge"
  internal = true

  ipam_config {
    subnet  = "172.29.0.0/16"
    gateway = "172.29.0.1"
  }

  labels {
    label = "project"
    value = var.project_name
  }
}

# ============================================
# VOLUMES
# ============================================
resource "docker_volume" "postgres_data" {
  name = "${var.project_name}-postgres-data"

  labels {
    label = "project"
    value = var.project_name
  }
  labels {
    label = "service"
    value = "postgres"
  }
}

resource "docker_volume" "redis_data" {
  name = "${var.project_name}-redis-data"

  labels {
    label = "project"
    value = var.project_name
  }
  labels {
    label = "service"
    value = "redis"
  }
}

# ============================================
# BASE DE DONNÉES POSTGRESQL
# ============================================
resource "docker_container" "postgres" {
  name  = "${var.project_name}-postgres"
  image = docker_image.postgres.image_id

  restart = "unless-stopped"

  env = [
    "POSTGRES_DB=${var.postgres_db}",
    "POSTGRES_USER=${var.postgres_user}",
    "POSTGRES_PASSWORD=${var.postgres_password}",
    "PGDATA=/var/lib/postgresql/data/pgdata"
  ]

  volumes {
    volume_name    = docker_volume.postgres_data.name
    container_path = "/var/lib/postgresql/data"
  }

  networks_advanced {
    name    = docker_network.backend.name
    aliases = ["db", "postgres"]
  }

  healthcheck {
    test         = ["CMD-SHELL", "pg_isready -U ${var.postgres_user} -d ${var.postgres_db}"]
    interval     = "10s"
    timeout      = "5s"
    retries      = 5
    start_period = "30s"
  }

  labels {
    label = "project"
    value = var.project_name
  }
  labels {
    label = "service"
    value = "postgres"
  }
}

# ============================================
# CACHE REDIS
# ============================================
resource "docker_container" "redis" {
  name  = "${var.project_name}-redis"
  image = docker_image.redis.image_id

  restart = "unless-stopped"

  command = [
    "redis-server",
    "--appendonly", "yes",
    "--maxmemory", "256mb",
    "--maxmemory-policy", "allkeys-lru"
  ]

  volumes {
    volume_name    = docker_volume.redis_data.name
    container_path = "/data"
  }

  networks_advanced {
    name    = docker_network.backend.name
    aliases = ["cache", "redis"]
  }

  healthcheck {
    test         = ["CMD", "redis-cli", "ping"]
    interval     = "10s"
    timeout      = "5s"
    retries      = 3
    start_period = "10s"
  }

  labels {
    label = "project"
    value = var.project_name
  }
  labels {
    label = "service"
    value = "redis"
  }
}

# ============================================
# APPLICATION (avec répliques)
# ============================================
resource "docker_container" "app" {
  count = var.app_replicas

  name  = "${var.project_name}-app-${count.index + 1}"
  image = docker_image.app.image_id

  restart = "unless-stopped"

  env = [
    "NODE_ENV=${var.environment}",
    "PORT=${var.app_port}",
    "DATABASE_HOST=db",
    "DATABASE_PORT=5432",
    "DATABASE_NAME=${var.postgres_db}",
    "DATABASE_USER=${var.postgres_user}",
    "DATABASE_PASSWORD=${var.postgres_password}",
    "REDIS_HOST=cache",
    "REDIS_PORT=6379",
    "INSTANCE_ID=${count.index + 1}"
  ]

  # Connecté aux deux réseaux
  networks_advanced {
    name    = docker_network.frontend.name
    aliases = ["app-${count.index + 1}"]
  }

  networks_advanced {
    name    = docker_network.backend.name
    aliases = ["app-${count.index + 1}"]
  }

  healthcheck {
    test         = ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:${var.app_port}/health"]
    interval     = "30s"
    timeout      = "10s"
    retries      = 3
    start_period = "40s"
  }

  depends_on = [
    docker_container.postgres,
    docker_container.redis
  ]

  labels {
    label = "project"
    value = var.project_name
  }
  labels {
    label = "service"
    value = "app"
  }
  labels {
    label = "replica"
    value = tostring(count.index + 1)
  }
}

# ============================================
# REVERSE PROXY NGINX
# ============================================
resource "docker_container" "nginx" {
  name  = "${var.project_name}-nginx"
  image = docker_image.nginx.image_id

  restart = "unless-stopped"

  ports {
    internal = 80
    external = var.nginx_external_port
  }

  # Montage de la configuration Nginx
  upload {
    content = templatefile("${path.module}/config/nginx.conf.tpl", {
      app_port     = var.app_port
      app_replicas = var.app_replicas
      project_name = var.project_name
    })
    file = "/etc/nginx/nginx.conf"
  }

  networks_advanced {
    name    = docker_network.frontend.name
    aliases = ["nginx", "proxy"]
  }

  healthcheck {
    test         = ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost/health"]
    interval     = "15s"
    timeout      = "5s"
    retries      = 3
    start_period = "10s"
  }

  depends_on = [docker_container.app]

  labels {
    label = "project"
    value = var.project_name
  }
  labels {
    label = "service"
    value = "nginx"
  }
}

# ============================================
# ADMINER (Administration BDD)
# ============================================
resource "docker_container" "adminer" {
  name  = "${var.project_name}-adminer"
  image = docker_image.adminer.image_id

  restart = "unless-stopped"

  ports {
    internal = 8080
    external = var.adminer_external_port
  }

  env = [
    "ADMINER_DEFAULT_SERVER=db",
    "ADMINER_DESIGN=dracula"
  ]

  # Connecté au frontend (pour l'accès web) et backend (pour la BDD)
  networks_advanced {
    name = docker_network.frontend.name
  }

  networks_advanced {
    name = docker_network.backend.name
  }

  depends_on = [docker_container.postgres]

  labels {
    label = "project"
    value = var.project_name
  }
  labels {
    label = "service"
    value = "adminer"
  }
}

outputs.tf

# outputs.tf
output "nginx_url" {
  description = "URL du reverse proxy Nginx"
  value       = "http://localhost:${var.nginx_external_port}"
}

output "adminer_url" {
  description = "URL de l'interface Adminer"
  value       = "http://localhost:${var.adminer_external_port}"
}

output "app_container_ids" {
  description = "IDs des conteneurs applicatifs"
  value       = docker_container.app[*].id
}

output "app_container_names" {
  description = "Noms des conteneurs applicatifs"
  value       = docker_container.app[*].name
}

output "postgres_container_id" {
  description = "ID du conteneur PostgreSQL"
  value       = docker_container.postgres.id
}

output "network_ids" {
  description = "IDs des réseaux Docker"
  value = {
    frontend = docker_network.frontend.id
    backend  = docker_network.backend.id
  }
}

output "volume_names" {
  description = "Noms des volumes Docker"
  value = {
    postgres = docker_volume.postgres_data.name
    redis    = docker_volume.redis_data.name
  }
}

terraform.tfvars

# terraform.tfvars
project_name = "monapp"
environment  = "development"

# PostgreSQL
postgres_version  = "16-alpine"
postgres_db       = "monapp_db"
postgres_user     = "monapp_user"
postgres_password = "un-mot-de-passe-securise"

# Redis
redis_version = "7-alpine"

# Nginx
nginx_version       = "1.25-alpine"
nginx_external_port = 80

# Application
app_image    = "node:20-alpine"
app_port     = 3000
app_replicas = 2

# Adminer
adminer_external_port = 8080

Déploiement de la stack

# Initialiser Terraform
terraform init

# Voir le plan d'exécution
terraform plan

# Déployer la stack
terraform apply

# Vérifier les conteneurs créés
docker ps

# Voir les outputs
terraform output

# Accéder à l'application
curl http://localhost

# Accéder à Adminer
open http://localhost:8080

Gestion du Cycle de Vie des Conteneurs

Terraform offre des fonctionnalités avancées pour gérer le cycle de vie des conteneurs Docker :

Mise à jour des conteneurs

Lorsque vous changez la version d'une image ou une variable d'environnement, Terraform détecte le changement et recrée le conteneur concerné :

# Changer la version de l'image dans terraform.tfvars
# postgres_version = "16.1-alpine"

# Voir les changements
terraform plan
# => docker_container.postgres will be replaced

# Appliquer
terraform apply

Lifecycle rules

resource "docker_container" "app" {
  # ...

  lifecycle {
    # Créer le nouveau conteneur avant de détruire l'ancien
    create_before_destroy = true

    # Ignorer les changements sur certains attributs
    ignore_changes = [
      image,  # Ne pas recréer si l'image change
    ]
  }
}

Destruction sélective

# Détruire uniquement un conteneur spécifique
terraform destroy -target=docker_container.adminer

# Détruire toute la stack
terraform destroy

Avantages de l'IaC pour Docker

L'utilisation de Terraform pour gérer Docker apporte des avantages significatifs qui vont au-delà de la simple gestion de conteneurs :

Reproductibilité totale

Chaque déploiement produit exactement le même résultat. Le code Terraform décrit l'état souhaité, et Terraform s'assure que la réalité correspond à cet état. Fini les "ça marchait sur ma machine" :

# Le même code produit le même résultat partout
git clone https://github.com/mon-org/docker-stack.git
cd docker-stack
terraform init
terraform apply -var-file="env/production.tfvars"

Traçabilité et audit

Grâce au fichier d'état et au contrôle de version Git, vous savez exactement qui a changé quoi, quand, et pourquoi. Chaque modification passe par un terraform plan qui documente les changements.

Intégration multi-providers

L'un des avantages les plus puissants est la capacité à gérer Docker aux côtés d'autres services dans la même configuration :

# Créer un enregistrement DNS pour le conteneur
resource "cloudflare_record" "app" {
  zone_id = var.cloudflare_zone_id
  name    = "app"
  value   = var.server_ip
  type    = "A"
  proxied = true
}

# Créer un conteneur Docker
resource "docker_container" "app" {
  name  = "app"
  image = docker_image.app.image_id

  ports {
    internal = 3000
    external = 3000
  }
}

# Configurer le monitoring
resource "datadog_monitor" "app_health" {
  name    = "Application Health Check"
  type    = "service check"
  message = "L'application ne répond plus!"

  query = "\"http.can_connect\".over(\"instance:app\").by(\"host\",\"instance\").last(2).count_by_status()"
}

Bonnes Pratiques

Pour tirer le meilleur parti de Terraform avec Docker, suivez ces recommandations :

  • Utilisez des images avec des tags spécifiques plutôt que latest. Cela garantit la reproductibilité des déploiements.
  • Marquez les mots de passe comme sensitive dans les variables pour éviter qu'ils n'apparaissent dans les logs.
  • Utilisez des réseaux internes pour les services qui n'ont pas besoin d'accès Internet (bases de données, caches).
  • Configurez des healthchecks sur tous les conteneurs pour détecter les problèmes rapidement.
  • Persistez les données avec des volumes nommés pour les services stateful.
  • Utilisez depends_on pour contrôler l'ordre de création des conteneurs.
  • Ne stockez pas les secrets dans les fichiers .tfvars versionnés. Utilisez des variables d'environnement ou un gestionnaire de secrets.
  • Utilisez les labels pour organiser et identifier vos ressources Docker.

Conclusion

Terraform avec le provider Docker offre une approche puissante et structurée pour gérer vos environnements conteneurisés. Bien que Docker Compose reste un excellent choix pour le développement local simple, Terraform brille lorsque vous avez besoin de reproductibilité, traçabilité et intégration avec d'autres services d'infrastructure.

L'exemple de stack multi-conteneurs que nous avons construit — avec Nginx, une application, PostgreSQL, Redis et Adminer — illustre comment Terraform peut gérer des déploiements Docker complexes de manière déclarative et maintenable. Chaque composant est versionné, documenté et peut être modifié indépendamment.

Dans notre prochain article, nous explorerons les Terraform Workspaces, un mécanisme puissant pour gérer plusieurs environnements (développement, staging, production) à partir d'une même base de code. Ne manquez pas cette étape clé vers la maîtrise de Terraform !

Vous vous êtes abonné avec succès à CodeClan
Parfait ! Ensuite, complétez le paiement pour obtenir un accès complet à tout le contenu premium.
Erreur ! Impossible de s'inscrire. Lien invalide.
Bienvenue ! Vous vous êtes connecté avec succès.
Erreur ! Impossible de se connecter. Veuillez réessayer.
Succès ! Votre compte est entièrement activé, vous avez maintenant accès à tout le contenu.
Erreur ! Le paiement Stripe a échoué.
Succès ! Vos informations de facturation sont mises à jour.
Erreur ! La mise à jour des informations de facturation a échoué.