Start / Cheat Sheets

Cheat Sheet

Terraform und OpenTofu: Teambetrieb — Cheat Sheet

Stand: · Terraform und OpenTofu in der Praxis

TerraformOpenTofuInfrastructure as CodeCI/CD

Der Spickzettel zur zweiten Hälfte des Seminars Terraform und OpenTofu in der Praxis: wo Geheimnisse trotz sensitive landen, wie Umgebungen getrennt werden, wie Bestand übernommen und Drift behandelt wird, welche Prüfung welchen Fehler findet, woran ein gespeicherter Plan gebunden ist — und worin sich die beiden Werkzeuge inzwischen unterscheiden. Sprache, Ressourcen, Module und State stehen auf dem ersten Blatt: Terraform und OpenTofu: Kern.

Maßgeblich für Argumente, Flags und Versionsstände ist die Originaldoku — developer.hashicorp.com/terraform und opentofu.org/docs. Dieses Blatt trifft eine Auswahl, Stand Terraform 1.16 und OpenTofu 1.13 (Ende September 2026); die Befehle gelten mit tofu statt terraform gleich.

Zugänge trennen und wo Geheimnisse landen

Ein Lauf braucht drei verschiedene Zugänge: zum Backend mit dem State, zum Zielsystem des Providers und die Identität der Umgebung, in der er läuft.

ZugangWerMindestumfang
Backend (State-Datenbank)Plan und Applynur die State-Datenbank, Verbindung über PG_CONN_STR
Docker-Host testTeam und PipelineSSH als Deploy-Benutzer
Docker-Host prodnur die PipelineSSH als Deploy-Benutzer
RegistryPull des Anwendungs-Imagesnur lesen, eigener Zugang je Umgebung
Secret Storeder Lauf selbstnur die eigenen Pfade, kurzlebige Tokens

Wer den Docker-Daemon steuern darf, hat faktisch Root-Rechte auf dem Host — der kritischste Zugang.

sensitive = true wirkt nur auf die Anzeige. Ein normales Argument wie env speichert der Provider vollständig im State:

OrtWas dort stehtSchutz
State im BackendAttribute wie env als JSONZugriff auf den State eng fassen
Plan-Datei aus plan -outgeplante Attribute und Variablenwertekurzlebig, nie ins Repository
output -json und -rawsensible Outputs im Klartextnie ins Pipeline-Log schreiben
CI-Artefaktegespeicherte Pläne zwischen Jobskurze Aufbewahrung, wenige Leser
Lokaler Stateterraform.tfstate als Klartext-Dateigar nicht erst lokal arbeiten

Modul: Secrets und sensible Daten

Ephemeral Values, Write-only und State-Verschlüsselung

Ephemeral Values existieren nur während eines Laufs und landen weder im State noch in der Plan-Datei: Variablen und Outputs mit ephemeral = true und Ephemeral Resources aus einem ephemeral-Block. In eine verwaltete Ressource gelangen sie nur über Write-only Arguments — und die muss der Provider anbieten, etwa password_wo samt password_wo_version einer Cloud-Datenbank. Rotiert wird über eine höhere _wo_version; ohne sie plant das Werkzeug keine Änderung.

Der Docker-Provider 4.6 hat kein Argument mit der Endung _wo; ephemere Werte gehen dort nur in den Provider-Block (etwa registry_auth). Der Ausweg ist eine Datei, deren Pfad allein im State steht:

resource "docker_container" "db" {
  env = ["POSTGRES_PASSWORD_FILE=/geheim/db"]
  volumes {
    host_path      = "/srv/saatplan/geheim"
    container_path = "/geheim"
    read_only      = true
  }
}

OpenTofu verschlüsselt seit 1.7 State und Plan-Dateien selbst — im encryption-Block innerhalb von terraform oder über TF_ENCRYPTION. Terraform kennt keinen solchen Block; dort verschlüsselt allenfalls das Backend.

encryption {
  key_provider "pbkdf2" "saatplan" {
    passphrase = var.state_passphrase   # mindestens 16 Zeichen
  }
  method "aes_gcm" "saatplan" {
    keys = key_provider.pbkdf2.saatplan
  }
  state { method = method.aes_gcm.saatplan }
  plan  { method = method.aes_gcm.saatplan }
}

