Mit Terraform beschreibst Du Infrastruktur in Dateien, statt sie in einer Oberfläche anzuklicken. Der Provider bpg/proxmox übersetzt diese Beschreibung in Aufrufe an die Proxmox-API. Du klonst damit eine VM aus Deiner Debian-13-Vorlage, cloud-init setzt Benutzer und Schlüssel. Terraform merkt sich im State, was es angelegt hat, und zeigt mit terraform plan vor jeder Änderung, was passieren wird.
01Wofür Terraform, wofür Ansible
Terraform legt Dinge an, ändert ihre Größe und löscht sie wieder: VMs, Platten, Netze. Ansible richtet ein, was in der VM läuft: Pakete, Dateien, Dienste. Unsere Faustregel: Bis die VM per SSH erreichbar ist, arbeitet Terraform. Ab da übernimmt Ansible.
02Voraussetzungen
- Eine Proxmox-Übungsumgebung mit der Debian-13-Vorlage (ID 9000, mit Gastagent und cloud-init-Laufwerk).
- Der Benutzer terraform@pve mit API-Token, angelegt wie im Proxmox-Artikel.
- Terraform auf Deinem Arbeitsrechner nach der Anleitung auf developer.hashicorp.com, unter Windows am besten in WSL.
03Den Token sicher übergeben
Der Provider liest Adresse und Token aus Umgebungsvariablen. So steht der Token weder im Code noch in der Shell-Historie:
export PROXMOX_VE_ENDPOINT="https://192.168.178.10:8006/" # Beispiel, Deine Proxmox-Adresse
export PROXMOX_VE_INSECURE=true # nur für das selbst signierte Zertifikat im Labor
read -rsp "Proxmox-Token: " PROXMOX_VE_API_TOKEN && export PROXMOX_VE_API_TOKENBeim read fügst Du den Token im Format terraform@pve!provider=<geheimer-Teil> aus Deinem Passwortmanager ein. Die Eingabe bleibt unsichtbar.
04Eine VM aus der Vorlage klonen
Leg in Deinem Repository das Verzeichnis terraform/uebung/ an und darin die Datei main.tf:
terraform {
required_providers {
proxmox = { source = "bpg/proxmox" }
}
}
# Adresse und Token kommen aus der Umgebung, nie aus dieser Datei
provider "proxmox" {}
variable "ssh_schluessel_datei" {
type = string
default = "~/.ssh/id_ed25519.pub"
}
resource "proxmox_virtual_environment_vm" "uebung" {
name = "debian-uebung-01"
node_name = "pve" # Name Deines Proxmox-Knotens
stop_on_destroy = true
clone {
vm_id = 9000 # ID Deiner Vorlage
full = true
}
agent { enabled = true }
cpu {
cores = 2
type = "x86-64-v2-AES"
}
memory { dedicated = 2048 }
initialization {
ip_config {
ipv4 { address = "dhcp" }
}
user_account {
username = "debian"
keys = [trimspace(file(pathexpand(var.ssh_schluessel_datei)))]
}
}
}
output "ipv4_adressen" {
value = proxmox_virtual_environment_vm.uebung.ipv4_addresses
}Was passiert hier: clone kopiert die Vorlage vollständig, Platte und Netzwerkkarte kommen von dort. initialization füllt das cloud-init-Laufwerk mit Benutzer, Schlüssel und DHCP. Mit agent wartet Terraform nach dem Start, bis der Gastagent die IP-Adresse meldet.
05Der Ablauf: init, plan, apply, destroy
terraform init
terraform fmt && terraform validate
terraform plan -out=uebung.tfplan
terraform apply uebung.tfplaninit lädt den Provider und schreibt die genaue Version in .terraform.lock.hcl. Diese Datei gehört ins Repository. plan vergleicht Deinen Code, den State und den echten Zustand in Proxmox. Er zeigt, was angelegt (+), geändert (~) oder ersetzt (-/+) würde. Lies den Plan jedes Mal ganz: „must be replaced“ heißt, die VM wird gelöscht und neu gebaut.
Ändere danach dedicated = 2048 auf 3072 und lass erneut plan laufen. Terraform zeigt genau eine Änderung. Am Ende räumst Du mit terraform destroy auf, auch hier mit Vorschau und Rückfrage.
06Der State und warum er nicht ins öffentliche Repo gehört
Nach jedem apply schreibt Terraform die Datei terraform.tfstate. Darin steht jede verwaltete Ressource mit allen Attributen. Ohne State weiß Terraform nicht mehr, was ihm gehört.
Der State ist Klartext. Er enthält Adressen und Kennungen, je nach Ressource auch Kennwörter. Variablen mit sensitive = true stehen dort ebenfalls lesbar, die Markierung blendet sie nur in der Ausgabe aus. Dasselbe gilt für gespeicherte Plandateien. Deshalb gehören .terraform/, *.tfstate, *.tfstate.*, *.tfplan und *.tfvars in die .gitignore. Für die Übung reicht der State lokal. Im Team liegt er in einem entfernten Backend mit Verschlüsselung, Sperre und Zugriffsschutz.
07Drift erkennen
Drift heißt: Die Wirklichkeit weicht vom Code ab, zum Beispiel weil jemand den Speicher einer VM in der Weboberfläche geändert hat. terraform plan zeigt das als geplante Änderung zurück auf den Code-Stand. Für Skripte gibt es terraform plan -detailed-exitcode. Es endet mit 0, wenn nichts abweicht, mit 2 bei Änderungen und mit 1 bei einem Fehler.
Bei uns ist eine Änderung erst fertig, wenn ein weiterer plan nach dem Einspielen leer ist.
08Übergabe an Ansible
Nach dem apply zeigt terraform output die IP-Adressen. Die erste Liste gehört meist zur Loopback-Schnittstelle, die Adresse Deiner VM steht dahinter. Trag sie in Dein Ansible-Inventar ein und mach weiter mit Ansible lernen. Für feste Adressen setzt Du statt dhcp eine Adresse wie 192.168.178.51/24 und dazu gateway. Später liest Ansible sein Inventar aus NetBox, dann entfällt das Abtippen.
09Übung
- Leg terraform/uebung/ mit der main.tf und einer passenden .gitignore an.
- Übergib Adresse und Token über die Umgebung und führe terraform init aus.
- Leg die VM mit plan und apply an und melde Dich per ssh debian@<IP-der-VM> an.
- Erhöh den Speicher im Code auf 3072 MB und spiel die Änderung ein.
- Ändere die CPU-Kerne in der Weboberfläche, finde die Drift mit terraform plan -detailed-exitcode und dreh sie per apply zurück.
- Räum mit terraform destroy auf und prüfe mit git status, dass kein State im Repository landet.
Geschafft, wenn: Ein plan direkt nach dem apply keine Änderung meldet, Du die Drift aus Schritt 5 erkannt hast und erklären kannst, was im State steht und warum plan vor jedem apply kommt.
10Typische Fallen
- Ausrufezeichen in der Shell: In doppelten Anführungszeichen deutet Bash das ! im Token als Verweis auf die Befehlshistorie. Mit read wie oben oder in einfachen Anführungszeichen passiert das nicht.
- Terraform wartet ewig: Ist agent eingeschaltet, fehlt aber der Gastagent in der Vorlage, wartet Terraform bis zum Zeitlimit auf eine IP. Installier den Agenten in der Vorlage oder setz enabled = false.
- Fehlende Rechte: Meldet die API „permission check failed“, fehlt dem Token eine Berechtigung. Ergänze gezielt, was in der Meldung steht, statt einfach Administrator zu vergeben.
- State verloren: Dann kennt Terraform seine VMs nicht mehr und will sie neu anlegen. Mit terraform import holst Du bestehende Ressourcen zurück. Besser ist eine Sicherung des States.
- Neuer Host-Schlüssel: Eine neu gebaute VM mit alter IP hat einen neuen SSH-Schlüssel, und SSH verweigert die Verbindung. Entfern den alten Eintrag mit ssh-keygen -R <IP-der-VM>.
11Häufige Fragen zu Terraform mit Proxmox
Warum der Provider bpg/proxmox? Er wird aktiv gepflegt und deckt VMs, Container, Netze, Benutzer und vieles mehr in Proxmox ab. Seine Dokumentation im Terraform Registry ist ausführlich und hat Beispiele für das Klonen mit cloud-init.
Muss der Provider auch per SSH auf den Proxmox-Host? Für das Klonen aus einer Vorlage reicht der API-Token. SSH braucht der Provider nur für einige Aufgaben, etwa das Hochladen von cloud-init-Dateien als Snippets oder das Importieren von Platten aus einer Datei.
Terraform oder OpenTofu? OpenTofu ist ein freier Ableger von Terraform und versteht dieselbe Sprache. Der Provider bpg/proxmox ist für beide gedacht. Für den Lernpfad ist es egal, die Konzepte sind dieselben.
Was ist der Unterschied zwischen plan und apply? plan rechnet aus, was sich ändern würde, und ändert nichts. apply führt die Änderungen aus. Speicherst Du den Plan mit -out und gibst ihn an apply, wird genau das eingespielt, was Du gelesen hast.
12Weiter im Lernpfad
Zurück zur Übersicht: Lernpfad IT-Infrastruktur. Die Grundlage ist die Proxmox-Übungsumgebung, weiter geht es mit Ansible und NetBox als Quelle der Wahrheit. Infrastruktur als Code gehört zu unserem Praktikum Fachinformatiker Systemintegration. Wenn Du lieber programmierst und Werkzeuge rund um die Automatisierung bauen willst, sieh Dir das Praktikum Fachinformatiker Anwendungsentwicklung an.
Zuletzt geprüft: Oktober 2026
