
Terraform provisionne des VMs Proxmox de façon déclarative et reproductible : on décrit la machine voulue, Terraform la crée, la modifie ou la détruit. Ce guide utilise le provider bpg/proxmox (le plus actif) pour, à partir d'un template cloud-init, cloner une VM, l'initialiser et récupérer son IP via l'agent. Il couvre la préparation côté Proxmox (rôle à moindre privilège, token API), le code Terraform complet et le cycle init / plan / apply / destroy. Tout a été testé sur Proxmox VE 9.1 avec le provider 0.111. Pour administrateurs qui industrialisent Proxmox.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Créer un rôle à moindre privilège et un token API pour Terraform.
- Construire un template cloud-init Ubuntu réutilisable.
- Configurer le provider
bpg/proxmoxet décrire une VM. - Cloner une VM, l'initialiser par cloud-init et lire son IP.
- Gérer le cycle de vie complet (
apply,destroy).
Prérequis
Section intitulée « Prérequis »- Un Proxmox VE (l'exemple est validé sur 9.1) accessible en API et en SSH.
- Terraform (ou OpenTofu) installé sur votre poste.
- Des notions de Terraform (syntaxe, providers, état).
Préparer Proxmox : rôle, utilisateur et token
Section intitulée « Préparer Proxmox : rôle, utilisateur et token »Par sécurité, Terraform ne se connecte pas en root. On crée un rôle dédié limité aux actions nécessaires, un utilisateur, et un token API.
Créer le rôle à moindre privilège
Section intitulée « Créer le rôle à moindre privilège »pveum role add TerraformProv --privs "VM.Allocate VM.Audit VM.Clone VM.Config.Disk VM.Config.CPU VM.Config.Memory VM.Config.Network VM.Config.Options VM.Config.Cloudinit VM.Config.CDROM VM.Config.HWType VM.PowerMgmt VM.Console VM.Migrate VM.GuestAgent.Audit Datastore.Allocate Datastore.AllocateSpace Datastore.Audit Pool.Allocate Sys.Audit Sys.Modify SDN.Use"Créer l'utilisateur et le token
Section intitulée « Créer l'utilisateur et le token »pveum user add terraform-prov@pvepveum aclmod / -user terraform-prov@pve -role TerraformProvpveum user token add terraform-prov@pve terraform -expire 0 -privsep 0 -comment "Terraform token"L'option -privsep 0 fait que le token hérite des permissions de l'utilisateur. Notez le secret affiché, il ne sera plus jamais réaffiché :
full-tokenid │ terraform-prov@pve!terraformvalue │ 4192f998-78d7-4d8f-a23e-5b32cbf4b157Créer un template cloud-init Ubuntu
Section intitulée « Créer un template cloud-init Ubuntu »Terraform clonera un template. On part d'une cloud image Ubuntu, on y injecte le qemu-guest-agent (indispensable pour remonter l'IP), puis on convertit la VM en template taggé template.
-
Préparer l'image sur le nœud Proxmox :
Fenêtre de terminal wget https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img \-O /tmp/ubuntu-24.04.imgvirt-customize -a /tmp/ubuntu-24.04.img --install qemu-guest-agent -
Créer la VM et importer le disque (syntaxe moderne
import-from) :Fenêtre de terminal qm create 9000 --name ubuntu-2404-template --memory 2048 --cores 2 \--net0 virtio,bridge=vmbr0 --scsihw virtio-scsi-single \--serial0 socket --vga serial0 --agent enabled=1 --ostype l26qm set 9000 --scsi0 srv-pve:0,import-from=/tmp/ubuntu-24.04.imgqm set 9000 --ide2 srv-pve:cloudinitqm set 9000 --boot order=scsi0qm set 9000 --tags template -
Convertir en template :
Fenêtre de terminal qm template 9000
Adaptez le stockage (srv-pve) au vôtre. Le tag template permettra à Terraform de retrouver ce template dynamiquement.
Configurer le provider Terraform
Section intitulée « Configurer le provider Terraform »Créez un fichier main.tf avec le provider bpg/proxmox. Il dialogue avec l'API (token) et en SSH pour les opérations sur fichiers (les snippets cloud-init).
terraform { required_providers { proxmox = { source = "bpg/proxmox" version = "0.111.0" } }}
provider "proxmox" { endpoint = var.endpoint api_token = var.api_token insecure = true
ssh { agent = true username = "root" }}Les variables dans variables.tf :
variable "endpoint" { type = string}
variable "api_token" { type = string sensitive = true}
variable "target_node" { type = string default = "proxmox1"}
variable "vm_hostname" { type = string default = "tf-demo"}Le token ne doit jamais être écrit en clair dans le code. Passez-le par variable d'environnement :
export TF_VAR_api_token='terraform-prov@pve!terraform=4192f998-...'export TF_VAR_endpoint='https://192.168.10.11:8006/'Définir la VM par clonage
Section intitulée « Définir la VM par clonage »Ajoutez à main.tf la source de données qui retrouve le template, le fichier cloud-init et la ressource VM :
data "proxmox_virtual_environment_vms" "template" { tags = ["template"]}
resource "proxmox_virtual_environment_file" "cloud_user_config" { content_type = "snippets" datastore_id = "srv-pve" node_name = var.target_node
source_raw { data = file("${path.module}/cloud-init/user_data.yaml") file_name = "${var.vm_hostname}-ci-user.yaml" }}
resource "proxmox_virtual_environment_vm" "vm" { name = var.vm_hostname node_name = var.target_node on_boot = false tags = ["terraform", "ubuntu"]
agent { enabled = true }
cpu { type = "x86-64-v2-AES" cores = 2 sockets = 1 }
memory { dedicated = 2048 }
network_device { bridge = "vmbr0" model = "virtio" }
scsi_hardware = "virtio-scsi-single"
disk { interface = "scsi0" iothread = true datastore_id = "srv-pve" size = 10 discard = "on" }
clone { vm_id = data.proxmox_virtual_environment_vms.template.vms[0].vm_id }
initialization { datastore_id = "srv-pve" interface = "ide2"
ip_config { ipv4 { address = "dhcp" } }
user_data_file_id = proxmox_virtual_environment_file.cloud_user_config.id }}
output "vm_ipv4" { value = proxmox_virtual_environment_vm.vm.ipv4_addresses}Le fichier cloud-init/user_data.yaml configure la machine au premier démarrage (clé SSH, agent) :
#cloud-confighostname: tf-demopackage_update: truepackages: - qemu-guest-agentusers: - default - name: ubuntu sudo: ALL=(ALL) NOPASSWD:ALL ssh_authorized_keys: - ssh-ed25519 AAAA... votre-cleruncmd: - systemctl enable --now qemu-guest-agentProvisionner : init, plan, apply
Section intitulée « Provisionner : init, plan, apply »terraform initterraform planterraform apply -auto-approveTerraform crée le snippet via SSH puis clone la VM. La sortie réelle montre la création en quelques secondes :
proxmox_virtual_environment_file.cloud_user_config: Creation complete after 1s [id=srv-pve:snippets/tf-demo-ci-user.yaml]proxmox_virtual_environment_vm.vm: Creation complete after 3s [id=101]Apply complete! Resources: 2 added, 0 changed, 0 destroyed.Vérifier la VM
Section intitulée « Vérifier la VM »Une fois l'agent démarré (cloud-init installe qemu-guest-agent), l'IP remonte dans les outputs :
terraform output vm_ipv4# tolist([ ["127.0.0.1"], ["192.168.10.209"] ])La connexion SSH confirme que cloud-init a fait son travail :
ssh ubuntu@192.168.10.209# tf-demo - Ubuntu 24.04.4 LTSDétruire l'infrastructure
Section intitulée « Détruire l'infrastructure »Tout l'intérêt de Terraform : défaire proprement ce qu'il a créé.
terraform destroy -auto-approve# proxmox_virtual_environment_vm.vm: Destruction complete after 2s# Destroy complete! Resources: 2 destroyed.Le template (géré via une source de données, pas par Terraform) reste intact pour les prochains déploiements.
À retenir
Section intitulée « À retenir »- Le provider
bpg/proxmoxest le plus actif ; épinglez une version récente (testé en 0.111). - Créez un rôle à moindre privilège : sur PVE 9,
VM.Monitorn'existe plus etVM.GuestAgent.Auditest requis pour lire l'IP. - Le provider a besoin d'un accès SSH root pour les snippets cloud-init (agent en local, clé privée en CI).
- On clone un template taggé via une source de données, puis on initialise par cloud-init.
- Le token se passe par
TF_VAR_api_token, jamais en clair dans le code.
FAQ : questions fréquentes sur Terraform et Proxmox
Section intitulée « FAQ : questions fréquentes sur Terraform et Proxmox »bpg/proxmox
Deux providers communautaires existent pour Proxmox. Le provider de référence en 2026 estbpg/proxmox, activement maintenu, qui expose les ressources proxmox_virtual_environment_*. L'ancien telmate/proxmox est nettement moins à jour.Épinglez une version récente :terraform {
required_providers {
proxmox = {
source = "bpg/proxmox"
version = "0.111.0"
}
}
}
Un rôle dédié avec pveum
pveum role add TerraformProv --privs "VM.Allocate VM.Audit VM.Clone VM.Config.Disk VM.Config.CPU VM.Config.Memory VM.Config.Network VM.Config.Options VM.Config.Cloudinit VM.Config.CDROM VM.Config.HWType VM.PowerMgmt VM.Console VM.Migrate VM.GuestAgent.Audit Datastore.Allocate Datastore.AllocateSpace Datastore.Audit Pool.Allocate Sys.Audit Sys.Modify SDN.Use"
Les privilèges sont séparés par des espaces. Sur PVE 9, incluez VM.GuestAgent.Audit (lecture de l'IP) et retirez VM.Monitor qui n'existe plus.Privilège agent et qemu-guest-agent
Deux causes à vérifier :- Côté permissions : sans
VM.GuestAgent.Audit, Terraform crée la VM mais renvoie unHTTP 403(error retrieving VM network interfaces from agent). Ajoutez ce privilège au rôle. - Côté VM : le
qemu-guest-agentdoit être installé et actif (via cloud-initpackages: [qemu-guest-agent]).
terraform output affiche l'IP. La VM doit aussi avoir agent { enabled = true }.Source de données + bloc clone
Le template Proxmox est taggé (qm set 9000 --tags template). Terraform le retrouve via une source de données :data "proxmox_virtual_environment_vms" "template" {
tags = ["template"]
}
La ressource VM le clone et applique cloud-init :clone {
vm_id = data.proxmox_virtual_environment_vms.template.vms[0].vm_id
}
Le bloc initialization injecte ensuite la configuration (IP, user_data).SSH en plus du token API
Le token API ne suffit pas pour tout : téléverser des snippets cloud-init nécessite un accès au système de fichiers du nœud. Le provider se connecte donc en SSH (root) en complément.ssh {
agent = true
username = "root"
}
- En local : activez l'agent SSH (
ssh-add). - En CI : remplacez par
private_key = file("/chemin/cle").
proxmox_virtual_environment_file échouent.