Statt pbkdf2 gehen Schlüssel aus AWS KMS, GCP KMS, Azure Key Vault oder OpenBao. Beim Umstellen und Rotieren steht die alte Methode als fallback, bis jeder State neu geschrieben ist; danach enforced = true. Was Verschlüsselung nicht leistet: Sie schützt nicht vor Datenverlust (ohne Schlüssel ist der State verloren), verhindert keinen Replay mit einem älteren State und ersetzt keine Berechtigungen — wer tofu ausführt, sieht die Geheimnisse weiterhin. Key Provider und Methoden nie umbenennen, ihre Namen stehen im State.

Modul: Secrets und sensible Daten

Umgebungen: Workspaces oder getrennte Root Modules

Test und Produktion dürfen sich keinen State teilen.

CLI-WorkspacesGetrennte Root Modules
Aufbaueine Konfiguration, mehrere Statesje Umgebung ein eigenes Verzeichnis
Backend und Zugängedasselbe Backend, dieselben Zugängeeigenes Backend, eigene Zugänge
Wechselterraform workspace selectdurch das Verzeichnis
Gut fürkurzlebige Kopien je Feature-Branchtest und prod mit eigenem Risiko

Unterschiede gehören in Variablenwerte, nicht in Bedingungen im Modul: terraform.tfvars wird automatisch geladen, weitere Dateien per -var-file. Dann steht der Unterschied zwischen test und prod im Diff. Beim pg-Backend braucht jede Umgebung ein eigenes schema_name — sonst teilen sich beide die Zeile default und damit den State.

Mehrere Ziele in einer Konfiguration laufen über alias; an ein Modul geht die Konfiguration mit providers = { docker = docker.prod } im module-Block.

AWSAzureGoogle Cloud
Ziel statt Docker-HostAccount und RegionSubscriptionProjekt und Region
Im Provider-Blockregion, assume_rolesubscription_idproject, region
Mehrere Zielealias je Account oder Regionalias je Subscriptionalias je Projekt

Zwischen Teams wird per Datenquelle nach Namen übergeben, nicht per Schreibzugriff.

Terraform StacksTerragrunt
Wo es läuftin HCP Terraformlokal und in jeder Pipeline
Einheitcomponent in .tfcomponent.hclUnit mit terragrunt.hcl
Umgebungendeployment in .tfdeploy.hclVerzeichnisse oder terragrunt.stack.hcl
AbhängigkeitenStacks reichen Outputs weiterdependency-Blöcke, run --all
EngineTerraformOpenTofu oder Terraform

terragrunt run --all arbeitet die Units in der Reihenfolge ihrer dependency-Blöcke ab. Das Werkzeug ordnet nur, was schon geschnitten ist — die State-Grenzen kommen zuerst.

Modul: Umgebungen und größere Strukturen

Bestand übernehmen: import, moved, removed

terraform importimport-Block
Ablaufsofort in den Stateerst im Plan, dann beim Apply
VorschaukeinePlan zeigt jeden Import
Konfigurationvon Hand schreibenmit -generate-config-out erzeugbar
Mehrere Objekteein Aufruf je Objektfor_each über Map oder Set
Im Reviewunsichtbarsteht im Pull Request

Die Doku empfiehlt den Block. Die ID muss beim Plan bekannt sein; die Zieldatei von -generate-config-out darf noch nicht existieren, und die Generierung gilt in beiden Werkzeugen als experimentell. Ziel ist ein Plan mit 1 to import und sonst nur Nullen.

import {
  to = docker_container.db
  id = var.db_container_id
}
moved {
  from = docker_container.db
  to   = module.saatplan_db.docker_container.db
}
removed {
  from = docker_volume.daten
  lifecycle {
    destroy = false
  }
}

moved hält eine neue Adresse fest (in geteilten Modulen stehen lassen — ihr Entfernen bricht ältere Aufrufer); er wandelt keine verwaltete Ressource in eine Data Source um. removed ohne destroy = false löscht das Objekt, als wäre die Ressource aus dem Code entfernt worden.

Modul: Bestand übernehmen und refaktorieren

Drift erkennen und behandeln

