man opentofu
OpenTofu — Cheat Sheet
Référence rapide pour l'infrastructure as code avec OpenTofu (fork open-source de Terraform).
Référence rapide pour l’infrastructure as code avec OpenTofu (fork open-source de Terraform).
Concepts clés
| Concept | Description |
|---|---|
| Provider | Plugin d’interaction avec une API (AWS, Proxmox, Docker…) |
| Resource | Objet d’infrastructure géré (resource "type" "nom") |
| Data source | Lecture d’une ressource existante non gérée par Tofu |
| Variable | Paramètre d’entrée du module |
| Output | Valeur exportée du module |
| Module | Ensemble de ressources réutilisables |
| State | Fichier de suivi de l’état de l’infra (terraform.tfstate) |
| Workspace | Isolation d’environnements (dev/staging/prod) |
| Backend | Stockage du state (local, S3, GitLab…) |
CLI — Commandes essentielles
Cycle de vie de base
tofu init # initialiser le répertoire (télécharger les providers)tofu init -upgrade # mettre à jour les providerstofu init -backend-config=backend.hcl # avec config backend externe
tofu validate # valider la syntaxe HCLtofu fmt # formater les fichiers .tftofu fmt -recursive # récursif (sous-dossiers)tofu fmt -check # vérifier sans modifier (CI)
tofu plan # calculer les changements (dry-run)tofu plan -out=plan.tfplan # sauvegarder le plantofu plan -var="env=production" # avec variabletofu plan -var-file=prod.tfvars # avec fichier de variablestofu plan -target=resource.type.name # cibler une ressource spécifiquetofu plan -refresh=false # ne pas rafraîchir le state
tofu apply # appliquer les changements (confirmation demandée)tofu apply -auto-approve # sans confirmation (CI/CD)tofu apply plan.tfplan # appliquer un plan sauvegardétofu apply -replace=resource.type.name # forcer la recréation
tofu destroy # détruire toutes les ressourcestofu destroy -auto-approve # sans confirmationtofu destroy -target=resource.type.name # détruire une ressourceGestion du state
tofu show # afficher le state lisiblementtofu show plan.tfplan # afficher un plan sauvegardétofu state list # lister les ressources dans le statetofu state show resource.type.nom # détails d'une ressourcetofu state mv src dst # renommer/déplacer une ressource dans le statetofu state rm resource.type.nom # retirer du state (sans détruire)tofu state pull # afficher le state distant bruttofu state push state.tfstate # pousser un state local
tofu import resource.type.nom id # importer une ressource existantetofu refresh # synchroniser le state avec la réalité
tofu output # afficher tous les outputstofu output nom_output # output spécifiquetofu output -json # format JSONWorkspaces
tofu workspace list # lister les workspacestofu workspace new staging # créer un workspacetofu workspace select production # changer de workspacetofu workspace show # workspace actueltofu workspace delete staging # supprimer (doit être vide)Structure de projet
infra/├── main.tf # ressources principales├── variables.tf # déclarations des variables├── outputs.tf # déclarations des outputs├── providers.tf # configuration des providers├── versions.tf # contraintes de version├── terraform.tfvars # valeurs des variables (ne pas committer si secrets)├── dev.tfvars # variables pour dev├── prod.tfvars # variables pour prod└── modules/ ├── lxc/ │ ├── main.tf │ ├── variables.tf │ └── outputs.tf └── docker-stack/ ├── main.tf ├── variables.tf └── outputs.tfSyntaxe HCL
Providers
# versions.tfterraform { required_version = ">= 1.6.0"
required_providers { proxmox = { source = "bpg/proxmox" version = "~> 0.50" } docker = { source = "kreuzwerker/docker" version = "~> 3.0" } }
backend "http" { address = "https://gitlab.domain.tld/api/v4/projects/1/terraform/state/homelab" lock_address = "https://gitlab.domain.tld/api/v4/projects/1/terraform/state/homelab/lock" unlock_address = "https://gitlab.domain.tld/api/v4/projects/1/terraform/state/homelab/lock" lock_method = "POST" unlock_method = "DELETE" username = "tofu" password = var.gitlab_token retry_wait_min = 5 }}
# providers.tfprovider "proxmox" { endpoint = "https://proxmox.internal:8006" username = var.proxmox_user password = var.proxmox_password insecure = false}Variables
# variables.tfvariable "environment" { type = string description = "Environnement de déploiement" default = "dev"
validation { condition = contains(["dev", "staging", "prod"], var.environment) error_message = "L'environnement doit être dev, staging ou prod." }}
variable "vm_count" { type = number default = 1}
variable "tags" { type = list(string) default = []}
variable "config" { type = object({ cpu = number memory = number disk = string }) default = { cpu = 2 memory = 2048 disk = "32G" }}
variable "db_password" { type = string sensitive = true # masqué dans les logs}# terraform.tfvarsenvironment = "prod"vm_count = 3tags = ["homelab", "production"]Ressources
# Resource basiqueresource "docker_container" "nginx" { name = "nginx" image = docker_image.nginx.image_id
ports { internal = 80 external = 8080 }
volumes { container_path = "/etc/nginx/conf.d" host_path = "/opt/nginx/conf.d" read_only = true }
labels { label = "traefik.enable" value = "true" }
restart = "unless-stopped"}
# LXC sur Proxmox (provider bpg/proxmox)resource "proxmox_virtual_environment_container" "app" { node_name = "pve" vm_id = 200
description = "App LXC - ${var.environment}" tags = var.tags
initialization { hostname = "lxc-app-${var.environment}"
ip_config { ipv4 { address = "10.0.0.20/24" gateway = "10.0.0.1" } }
user_account { keys = [file("~/.ssh/id_ed25519.pub")] } }
cpu { cores = var.config.cpu }
memory { dedicated = var.config.memory }
disk { datastore_id = "local-lvm" size = 32 }
network_interface { name = "eth0" bridge = "vmbr0" }
operating_system { template_file_id = "local:vztmpl/debian-12-standard_12.2-1_amd64.tar.zst" type = "debian" }
started = true unprivileged = true}Data sources
# Lire une ressource existante (non gérée par Tofu)data "proxmox_virtual_environment_nodes" "nodes" {}
data "docker_image" "nginx" { name = "nginx:latest"}
# Utiliser dans une ressourceresource "docker_container" "nginx" { image = data.docker_image.nginx.image_id name = "nginx"}Outputs
# outputs.tfoutput "container_ip" { value = proxmox_virtual_environment_container.app.initialization[0].ip_config[0].ipv4[0].address description = "Adresse IP du conteneur LXC"}
output "container_id" { value = proxmox_virtual_environment_container.app.vm_id}
output "db_connection" { value = "postgres://user:${var.db_password}@${resource.db.host}:5432/app" sensitive = true # masqué dans tofu output}Meta-arguments et fonctions
Meta-arguments
# count — créer N instancesresource "proxmox_virtual_environment_container" "worker" { count = var.vm_count vm_id = 300 + count.index
initialization { hostname = "worker-${count.index + 1}" }}
# for_each — itérer sur une map/setvariable "services" { default = { nginx = { port = 80 } redis = { port = 6379 } }}
resource "docker_container" "service" { for_each = var.services name = each.key
ports { internal = each.value.port external = each.value.port }}
# depends_on — dépendance expliciteresource "docker_container" "app" { depends_on = [docker_container.db]}
# lifecycle — contrôler la gestion du cycle de vieresource "docker_container" "app" { lifecycle { prevent_destroy = true # empêcher la destruction accidentelle ignore_changes = [image] # ignorer les changements sur image replace_triggered_by = [var.config] # recréer si cette valeur change }}Fonctions courantes
# Chaînes"${var.env}-server" # interpolationformat("server-%03d", count.index) # formatageupper("hello") # "HELLO"lower("HELLO") # "hello"replace("hello world", " ", "-") # "hello-world"split(",", "a,b,c") # ["a", "b", "c"]join("-", ["a", "b", "c"]) # "a-b-c"trimspace(" hello ") # "hello"
# Collectionslength(var.tags) # longueurcontains(var.tags, "prod") # appartenanceconcat(list1, list2) # fusionner des listesflatten([[1, 2], [3]]) # [1, 2, 3]toset(["a", "b", "a"]) # {"a", "b"}tomap({a = 1, b = 2})
# Conditionscondition ? valeur_si_vrai : valeur_si_fauxvar.env == "prod" ? "t3.large" : "t3.micro"
# Fichiers et encodagefile("~/.ssh/id_ed25519.pub") # lire un fichierfilebase64("cert.pem") # en base64base64encode("string")jsonencode({key = "value"}) # sérialiser en JSONyamlencode({key = "value"}) # sérialiser en YAML
# Réseaucidrhost("10.0.0.0/24", 10) # "10.0.0.10"cidrsubnet("10.0.0.0/16", 8, 1) # "10.0.1.0/24"Modules
Créer un module
# modules/lxc/variables.tfvariable "hostname" { type = string }variable "ip_address" { type = string }variable "cpu" { type = number; default = 2 }variable "memory" { type = number; default = 2048 }
# modules/lxc/main.tfresource "proxmox_virtual_environment_container" "this" { # ...}
# modules/lxc/outputs.tfoutput "vm_id" { value = proxmox_virtual_environment_container.this.vm_id}Appeler un module
# main.tfmodule "nginx_lxc" { source = "./modules/lxc"
hostname = "lxc-nginx" ip_address = "10.0.0.10/24" cpu = 2 memory = 1024}
module "db_lxc" { source = "app.terraform.io/org/lxc/proxmox" # module distant (registry) version = "~> 1.0"
hostname = "lxc-db" ip_address = "10.0.0.11/24"}
# Utiliser les outputs d'un moduleoutput "nginx_id" { value = module.nginx_lxc.vm_id}Backends — Stockage du state
GitLab HTTP Backend
terraform { backend "http" { address = "https://gitlab.domain.tld/api/v4/projects/PROJECT_ID/terraform/state/STATE_NAME" lock_address = "https://gitlab.domain.tld/api/v4/projects/PROJECT_ID/terraform/state/STATE_NAME/lock" unlock_address = "https://gitlab.domain.tld/api/v4/projects/PROJECT_ID/terraform/state/STATE_NAME/lock" lock_method = "POST" unlock_method = "DELETE" username = "tofu" password = var.gitlab_token # ou TF_HTTP_PASSWORD en env var retry_wait_min = 5 }}S3-compatible (Minio, AWS)
terraform { backend "s3" { bucket = "tofu-state" key = "homelab/terraform.tfstate" region = "us-east-1" endpoint = "https://minio.domain.tld"
access_key = var.minio_access_key secret_key = var.minio_secret_key
skip_credentials_validation = true skip_metadata_api_check = true skip_region_validation = true force_path_style = true }}Bonnes pratiques
- Ne jamais committer
terraform.tfstateouterraform.tfvarscontenant des secrets dans Git. - Utiliser un backend distant (GitLab, S3/Minio) pour partager le state entre collaborateurs.
- Toujours exécuter
tofu planet le relire avanttofu apply. - Marquer les variables sensibles avec
sensitive = true. - Utiliser
prevent_destroy = truesur les ressources critiques (bases de données, volumes). - Versionner les providers avec
~>(patch flexible) plutôt que de fixer exactement.
# .gitignore recommandé*.tfstate*.tfstate.backup.terraform/*.tfvars # si contient des secrets*.tfplan.terraform.lock.hcl # à committer si reproductibilité souhaitée
# Variables sensibles via variables d'environnement# TF_VAR_db_password=secret tofu apply# ou dans un fichier non commité :# echo 'db_password = "secret"' > secrets.auto.tfvars