De nos jours, l'automatisation des tâches et la création d'infrastructures immuables sont des aspects incontournables. C'est dans cette optique que l'utilisation d'outils comme Terraform ou son alternative libre OpenTofu deviennent particulièrement intéressants pour la création automatique et en masse de conteneurs LXC.

Depuis 2025, j'utilise OpenTofu qui est compatible avec les commandes Terraform. Changez les commandes "terraform" par "tofu". L'article a été mis à jour le 24/01/2025 suite au live sur Twitch de TheRealSeboss666 (blog : https://blog.seboss666.info).

L'idée sous-jacente à cette approche est de déployer des conteneurs pour isoler et héberger différents services, tout en évitant l'usage habituel de Docker ou de Podman. Outre l'aspect sécuritaire qu'offre cette méthode, elle vise également à explorer et à exploiter pleinement les outils disponibles.

Ce document part du principe que vous maîtrisez déjà Terraform, que vous disposez des droits d'administration sur Proxmox et que vous êtes capable d'effectuer des manipulations sans contraintes majeures.

Plutôt que de réinventer la roue, je vous suggère l'article de Quentin, "au final, qu'est-ce qu'un conteneur ?", pour découvrir ce qu'est un conteneur Linux.

L'environnement de travail pour cet article est le suivant :

  • un serveur Proxmox 8.3.x ;
  • OpenTofu 1.9.x ;
  • un conteneur LXC, avec Debian.

Préparation du serveur Proxmox

Pour simplifier les opérations dans vos environnements de test, vous pouvez utiliser le compte root@pam créé lors de l'installation de Proxmox. Ce compte possède tous les privilèges nécessaires pour effectuer des actions sur la plateforme.

Par la suite, je vais créer un utilisateur nommé "terrabot" dans le realm "pve". Ce compte aura un rôle spécifique, doté des droits nécessaires pour la création et la gestion des conteneurs, des machines virtuelles et des disques virtuels.

Création d'un rôle

La première étape est la création d'un rôle. Ce rôle disposera des droits nécessaires pour gérer les machines et leur stockage. Voici donc tous les rôles nécessaires :

  "Datastore.Allocate",
  "Datastore.AllocateSpace",
  "Datastore.AllocateTemplate",
  "Datastore.Audit",
  "Pool.Allocate",
  "Pool.Audit",
  "SDN.Allocate",
  "SDN.Audit",
  "SDN.Use",
  "Sys.Audit",
  "Sys.Console",
  "VM.Allocate",
  "VM.Audit",
  "VM.Backup",
  "VM.Clone",
  "VM.Config.CDROM",
  "VM.Config.CPU",
  "VM.Config.Cloudinit",
  "VM.Config.Disk",
  "VM.Config.HWType",
  "VM.Config.Memory",
  "VM.Config.Network",
  "VM.Config.Options",
  "VM.Console",
  "VM.Migrate",
  "VM.Monitor",
  "VM.PowerMgmt",
  "VM.Snapshot",
  "VM.Snapshot.Rollback",
  "Mapping.Use",
  "Mappping.Audit",
  "Mapping.Modify",
  "Realm.AllocateUser",
  "Permissions.Modify"

Je n'ai pas utilisé Terraform pour créer ce rôle, puisque Terraform a besoin de ce rôle avant de créer des ressources... Terraform-ception. Une autre solution plus rapide est d'utiliser le rôle "PVEAdmin" et de le propager à la racine.

Création d'un utilisateur

Maintenant, créons l'utilisateur "terrabot" ; cet utilisateur sera utilisé exclusivement pour Terraform. Dirigez-vous dans l'onglet "Permissions", puis sur "Users", et cliquez sur le bouton "Create". Créez un nouvel utilisateur dans le realm "pve", ajoutez des informations selon votre besoin et validez les changements.

Par la suite, ajoutez un mot de passe complexe à cet utilisateur (vous ne l'utiliserez probablement pas, mais ajoutez quand même un mot de passe) et validez ces changements.

Création d'un token pour l'utilisateur

Avant-dernière étape, créons un token pour cet utilisateur fraichement créé. Toujours dans l'onglet "Permissions", cliquez sur le lien "API Token" et cliquez sur le bouton "Add". Saisissez l'utilisateur et le nom du token. Ce nom est important, vous en aurez besoin plus loin dans le code Terraform. Ici, j'ai appelé ce token "tf". J'ai décoché la case "Privileges separations" et n'ai pas mis de date d'expiration pour le token. Dans des environnements de production, il est fortement conseillé de renouveler les tokens régulièrement.

Attribution du rôle

Nous sommes au virage final. Attribuons désormais le rôle à notre utilisateur et plus particulièrement au token. Retournez une dernière fois dans l'onglet "Permissions" et sélectionnez "API User token". Choisissez l'utilisateur créé (ici Terrabot) et sélectionnez le rôle "Terraform". Validez les changements et répétez l'opération avec l'utilisateur précédemment créé (dans notre exemple, "terrabot"). Vous allez ainsi avoir dans la fenêtre "Permissions" deux lignes - IMAGE À METTRE.

Le code Terraform complet

Enfin, voici le code complet que j'utilise pour créer des conteneurs sur Proxmox avec Terraform. Le code est assez complet, j'aime bien avoir le maximum d'informations et d'attributs pour préciser au maximum mes ressources.

Fichier "variables.tf" :

### variables.tf
variable "ct_bridge" {
  type = string
}
variable "ct_datastore_storage_location" {
  type = string
}
variable "ct_datastore_template_location" {
  type = string
}
variable "ct_description" {
  type = string
}
variable "ct_disk_size" {
  type    = string
  default = "20"
}
variable "ct_id" {
  type = number
}
variable "ct_ip" {
  type = string
}
variable "ct_name" {
  type = string
}
variable "ct_nic_rate_limit" {
  type = number
}
variable "ct_memory" {
  type = number
}
variable "ct_source_file_path" {
  type = string
}
variable "ct_swap" {
  type = number
}
variable "ct_tags" {
  type = list(string)
}
variable "dns_domain" {
  type = string
}
variable "dns_servers" {
  type = list(string)
}
variable "gateway" {
  type = string
}
variable "node" {
  type = string
}
variable "os_type" {
  type = string
}
variable "pve_api_token" {
  type = string
}
variable "pve_api_user" {
  type = string
}
variable "pve_host_address" {
  type = string
}
variable "ssh_pubkey" {
  type = list(string)
}
variable "tmp_dir" {
  type = string
}

Le fichier "variables.auto.tfvars", est à modifier pour que vous puissiez avoir un conteneur correspondant à votre besoin :

### variables.auto.tfvars
ct_bridge                      = "vmbr0"
ct_datastore_storage_location  = "local"
ct_datastore_template_location = "local"
ct_description                 = "Managed by OpenTofu. Change me :)"
ct_disk_size                   = "20"
ct_id                          = 9999123
ct_ip                          = "192.168.1.3/24"
ct_name                        = "ct-test"
ct_nic_rate_limit              = 100
ct_memory                      = 512
ct_source_file_path            = "http://download.proxmox.com/images/system/debian-12-standard_12.7-1_amd64.tar.zst"
ct_swap                        = 0
ct_tags                        = ["linux", "infra"]
dns_domain                     = "domain.net"
dns_servers                    = ["192.168.1.1"]
gateway                        = "192.168.1.1"
node                           = "pve01"
os_type                        = "debian"
pve_api_token                  = "terrabot@pve!tf=change-my-token"
pve_api_user                   = "terrabot"
pve_host_address               = "https://192.168.1.2:8006"
ssh_pubkey                     = ["changeme"]
tmp_dir                        = "/var/tmp"

Fichier "container.tf" :

### container.tf
# see https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password.html. It will download "hashicorp/random" provider

terraform {
  required_providers {
    proxmox = {
      source  = "bpg/proxmox"
      version = "~> 0.70"
    }
    random = {
      source = "hashicorp/random"
      version = "3.6.3"
    }
  }
}

provider "proxmox" {
  endpoint  = var.pve_host_address
  api_token = var.pve_api_token
  insecure  = true
  ssh {
    agent    = true
    username = var.pve_api_user
  }
  tmp_dir = var.tmp_dir
}

resource "random_password" "container_root_password" {
  length           = 24
  override_special = "_%@"
  special          = true
}

output "container_root_password" {
  value     = random_password.container_root_password.result
  sensitive = true
}

# location of containers templates
resource "proxmox_virtual_environment_file" "debian_container_template" {
  content_type = "vztmpl"
  datastore_id = var.ct_datastore_template_location
  node_name    = var.node

  source_file {
    path = var.ct_source_file_path
  }
}

resource "proxmox_virtual_environment_container" "debian_container" {
  description   = var.ct_description
  node_name     = var.node
  start_on_boot = true
  tags          = var.ct_tags
  unprivileged  = true
  vm_id         = var.ct_id

  cpu {
    architecture = "amd64"
    cores        = 1
  }

  disk {
    datastore_id = var.ct_datastore_storage_location
    size         = var.ct_disk_size
  }

  memory {
    dedicated = var.ct_memory
    swap      = var.ct_swap
  }

  operating_system {
    template_file_id = proxmox_virtual_environment_file.debian_container_template.id
    type             = var.os_type
  }

  initialization {
    hostname = var.ct_name

    dns {
      domain = var.dns_domain
      server = var.dns_server
    }

    ip_config {
      ipv4 {
        address = var.ct_ip
        gateway = var.gateway
      }
    }
    user_account {
      keys     = var.ssh_pubkey
      password = random_password.container_root_password.result
    }
  }
  network_interface {
    name       = var.ct_bridge
    rate_limit = var.ct_nic_rate_limit
  }

  features {
    nesting = true
    fuse    = false
  }
}

Veillez à bien changer les valeurs dans le fichier "variables.auto.tfvars" pour correspondre à votre environnement. L'axe d'amélioration serait d'utiliser cette base de fichiers et en créer un module, permettant d'unifier le code pour plusieurs ressources et itérer en fonction du nombre de conteneurs à créer.

Share this post