terraform plan -refresh-only        # nur lesen: was hat sich draußen geändert?
terraform apply -refresh-only       # Abweichung bewusst in den State übernehmen
terraform plan -detailed-exitcode   # 0 = keine Änderung, 1 = Fehler, 2 = Drift

terraform refresh ist veraltet — es übernimmt Abweichungen ohne Rückfrage.

Drift übernehmenDrift zurückführen
WannÄnderung war richtig und bleibtÄnderung war ein Versehen
Mittelapply -refresh-only aktualisiert den Statenormales apply stellt den Code-Stand her
Codeim selben Zug nachziehenbleibt unverändert
Sonstdas nächste apply dreht sie zurücksie kommt wieder — Ursache klären

Beim Docker-Provider zeigt sich Drift so: Ein gestoppter Container gilt als Änderung (must_run), docker update --restart erscheint bei restart, ein gelöschter und neu gestarteter Container hat eine neue ID und gilt als verschwunden.

Module: Bestand übernehmen und refaktorieren · Betrieb, Fehler und Upgrades

Welche Prüfung welchen Fehler findet

Prüfungfindetbraucht
fmt -checkuneinheitliche Formatierungnur die Dateien
validateTippfehler, falsche Typen, tote Referenzeninit, kein Backend
TFLintungenutzte Variablen, fehlende VersionenPlugins per --init
Checkovriskante Einstellungen laut RegelwerkCode oder Plan als JSON
test mit Mockfalsche Logik, verletzte Bedingungenkeinen Docker-Host
test mit applyFehler im Zusammenspiel mit Dockereinen echten Docker-Host

validate läuft auch nach init -backend=false — so prüft die Pipeline, ohne den Remote State anzufassen. Ehrlich zur Abdeckung: Für kreuzwerker/docker pflegt TFLint keinen Regelsatz (nur AWS, Azure, Google Cloud), und Checkov hat keine Regel für docker_container, docker_image oder docker_network — ein grünes Ergebnis heißt dort: nichts geprüft.

# *.tftest.hcl — run-Block führt plan oder apply aus (Standard: apply)
mock_provider "docker" {}
run "web_port_unter_1024_abgelehnt" {
  command = plan
  variables { ports = { web = 80 } }
  expect_failures = [var.ports]
}
run "db_port_bleibt_intern" {
  command = plan
  assert {
    condition     = length(docker_container.db.ports) == 0
    error_message = "saatplan-db veröffentlicht Ports"
  }
}

In expect_failures stehen nur eigene Bedingungen: Validierungen, Pre- und Postconditions, check-Blöcke. Eine reale Umgebung braucht ein Test, wenn Port-Bindung, Netzanschluss oder Volume-Mount die Frage sind oder ein Dienst antworten muss. Am Ende jeder Datei wird in umgekehrter run-Reihenfolge zerstört — auch nach roten Assertions.

Modul: Automatisierte Tests und Qualitätsprüfungen

Pipeline: Stufen und gespeicherter Plan

StufeFrageBefehl
FormatIst der Code einheitlich formatiert?terraform fmt -check
ValidierungIst die Konfiguration in sich stimmig?terraform validate
TestsVerhalten Module sich wie zugesagt?terraform test
Security ScanVerstößt etwas gegen Regeln?Checkov, conftest
PlanWas genau würde sich ändern?terraform plan -out=…

Mit -input=false bricht ein Schritt ab, statt zu warten. Ausgeführt wird der geprüfte Plan, nicht ein neuer — dafür laut Doku das Arbeitsverzeichnis samt .terraform archivieren und am selben absoluten Pfad auspacken.

BindungWer prüftMeldung oder Mittel
CLI-VersionTerraform und OpenTofuplan files cannot be transferred
ProviderAbgleich mit dem Lock FileInconsistent dependency lock file
StateLineage im PlanSaved plan does not match the given state
Stand des StateSerial im PlanSaved plan is stale
Commitdie Pipeline selbstSHA im Artefaktnamen, gleicher Lauf
ZugangsdatenniemandUmgebung fest an den Job binden

Betriebssystem und Architektur müssen bei Plan und Apply gleich sein. -auto-approve beim Apply eines gespeicherten Plans wirkt nicht. Ein Lauf je State, keiner wird abgebrochen:

concurrency:
  group: saatplan-infra-prod
  cancel-in-progress: false
  queue: max          # bis zu 100 wartende Läufe statt verwerfen

