
Par défaut, Terraform stocke le state dans un fichier local (terraform.tfstate). C’est suffisant pour apprendre, mais en équipe ou en production, un backend distant permet de partager le state, de le sauvegarder automatiquement et d’éviter les modifications concurrentes. Ce guide vous montre comment passer d’un backend local à un backend S3 compatible (MinIO), en créant votre propre serveur de stockage dans une VM KVM.
Prérequis : avoir compris le rôle du state Terraform et savoir créer une VM avec cloud-init (Première infrastructure).
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Ce qu’est un backend et pourquoi le backend local ne suffit pas en équipe
- Les principaux types de backends (local, S3, consul, pg…)
- Déployer MinIO (stockage S3 compatible) dans une VM KVM avec cloud-init
- Configurer le backend S3 dans Terraform pour pointer vers MinIO
- Migrer un state existant du backend local vers S3
- Vérifier que le state est bien stocké à distance
Ce qu’est un backend
Section intitulée « Ce qu’est un backend »Le backend est le composant de Terraform qui décide où et comment le state est stocké. Il définit deux responsabilités :
- Le stockage : où le fichier
terraform.tfstateest sauvegardé (disque local, serveur distant, bucket S3…) - Le verrouillage : empêcher deux
terraform applysimultanés d’écrire dans le même state
Par défaut, Terraform utilise le backend local : le state est un fichier dans votre dossier de projet. Aucune configuration n’est nécessaire.
Pourquoi le backend local ne suffit pas
Section intitulée « Pourquoi le backend local ne suffit pas »Le backend local fonctionne bien pour un développeur seul sur sa machine. En équipe ou en production, il pose trois problèmes :
| Problème | Conséquence |
|---|---|
| Pas de partage | Chaque développeur a son propre state — les modifications de l’un sont invisibles pour l’autre |
| Pas de sauvegarde | Si le disque tombe en panne, le state est perdu |
| Risque de conflit | Deux apply simultanés corrompent le state |
Les principaux types de backends
Section intitulée « Les principaux types de backends »Terraform supporte plusieurs backends. Voici les plus courants :
| Backend | Stockage | Verrouillage | Cas d’usage |
|---|---|---|---|
| local | Fichier sur disque | Oui (verrou fichier) | Développement solo, apprentissage |
| s3 | Bucket S3 (AWS ou compatible) | Oui (natif ou DynamoDB) | Équipes, production, MinIO on-premise |
| consul | Consul KV store | Oui (natif) | Environnements HashiCorp |
| pg | Base PostgreSQL | Oui (natif) | Infra existante avec PostgreSQL |
| gcs | Google Cloud Storage | Oui (natif) | Projets GCP |
| azurerm | Azure Blob Storage | Oui (natif) | Projets Azure |
Déployer MinIO dans une VM KVM
Section intitulée « Déployer MinIO dans une VM KVM »Pour tester un backend S3 sans compte cloud, vous allez créer une VM qui héberge MinIO. Cloud-init installe et configure MinIO automatiquement au premier démarrage.
Préparer le cloud-init
Section intitulée « Préparer le cloud-init »Le fichier cloud-init installe MinIO, crée un service systemd et prépare un bucket pour le state :
#cloud-configusers: - name: terraform sudo: ALL=(ALL) NOPASSWD:ALL groups: sudo shell: /bin/bash ssh_authorized_keys: - ${ssh_public_key}
package_update: truepackages: - qemu-guest-agent - wget
runcmd: - systemctl enable --now qemu-guest-agent # Télécharger et installer MinIO - wget -q https://dl.min.io/server/minio/release/linux-amd64/minio -O /usr/local/bin/minio - chmod +x /usr/local/bin/minio # Créer l'utilisateur et le répertoire de stockage - useradd -r -s /sbin/nologin minio-user - mkdir -p /data/minio - chown minio-user:minio-user /data/minio # Configurer MinIO via un fichier d'environnement - | cat > /etc/default/minio << 'EOF' MINIO_ROOT_USER=minioadmin MINIO_ROOT_PASSWORD=minioadmin MINIO_VOLUMES="/data/minio" MINIO_OPTS="--console-address :9001" EOF # Créer le service systemd - | cat > /etc/systemd/system/minio.service << 'SVCEOF' [Unit] Description=MinIO Object Storage After=network-online.target Wants=network-online.target
[Service] User=minio-user Group=minio-user EnvironmentFile=/etc/default/minio ExecStart=/usr/local/bin/minio server $MINIO_VOLUMES $MINIO_OPTS Restart=always RestartSec=5
[Install] WantedBy=multi-user.target SVCEOF - systemctl daemon-reload - systemctl enable --now minio.service # Installer le client mc et créer le bucket - wget -q https://dl.min.io/client/mc/release/linux-amd64/mc -O /usr/local/bin/mc - chmod +x /usr/local/bin/mc - sleep 5 - /usr/local/bin/mc alias set local http://127.0.0.1:9000 minioadmin minioadmin - /usr/local/bin/mc mb local/terraform-state --with-versioningCe cloud-init réalise cinq opérations : installation du binaire MinIO, création d’un utilisateur système dédié, écriture d’un service systemd, installation du client mc (MinIO Client), et création du bucket terraform-state avec versioning activé pour recevoir le state.
Exemple de configuration plus propre côté shell :
export AWS_ACCESS_KEY_ID="terraform-backend"export AWS_SECRET_ACCESS_KEY="mot-de-passe-fort"terraform initDans ce cas, supprimez access_key et secret_key du bloc backend pour éviter de laisser des secrets dans versions.tf.
Créer la VM avec Terraform
Section intitulée « Créer la VM avec Terraform »Utilisez cette configuration pour déployer la VM MinIO avec un réseau statique :
# variables.tf — paramètres de la VMvariable "vm_name" { description = "Nom de la VM MinIO" type = string default = "minio-state"}
variable "base_image" { description = "Chemin vers l'image de base Ubuntu" type = string default = "/chemin/vers/ubuntu-24.04-cloudimg.img"}
variable "ssh_public_key" { description = "Chemin vers la clé publique SSH" type = string default = "~/.ssh/id_ed25519.pub"}# main.tf — VM MinIO avec cloud-initresource "libvirt_volume" "disk" { name = "${var.vm_name}.qcow2" pool = "default"
target = { format = { type = "qcow2" } } create = { content = { url = var.base_image } }}
resource "libvirt_cloudinit_disk" "init" { name = "${var.vm_name}-cloud-init.iso"
user_data = templatefile("${path.module}/cloud-init.cfg", { ssh_public_key = trimspace(file(pathexpand(var.ssh_public_key))) })
network_config = file("${path.module}/network-config.cfg")
meta_data = jsonencode({ instance-id = var.vm_name local-hostname = var.vm_name })}
resource "libvirt_domain" "vm" { name = var.vm_name type = "kvm" memory = 1024 memory_unit = "MiB" vcpu = 1
os = { type = "hvm", type_arch = "x86_64", type_machine = "q35" }
devices = { disks = [ { source = { file = { file = libvirt_volume.disk.path } } target = { dev = "vda", bus = "virtio" } driver = { name = "qemu", type = "qcow2" } }, { device = "cdrom" driver = { name = "qemu", type = "raw" } source = { file = { file = libvirt_cloudinit_disk.init.path } } target = { dev = "sda", bus = "sata" } read_only = true } ] interfaces = [{ model = { type = "virtio" } source = { network = { network = "default" } } }] }}La configuration réseau attribue une IP statique à la VM pour que le endpoint MinIO soit prévisible :
# network-config.cfgversion: 2ethernets: enp1s0: dhcp4: false addresses: [192.168.122.50/24] routes: [{to: default, via: 192.168.122.1}] nameservers: {addresses: [192.168.122.1, 8.8.8.8]}-
Déployez la VM
Fenêtre de terminal terraform initterraform apply -auto-approveTerraform crée le volume, l’image cloud-init et la VM. Résultat attendu :
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.Outputs:minio_endpoint = "http://192.168.122.50:9000"minio_console = "http://192.168.122.50:9001"vm_name = "minio-state" -
Attendez la fin de cloud-init
Cloud-init télécharge MinIO et crée le bucket. Patientez 1 à 2 minutes puis vérifiez :
Fenêtre de terminal ssh terraform@192.168.122.50 "cloud-init status"status: done -
Vérifiez que MinIO fonctionne
Fenêtre de terminal ssh terraform@192.168.122.50 "systemctl status minio --no-pager | head -6"● minio.service - MinIO Object StorageLoaded: loaded (/etc/systemd/system/minio.service; enabled; preset: enabled)Active: active (running) since Tue 2026-03-31 11:51:09 UTC; 20s agoMain PID: 3210 (minio) -
Vérifiez que le bucket existe
Fenêtre de terminal ssh terraform@192.168.122.50 "sudo mc alias set local http://127.0.0.1:9000 minioadmin minioadmin && sudo mc ls local/"Added `local` successfully.[2026-03-31 11:51:19 UTC] 0B terraform-state/
Configurer le backend S3 dans Terraform
Section intitulée « Configurer le backend S3 dans Terraform »Créez un nouveau projet Terraform (ou modifiez un existant) pour utiliser le backend S3.
Le bloc backend se place dans le bloc terraform {}, avant les required_providers.
Commencez par exporter les identifiants dans votre shell :
export AWS_ACCESS_KEY_ID="minioadmin"export AWS_SECRET_ACCESS_KEY="minioadmin"Ensuite, utilisez un bloc backend sans secrets en clair :
# versions.tf — avec backend S3 pointant vers MinIOterraform { required_version = ">= 1.11.0"
backend "s3" { # Nom du bucket créé par cloud-init bucket = "terraform-state" # Chemin du state dans le bucket (organisez par projet) key = "demo/terraform.tfstate" # Région obligatoire, mais ignorée par MinIO region = "us-east-1"
# Endpoint MinIO (remplace le endpoint AWS par défaut) endpoints = { s3 = "http://192.168.122.50:9000" }
# Désactiver les vérifications spécifiques à AWS skip_credentials_validation = true skip_metadata_api_check = true skip_requesting_account_id = true use_path_style = true skip_region_validation = true }
required_providers { libvirt = { source = "dmacvicar/libvirt" version = "~> 0.8" } }}
provider "libvirt" { uri = "qemu:///system"}Chaque paramètre a un rôle précis :
| Paramètre | Rôle |
|---|---|
bucket | Nom du bucket S3 qui contient le state |
key | Chemin du fichier state dans le bucket (permet plusieurs projets dans un bucket) |
region | Obligatoire pour le provider S3, mais ignoré par MinIO |
endpoints.s3 | URL du serveur S3 (MinIO au lieu d’AWS) |
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY | Identifiants lus depuis l’environnement au moment de terraform init |
skip_credentials_validation | Désactive la vérification des identifiants via l’API AWS IAM |
skip_metadata_api_check | Désactive l’appel au service de métadonnées EC2 |
skip_requesting_account_id | Désactive la résolution de l’ID de compte AWS |
use_path_style | Utilise le format endpoint/bucket/key au lieu de bucket.endpoint/key |
Initialiser et appliquer
Section intitulée « Initialiser et appliquer »-
Initialisez le projet
Fenêtre de terminal terraform initLe message confirme la connexion au backend S3 :
Successfully configured the backend "s3"! Terraform will automaticallyuse this backend unless the backend configuration changes. -
Créez une ressource de test
main.tf resource "libvirt_volume" "test" {name = "backend-s3-demo.qcow2"pool = "default"target = { format = { type = "qcow2" } }create = { content = { url = "/chemin/vers/ubuntu-24.04-cloudimg.img" } }}output "volume_name" {value = libvirt_volume.test.name} -
Appliquez
Fenêtre de terminal terraform apply -auto-approveApply complete! Resources: 1 added, 0 changed, 0 destroyed.Outputs:volume_name = "backend-s3-demo.qcow2"volume_path = "/var/lib/libvirt/images/backend-s3-demo.qcow2" -
Vérifiez : pas de state local
Fenêtre de terminal ls terraform.tfstate 2>&1ls: cannot access 'terraform.tfstate': No such file or directoryLe state n’est plus sur votre disque — il est dans MinIO.
-
Vérifiez : state dans MinIO
Fenêtre de terminal ssh terraform@192.168.122.50 "sudo mc ls --recursive local/terraform-state/"[2026-03-31 11:52:26 UTC] 1.9KiB STANDARD demo/terraform.tfstate
Examiner le state distant
Section intitulée « Examiner le state distant »Le state stocké dans MinIO est un fichier JSON identique à celui du backend local :
ssh terraform@192.168.122.50 \ "sudo mc cat local/terraform-state/demo/terraform.tfstate" | python3 -m json.tool{ "version": 4, "terraform_version": "1.14.3", "serial": 1, "lineage": "12376cd7-123c-5134-1737-b49bcd0aaafd", "outputs": { "volume_name": { "value": "backend-s3-demo.qcow2", "type": "string" } }, "resources": [ { "mode": "managed", "type": "libvirt_volume", "name": "test", "provider": "provider[\"registry.terraform.io/dmacvicar/libvirt\"]", "instances": [ { "attributes": { "name": "backend-s3-demo.qcow2", "path": "/var/lib/libvirt/images/backend-s3-demo.qcow2", "pool": "default" } } ] } ]}Comme vous pouvez le constater, la structure est identique au state local décrit dans Comprendre le state : même version, même serial, même lineage. Seul l’emplacement physique du fichier a changé.
Migrer un state existant vers S3
Section intitulée « Migrer un state existant vers S3 »Vous avez probablement des projets Terraform qui utilisent déjà le backend local. Terraform propose une commande de migration intégrée.
Scénario : projet avec state local
Section intitulée « Scénario : projet avec state local »Vous avez un projet fonctionnel avec un terraform.tfstate local :
ls terraform.tfstate-rw-rw-r-- 1 bob bob 1790 mars 31 13:53 terraform.tfstate-
Ajoutez le bloc backend dans
versions.tfInsérez le bloc
backend "s3" { ... }(comme dans la section précédente) dans votre blocterraform {}. -
Lancez la migration
Fenêtre de terminal terraform init -migrate-stateN’utilisez pas un simple
terraform initici : lors d’un changement de backend, le flag-migrate-stateest celui qui dit explicitement à Terraform de reprendre l’état existant au lieu de repartir comme si le backend était neuf.Terraform détecte l’ancien state local et propose de le copier :
Do you want to copy existing state to the new backend?Pre-existing state was found while migrating the previous "local" backendto the newly configured "s3" backend. No existing state was found in thenewly configured "s3" backend. Do you want to copy this state to the new"s3" backend? Enter "yes" to copy and "no" to start with an empty state.Enter a value: yesRépondez yes pour copier le state.
Successfully configured the backend "s3"! Terraform will automaticallyuse this backend unless the backend configuration changes. -
Vérifiez que le state fonctionne
Fenêtre de terminal terraform state listlibvirt_volume.migration_testFenêtre de terminal terraform planNo changes. Your infrastructure matches the configuration. -
Confirmez dans MinIO
Fenêtre de terminal ssh terraform@192.168.122.50 "sudo mc ls --recursive local/terraform-state/"[2026-03-31 11:55:26 UTC] 1.7KiB STANDARD migration-demo/terraform.tfstate -
Constatez l’état local après migration
Fenêtre de terminal ls -la terraform.tfstate*-rw-rw-r-- 1 bob bob 0 mars 31 13:55 terraform.tfstate-rw-rw-r-- 1 bob bob 1790 mars 31 13:55 terraform.tfstate.backupLe fichier
terraform.tfstatelocal est vidé (0 octet). Terraform a créé un backup de l’ancien state au cas où la migration échouerait. Vous pouvez supprimer ces fichiers une fois la migration confirmée.
Vérifier et récupérer en cas d’échec de migration
Section intitulée « Vérifier et récupérer en cas d’échec de migration »Avant de supprimer les artefacts locaux après une migration, vérifiez toujours les trois points suivants :
terraform state listfonctionne bien avec le nouveau backend- le fichier existe réellement dans MinIO avec
mc ls --recursive - votre
terraform.tfstate.backuplocal est conservé tant que ces deux vérifications ne sont pas terminées
Si terraform init -migrate-state échoue ou si vous avez un doute sur la copie :
- ne supprimez ni
terraform.tfstateniterraform.tfstate.backup - retirez temporairement le bloc
backendpour revenir au backend local - relancez
terraform init, puis vérifiez le state local avecterraform state list - faites une sauvegarde manuelle avec
terraform state pull > state-avant-remigration.json - recommencez ensuite la migration avec
terraform init -migrate-state
Organiser les states dans un bucket
Section intitulée « Organiser les states dans un bucket »Avec un seul bucket, la clé (key) permet d’organiser les states par projet :
terraform-state/ ← bucket├── production/│ ├── reseau/terraform.tfstate│ └── vms/terraform.tfstate├── staging/│ └── vms/terraform.tfstate└── dev/ └── terraform.tfstateChaque projet utilise une key différente :
# Projet production/reseaubackend "s3" { bucket = "terraform-state" key = "production/reseau/terraform.tfstate" # ... autres paramètres}
# Projet production/vmsbackend "s3" { bucket = "terraform-state" key = "production/vms/terraform.tfstate" # ... autres paramètres}Dépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Solution |
|---|---|---|
Error: Failed to get existing workspaces lors de init | MinIO injoignable ou bucket inexistant | Vérifier que MinIO tourne (systemctl status minio) et que le bucket existe (mc ls local/) |
Access Denied | Identifiants incorrects | Vérifier access_key et secret_key |
BucketRegionError ou erreur de région | skip_region_validation manquant | Ajouter skip_region_validation = true |
timeout lors de init | Paramètres skip_* manquants | Ajouter tous les skip_* listés dans l’exemple |
Error: Backend configuration changed | Modification du bloc backend sans init | Relancer terraform init -migrate-state |
| State vide après migration | Répondu « no » à la question de copie | Retirer le backend, init, re-migrer avec yes |
À retenir
Section intitulée « À retenir »- Le backend définit où Terraform stocke le state — en local (par défaut) ou à distance
- Un backend distant permet le partage, la sauvegarde automatique et le verrouillage du state
- Le backend S3 fonctionne avec tout serveur compatible S3 : AWS, MinIO, Ceph, Wasabi
- MinIO est un serveur S3 open source que vous pouvez déployer en quelques minutes dans une VM
- Activez le versioning du bucket pour pouvoir récupérer une ancienne version du state en cas d’écrasement
- La migration d’un backend local vers S3 se fait avec
terraform init -migrate-state— en gardant le backup local jusqu’aux vérifications finales - Les paramètres
skip_*sont indispensables pour les serveurs S3 non-AWS (MinIO, Ceph…) - Organisez vos states dans le bucket avec des clés hiérarchiques :
environnement/composant/terraform.tfstate