Der State-Lock des Backends bleibt die letzte Absicherung. Schon ein Plan führt Provider-Code aus und braucht Zugang zum Ziel — Pull Requests aus Forks bekommen deshalb nur fmt, validate und Tests mit Mocks.

WerkzeugLäuft woEntscheidung
SentinelHCP Terraform, Enterpriseadvisory, soft- oder hard-mandatory
OPAHCP Terraform, Enterpriseadvisory oder mandatory
conftesteigene Pipeline, Regodeny bricht ab, warn meldet
Checkoveigene Pipelinefehlgeschlagene Prüfung bricht ab

Policies lesen den Plan als JSON (terraform show -json). Zur Regel gehört ein Ausnahmeprozess — conftest kennt exception-Regeln, Checkov den Kommentar checkov:skip samt Begründung; jede Ausnahme bekommt ein Ablaufdatum.

Modul: Pipelines und freigegebene Änderungen

Fehlgeschlagene Läufe und Wiederherstellung

Bricht apply ab, sind die bis dahin ausgeführten Änderungen real; zurückgerollt wird nicht automatisch, ein halb erzeugtes Objekt ist in der Regel tainted.

FehlerbildWas passiert istErster Blick
Healthcheck-TimeoutContainer angelegt, wait_timeout abgelaufentainted im nächsten Plan
SSH bricht abDocker-Host mitten im Lauf wegTeil des Laufs im State
Keine RechteDeploy-Benutzer darf den Docker-Socket nichtRechte am Ziel prüfen
Lock belegtanderer Lauf hält den StateError acquiring the state lock
State nicht gespeichertBackend nicht erreichbarerrored.tfstate im Arbeitsordner
SchadenWegWerkzeug
Objekt halb erzeugtersetzen lassen-replace, tainted
Objekt da, State weiß nichtsübernehmenimport-Block
State nicht gespeicherterrored.tfstate zurückspielenterraform state push
State beschädigtSicherung der State-Datenbank einspielenpg_dump, state push
Ziel-Host verlorenneu aufbauen, Daten zurückspielenapply, Datensicherung

Ein erneutes apply vor dem state push erzeugt einen geforkten State. In der Pipeline das Arbeitsverzeichnis als Artefakt sichern, bevor der Runner es wegräumt. Das pg-Backend führt keine State-Historie und nutzt Advisory Locks, die mit der Sitzung fallen — force-unlock gibt es dort nicht.

HCP Terraform, EnterpriseEigene Pipeline
AusführungRemote Runs auf eigenen WorkernRunner, etwa in GitHub Actions
Stateje Workspace, mit Versioneneigenes Backend
SerialisierungWarteschlange je Workspaceconcurrency plus State-Lock
ZugriffTeams und Rechte je WorkspaceEnvironments, Branch-Schutz
PoliciesSentinel, OPAconftest, Checkov
DriftHealth Assessments ab Standardgeplanter Lauf
Interne Zieleüber HCP Terraform AgentsRunner im eigenen Netz

HCP Terraform läuft bei HashiCorp, Terraform Enterprise beim Kunden — beide führen nur Terraform aus.

Modul: Betrieb, Fehler und Upgrades

Terraform und OpenTofu im Vergleich

OpenTofu will mit Terraform-Konfigurationen kompatibel bleiben, und die meisten laufen ohne Änderung. Seit der Abspaltung entwickeln sich beide aber getrennt weiter. Stand: Terraform 1.16, OpenTofu 1.13.

FunktionTerraformOpenTofu
Verschlüsselung von State und Plannein, allenfalls das Backendja, seit 1.7 (encryption, TF_ENCRYPTION)
for_each in provider-Blöckenneinja, alias ist Pflicht
enabled im lifecycle-Blockneinja
plan und apply mit -excludeneinja
assume…-Funktionen und convertneinja, neu in 1.13
action-Blöcke und terraform queryjanicht dokumentiert
Symbol Libraries, eingebautes -lintneinexperimentell
Ephemeral Variables, Outputs, Resourcesab 1.10ab 1.11
Write-only Argumentsab 1.11ab 1.11
Eigene Dateiendungen.tf, .tftest.hclzusätzlich .tofu, .tofutest.hcl mit Vorrang
Test-Funktionterraform testtofu test
mock_provider, mock_resource, mock_datajaja
override_resource, _data, _modulejaja
Mock-Daten in eigenen .tfmock.hcljanicht dokumentiert
override_during plan oder applyjanicht dokumentiert
Overrides für einzelne Instanzen und [*]nicht dokumentiertja
mock_provider mit for_eachnicht dokumentiertja, mit alias
state_key und parallel im run-Blockjanicht dokumentiert
UmfeldTerraformOpenTofu
Provider-Registryregistry.terraform.ioregistry.opentofu.org
Orchestrierung vieler Root ModulesStacks in HCP Terraform; Terragruntkein Stacks-Gegenstück; Terragrunt
AusführungsplattformenHCP Terraform, Terraform Enterpriseu. a. Spacelift, env0, Scalr, Harness; Atlantis mit terraform_distribution: opentofu
Policies auf der PlattformSentinel, OPA in HCP Terraformje Plattform eigene — prüfen, nicht voraussetzen
MCP Serverhashicorp/terraform-mcp-serverhttps://mcp.opentofu.org/mcp

Wer beide Werkzeuge bedient, bleibt bei der gemeinsamen Teilmenge. Experimentelle Funktionen können sich in jedem Release ändern.

Module: Terraform und OpenTofu vergleichen und migrieren · Secrets und sensible Daten · Automatisierte Tests und Qualitätsprüfungen · Umgebungen und größere Strukturen · Betrieb, Fehler und Upgrades · KI-gestützte IaC-Entwicklung

Migration nach OpenTofu: Prüffragen und Rückweg

BausteinPrüffrage
ProviderLiegt jeder Provider in der Registry des Zielwerkzeugs, in der benötigten Version?
ModuleNutzen eingebundene Module Funktionen, die nur eines der beiden Werkzeuge kennt?
BackendUnterstützt das Zielwerkzeug das Backend?
AusführungFührt die Pipeline oder Plattform das Zielwerkzeug überhaupt aus?
WerkzeugeKennen Linter, Scanner und Editor-Plug-ins die Eigenheiten des Zielwerkzeugs?

Auf HCP Terraform und Terraform Enterprise entscheidet die Plattform. Der erste Nachweis ist ein tofu plan ohne Änderungen; er zeigt nur, dass OpenTofu den State richtig liest. Erst eine angewendete kleine Änderung beweist, dass es die Umgebung auch verwaltet. tofu init ergänzt die Lock-Datei um die neuen Provider-Adressen — sie gehört in den Commit.

Abhängige KonfigurationenVon unten nach obenVon oben nach unten
Reihenfolgeerst die Konsumenten migrierenerst die Quelle migrieren
Wer liest wessen StateOpenTofu liest Terraform-StateTerraform liest OpenTofu-State
Zusicherungzuverlässignicht zuverlässig
Rückweg betrifftwenigealle Leser

Wann der Rückweg zugeht: Solange nur Gemeinsames genutzt wird, führen terraform init und plan zurück. Verschlüsselter State ist für Terraform nicht mehr lesbar — dann ist die Entscheidung endgültig. Jede nur in OpenTofu verfügbare Funktion macht den Rückweg zur Umbauarbeit; wer ihn offenhalten will, bündelt Eigenes in .tofu-Dateien, die Terraform nicht liest.

VersionÄnderung
OpenTofu 1.12Provisioner-Verbindungen mit type = "winrm" erzeugen Warnungen
OpenTofu 1.13WinRM für Provisioner entfällt ganz, SSH ist der Ersatz
OpenTofu 1.13base64gzip liefert andere Bytes — Ressourcen können ersetzt werden
OpenTofu 1.13macOS erst ab Version 13 Ventura
OpenTofu 1.13letzte Reihe mit offiziellen 32-Bit-Builds

Modul: Terraform und OpenTofu vergleichen und migrieren

KI-Werkzeuge und MCP Server

siehttut
Chat-Assistentnur, was hineinkopiert wirdschlägt Text vor
IDE-Assistentoffene Dateien, Projektkontextergänzt und ändert im Editor
Coding AgentRepository, Terminal, angebundene Serverführt Befehle aus, ändert mehrere Dateien

Wer plan aufrufen darf, kann auch apply aufrufen — die Rechte legt das Team fest, nicht das Modell. Im Kontext gehören Werkzeug und Version (OpenTofu 1.13 oder Terraform 1.16), Provider-Version, Modulschnittstelle und Sicherheitsvorgaben; ohne Provider-Version erfindet das Modell Argumente aus älteren Releases. Prüfgrundlagen statt Vertrauen:

tofu version                              # CLI und Provider-Versionen
tofu providers schema -json > schema.json # welche Argumente es wirklich gibt
tofu validate
tofu plan -out=vorschlag.tfplan
tofu show -json vorschlag.tfplan > plan.json
Toolset (Terraform MCP Server)Inhaltbraucht
registryStandard: öffentliche Provider, Module und Policiesnichts
registry-privateprivate Module und ProviderTFE_TOKEN
terraformOrganisationen, Workspaces, Runs, Variablen, State-VersionenTFE_TOKEN
schreibende Toolscreate_workspace, update_workspace, delete_workspace_safely, action_rundazu ENABLE_TF_OPERATIONS=true

Statt --toolsets gehen einzelne Werkzeuge mit --tools — nie beide zugleich. Der Server kennt nur Terraform; für OpenTofu-Code sind opentofu.org und der OpenTofu MCP Server die Quelle.

Annehmen, wenn …Zurückweisen, wenn …
der Plan genau die beabsichtigte Änderung zeigtder Plan Ersetzungen enthält, die niemand erklären kann
jedes Argument im Provider-Schema stehtArgumente oder Funktionen nicht belegbar sind
Tests und Pipeline unverändert grün sindTests angepasst wurden, damit sie bestehen
Annahmen benannt und bestätigt sindSecrets im Code oder neue offene Ports auftauchen

Modul: KI-gestützte IaC-Entwicklung

Typische Fallen

  • Ein Admin-Zugang für alles. Wer den State liest, kann dann auch produktiv ausrollen.
  • Passwort im backend-Block. Die Verbindungszeichenfolge steht damit im Repository.
  • Secret Store angebunden, Wert trotzdem im State. Er wurde über ein normales Argument weitergereicht.
  • Ephemere Variable an env. Normale Argumente lehnen ephemere Werte mit einem Fehler ab.
  • prod aus test kopiert. Bald sind es zwei Konfigurationen, und ein Fix landet nur in einer.
  • States lesen sich gegenseitig über terraform_remote_state — ein Neuaufbau wird unmöglich.
  • generated.tf ungeprüft übernommen. Darin stehen Standardwerte, berechnete Werte und Umgebungsvariablen im Klartext, samt Passwort.
  • Fehlende Zugangsdaten für Drift gehalten. Sie sehen aus wie gelöschte Objekte — erst den Plan lesen.
  • Test-Container nach hartem Abbruch. Sie bleiben stehen — per labels markieren; Tests mit apply nie neben prod.
  • Saved plan is stale mit neuem Plan umgangen. Ausgeführt wird dann etwas ohne Freigabe; ebenso ein abgelehnter Plan, dessen Artefakt noch herumliegt.
  • -lock=false als Rettung. Öffnet die Tür für zwei gleichzeitige Applies.
  • Kompatibilität aus dem gemeinsamen Ursprung gefolgert statt per Plan geprüft.
  • Terraform und OpenTofu abwechselnd gegen dieselbe Umgebung — oder die Pipeline ruft weiter terraform auf, obwohl längst tofu gilt.
  • State, Plan-Ausgabe oder Passwort im Prompt. Sensible Werte fließen so ins KI-Werkzeug ab.
  • Freigabe, weil die Pipeline grün ist — nicht, weil der Plan verstanden wurde.

Dieses Thema als Schulung für Ihr Team

Dieser Beitrag erklärt das Thema. Damit Ihr Team es danach auch anwendet, gibt es Terraform und OpenTofu in der Praxis als Schulung — an Ihrem eigenen Code, mit den Fragen, die ein Text nicht beantwortet. Sie wählen die Module, wir bauen daraus ein Programm.

5 Tage·ab 900 EUR netto pro Tag (bis 3 Teilnehmende) ·Termin nach Vereinbarung

Als Team-Schulung anfragenZum Seminar Terraform und OpenTofu in der Praxis →