Zum Inhalt springen
sw
en

Tippe um zu suchen

Automatisierung & DevOps

Terraform State Management: Remote State, Locking und Drift-Erkennung

Remote State mit S3, Azure Blob und Terraform Cloud, Locking-Mechanismen, Drift-Erkennung mit refresh-only, Workspaces, Import und Secrets im State absichern.

14 Min Lesezeit Expert Zuletzt aktualisiert:

Warum der State das eigentliche Herzstueck von Terraform ist

Wer mit Terraform arbeitet, denkt zuerst an HCL-Code: Ressourcen, Variablen, Module. Das eigentliche Kronjuwel ist aber die terraform.tfstate-Datei. Sie ist die einzige Quelle, aus der Terraform weiss, welche realen Ressourcen zu welchem Codeblock gehoeren, welche Attribute sie gerade haben und was bei einem terraform apply veraendert werden muss. Ohne State kann Terraform nicht diffen — es wuerde bei jedem Lauf versuchen, alles neu anzulegen.

Genau das macht den State so heikel: Er ist gleichzeitig unverzichtbar, sicherheitskritisch und ein potenzieller Single Point of Failure. In einem Schweizer KMU mit zwei bis fuenf Admins, die gemeinsam Azure- oder AWS-Infrastruktur per Terraform verwalten, entscheidet ein sauberes State-Management darueber, ob “wer hat zuletzt applied?” ein Scherz bleibt oder zu einer geloeschten Produktionsdatenbank fuehrt.

Dieser Artikel geht davon aus, dass du Terraform-Grundlagen (Provider, Resource-Blocks, plan/apply) bereits kennst. Der Fokus liegt konsequent auf State: wo er liegt, wie er gegen gleichzeitigen Zugriff geschuetzt wird, wie du Abweichungen zwischen Code und Realitaet aufspuerst, und wie du bestehende Infrastruktur sauber einbindest.

Was genau im State steckt

Der State ist JSON und enthaelt pro Ressource unter anderem:

  • die vollstaendige Attributliste, wie sie der Provider zuletzt zurueckgemeldet hat (id, IP-Adressen, ARNs, Passwort-Hashes, generierte Keys)
  • Abhaengigkeitsinformationen (depends_on-Graph) fuer die richtige Reihenfolge bei apply/destroy
  • Metadaten wie Terraform- und Provider-Version, serial-Zaehler und lineage-UUID, ueber die Terraform erkennt, ob zwei State-Dateien ueberhaupt zusammengehoeren

Der serial-Zaehler ist wichtig fuers Verstaendnis von Konflikten: Jede Aenderung erhoeht ihn um eins. Wenn zwei Personen parallel arbeiten und der Lock versagt, kann ein State mit niedrigerem serial versehentlich einen neueren ueberschreiben — genau das soll Locking verhindern.

Remote State: Warum lokal keine Option ist

Mit terraform init ohne Backend-Konfiguration landet der State als terraform.tfstate im aktuellen Verzeichnis. Das funktioniert fuer ein Lernprojekt, ist aber im Team und in Produktion aus mehreren Gruenden ungeeignet:

  • Keine Locking-Moeglichkeit — zwei Personen koennen gleichzeitig apply ausfuehren und sich gegenseitig die Aenderungen ueberschreiben.
  • Kein Backup, keine Versionierung — ein geloeschtes File bedeutet faktisch den Verlust der Verwaltbarkeit der gesamten Infrastruktur (die Ressourcen existieren zwar noch, Terraform kennt sie aber nicht mehr).
  • Secrets liegen unverschluesselt auf einer Entwickler-Festplatte statt in einem zugriffsgeschuetzten, auditierten Speicher.

Ein Remote Backend loest das, indem der State zentral abgelegt wird und alle Terraform-Aufrufe ihn dort lesen und schreiben.

Vergleich der gaengigen Backend-Optionen

BackendLockingVerschluesselungVersionierungKostenBesonderheit fuer CH-KMU
Lokal (Default)KeinsNein (ausser Disk-Verschluesselung)NeinKostenlosNur fuer Sandbox/Lernzwecke geeignet
AWS S3Nativ ueber use_lockfile (ab Terraform 1.10) oder DynamoDB (deprecated ab 1.11)SSE-S3/SSE-KMS moeglichJa, via S3-Bucket-VersionierungCent-Bereich pro MonatBucket-Region in eu-central-2 (Zuerich) fuer Datenresidenz waehlbar
Azure Blob Storage (azurerm)Nativ ueber Blob-Lease (60 Sekunden)SSE per Storage Account StandardJa, via Blob-Versionierung/Soft DeleteCent-Bereich pro MonatStorage Account in switzerlandnorth/switzerlandwest fuer FADP/NDSG-relevante Faelle
HCP Terraform (ehem. Terraform Cloud)Nativ, serverseitigJa, verwaltetJa, inkl. State-Historie im UIFree-Tier bis 5 Nutzer, danach gestaffeltZusaetzlich: Policy-as-Code (Sentinel/OPA), Run-Historie, keine eigene Infrastruktur fuer den Backend-Betrieb noetig
GitLab-/Azure-DevOps-Terraform-BackendNativ ueber HTTP-Backend-LockingAbhaengig vom AnbieterTeilweiseMeist in bestehendem Abo enthaltenPraktisch, wenn CI/CD ohnehin dort laeuft

Fuer die meisten Schweizer KMU, die bereits in Azure unterwegs sind, ist azurerm mit einem dedizierten Storage Account der pragmatischste Einstieg. Wer AWS nutzt, greift zu S3. HCP Terraform lohnt sich, sobald mehrere Teams beteiligt sind und ihr eine UI mit Run-Historie, Policy-Checks und Approval-Workflows wollt, ohne das selbst zu bauen.

Azure Blob Storage als Backend einrichten

terraform {
  backend "azurerm" {
    resource_group_name  = "rg-terraform-state"
    storage_account_name = "stterraformstatekmu01"
    container_name       = "tfstate"
    key                  = "network/prod.terraform.tfstate"
  }
}

Der Storage Account sollte vorab manuell oder per Bootstrap-Terraform (mit lokalem State, einmalig) angelegt werden — ein klassisches Henne-Ei-Problem, das die meisten Teams mit einem separaten “Bootstrap”-Terraform-Root loesen.

# Storage Account und Container fuer den Terraform-State anlegen (einmaliger Bootstrap)
New-AzResourceGroup -Name "rg-terraform-state" -Location "switzerlandnorth"

New-AzStorageAccount -ResourceGroupName "rg-terraform-state" `
  -Name "stterraformstatekmu01" `
  -Location "switzerlandnorth" `
  -SkuName "Standard_LRS" `
  -Kind "StorageV2" `
  -AllowBlobPublicAccess $false

$ctx = (Get-AzStorageAccount -ResourceGroupName "rg-terraform-state" -Name "stterraformstatekmu01").Context
New-AzStorageContainer -Name "tfstate" -Context $ctx -Permission Off

AWS S3 als Backend mit nativem Locking (ab Terraform 1.10)

terraform {
  backend "s3" {
    bucket       = "kmu-terraform-state"
    key          = "network/prod/terraform.tfstate"
    region       = "eu-central-2"
    encrypt      = true
    use_lockfile = true   # natives Locking, ersetzt DynamoDB seit Terraform 1.10
  }
}

HCP Terraform (Terraform Cloud) als Backend

terraform {
  cloud {
    organization = "meine-firma-kmu"
    workspaces {
      name = "netzwerk-prod"
    }
  }
}

Hier entfaellt jegliche eigene Backend-Infrastruktur — HCP Terraform uebernimmt Speicherung, Locking, Verschluesselung und Zugriffskontrolle per SSO/Teams.

State Locking im Detail: Was technisch passiert

Locking verhindert, dass zwei terraform apply- oder terraform plan-Prozesse gleichzeitig schreibend auf denselben State zugreifen. Der Ablauf ist bei allen Backends konzeptionell gleich:

  1. Terraform versucht, vor jedem State-lesenden/-schreibenden Vorgang einen Lock zu setzen.
  2. Gelingt das, laeuft die Operation; danach wird der Lock wieder freigegeben.
  3. Ist bereits ein Lock aktiv, bricht Terraform mit einer Fehlermeldung ab (Error acquiring the state lock) statt zu warten oder zu ueberschreiben.

Bei S3 mit use_lockfile = true ist das eine bedingte Objekt-Erstellung (Conditional Write) fuer die .tflock-Datei. Bei Azure Blob nutzt Terraform eine native Blob-Lease mit fixer 60-Sekunden-Laufzeit, die Terraform waehrend eines laufenden Vorgangs automatisch erneuert. Bricht der Terraform-Prozess hart ab (z. B. Stromausfall, Strg+C mitten im Apply gefolgt vom Beenden der Shell), bleibt der Lock bis zum Ablauf der Lease bzw. bis zur manuellen Freigabe bestehen.

# Aktuell gehaltene Locks sind an der Fehlermeldung erkennbar:
# Error: Error acquiring the state lock
# Lock Info:
#   ID:        7c8e2b3a-1234-4f5e-9abc-9f8e7d6c5b4a
#   Path:      network/prod.terraform.tfstate
#   Operation: OperationTypeApply
#   Who:       s.weber@BUILD-01
#   Created:   2026-07-07 09:14:22 UTC

# Lock manuell aufheben (nur wenn sicher ist, dass kein Prozess mehr laeuft!)
terraform force-unlock 7c8e2b3a-1234-4f5e-9abc-9f8e7d6c5b4a

Drift erkennen: Wenn Realitaet und State auseinanderlaufen

“Drift” bedeutet: Jemand (oder etwas) hat eine Ressource ausserhalb von Terraform veraendert — ein Admin hat im Azure-Portal schnell eine Firewall-Regel angepasst, ein Auto-Scaling-Prozess hat eine Instanzgroesse geaendert, oder ein Skript hat ein Tag entfernt. Der State weiss davon nichts, bis Terraform die Realitaet erneut abfragt.

Der saubere Weg: -refresh-only

Frueher gab es dafuer den Befehl terraform refresh, der den State stillschweigend ueberschrieb, ohne dass jemand die Aenderungen vorher sah. Seit Terraform 0.15.4 ist -refresh-only der empfohlene, sicherere Weg:

# Zeigt die Abweichung zwischen State und echter Infrastruktur, OHNE etwas zu aendern
terraform plan -refresh-only

# Beispiel-Ausgabe (gekuerzt):
# ~ resource "azurerm_network_security_rule" "allow_rdp" {
#     ~ source_address_prefix = "10.0.0.0/24" -> "0.0.0.0/0"
#   }
# Terraform will update the state to reflect these changes. No further
# actions are performed automatically.

Wichtig zu verstehen: -refresh-only aendert nur den State, niemals die echte Infrastruktur. Wenn der Plan zeigt, dass die RDP-Regel im echten Netz auf 0.0.0.0/0 steht statt wie im Code definiert auf 10.0.0.0/24, hast du zwei Optionen:

  • Der Code ist “richtig” und die manuelle Aenderung war ein Fehler oder ein Sicherheitsvorfall -> normaler terraform apply (ohne -refresh-only) stellt den Soll-Zustand wieder her.
  • Die manuelle Aenderung war beabsichtigt und soll bleiben -> terraform apply -refresh-only uebernimmt die reale Konfiguration in den State, danach muss der HCL-Code manuell nachgezogen werden, sonst driftet es beim naechsten Mal wieder auseinander.

Schritt-fuer-Schritt-Ablauf fuer Drift-Handling im Team

  1. terraform plan -refresh-only regelmaessig laufen lassen (idealerweise als geplanter CI-Job, z. B. taeglich per Task Scheduler oder Azure DevOps Pipeline-Schedule).
  2. Ausgabe pruefen: Ist die Abweichung erklaerbar (Notfall-Hotfix, Autoscaling) oder ein Anzeichen fuer Shadow-IT/Sicherheitsproblem?
  3. Entscheidung treffen: Code anpassen (Drift dauerhaft uebernehmen) oder Infrastruktur zuruecksetzen (terraform apply ohne Refresh-only-Flag).
  4. Nach der Entscheidung: normalen terraform plan/apply-Zyklus fahren, damit Code und Realitaet wieder synchron sind.
  5. Ursache dokumentieren — wiederkehrender Drift an derselben Stelle deutet meist auf einen Prozess hin, der Terraform umgeht (z. B. ein Skript, das direkt gegen die Azure-API arbeitet).

Modulstruktur fuer wartbare Terraform-Projekte

Ab einer Handvoll Ressourcen lohnt sich eine Aufteilung in Module. Eine bewaehrte Struktur fuer ein KMU-Setup mit mehreren Umgebungen:

terraform/
├── modules/
│   ├── network/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   ├── vm-fleet/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   └── storage/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
├── environments/
│   ├── dev/
│   │   ├── main.tf         # ruft Module mit dev-Parametern auf
│   │   ├── backend.tf      # eigener State-Key: env/dev/terraform.tfstate
│   │   └── terraform.tfvars
│   └── prod/
│       ├── main.tf         # ruft dieselben Module mit prod-Parametern auf
│       ├── backend.tf      # eigener State-Key: env/prod/terraform.tfstate
│       └── terraform.tfvars
└── bootstrap/
    └── main.tf             # legt Storage Account fuer den State selbst an (lokaler State)

Module kapseln wiederverwendbare Logik (z. B. “ein Netzwerk mit Subnetzen und NSGs”), waehrend die environments/-Ordner nur noch Parameter setzen. Das reduziert Copy-Paste-Fehler zwischen dev und prod drastisch, weil beide Umgebungen exakt denselben Modulcode nutzen.

Workspaces vs. getrennte Verzeichnisse fuer dev/prod

Terraform bietet mit terraform workspace einen eingebauten Mechanismus fuer mehrere State-Instanzen aus derselben Konfiguration:

terraform workspace new dev
terraform workspace new prod
terraform workspace list
terraform workspace select prod

# Im Code auf die aktive Workspace reagieren:
# resource "azurerm_resource_group" "rg" {
#   name = "rg-app-${terraform.workspace}"
# }

Workspaces sind bequem, haben aber eine wichtige Einschraenkung: Sie teilen sich dieselbe Backend-Konfiguration und denselben Code-Pfad. Ein terraform apply im falschen Workspace ist ein einziger falscher Tippfehler entfernt (terraform workspace select dev vergessen), und schon laeuft ein Prod-Apply mit Dev-Parametern — oder umgekehrt. Ausserdem lassen sich pro Workspace keine unterschiedlichen Provider-Credentials oder Backend-Regionen konfigurieren.

Entscheidungshilfe: Workspaces oder Verzeichnis-Trennung?

KriteriumTerraform WorkspacesGetrennte Verzeichnisse/State-Files
Setup-AufwandMinimal, ein CodebaseEtwas mehr Boilerplate (pro Env ein Ordner)
Versehentlicher Prod-ApplyReales Risiko bei falscher Workspace-AuswahlNahezu ausgeschlossen — anderer Ordner, andere Pipeline
Unterschiedliche Cloud-Konten/Subscriptions je EnvNicht sauber abbildbarProblemlos, da eigene Backend-Config pro Env
Unterschiedliche Provider-Versionen je EnvNicht moeglich (ein Code)Moeglich
Geeignet fuerKurzlebige Feature-/Test-Umgebungen, PR-Preview-StacksDauerhafte Umgebungen wie dev/test/prod

Fuer die klassische dev/prod-Trennung in einem KMU empfiehlt sich daher meist die Verzeichnis-Variante mit eigenem backend.tf pro Umgebung — der zusaetzliche Tippaufwand ist der Preis fuer die Sicherheit, nie versehentlich im falschen Konto zu landen. Workspaces eignen sich hingegen hervorragend fuer kurzlebige, gleichartige Stacks, etwa ein Preview-Environment pro Pull Request.

Bestehende Ressourcen importieren

Nicht jede Infrastruktur wird “Terraform-first” geboren. Oft existieren VMs, Storage Accounts oder Netzwerke bereits, bevor jemand beschliesst, sie unter IaC zu bringen. Dafuer gibt es Import.

Klassisch: terraform import

# Syntax: terraform import <Terraform-Adresse> <Cloud-Ressourcen-ID>
terraform import azurerm_resource_group.main /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-app-prod

Der Haken: terraform import legt nur den State-Eintrag an, schreibt aber keinen HCL-Code. Du musst den passenden resource-Block danach von Hand schreiben und so lange anpassen, bis terraform plan keine Abweichung mehr zeigt.

Modern: Import-Block mit Config-Generierung (ab Terraform 1.5)

# imports.tf
import {
  to = azurerm_storage_account.legacy
  id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-app-prod/providers/Microsoft.Storage/storageAccounts/stlegacyappdata"
}
# Generiert automatisch einen passenden HCL-Block basierend auf der realen Ressource
terraform plan -generate-config-out="generated_imports.tf"

# Danach pruefen, aufraeumen (Terraform generiert oft mehr Attribute als noetig)
# und regulaer:
terraform apply

Nach erfolgreichem Import unbedingt terraform plan ohne Import-Block laufen lassen, um sicherzustellen, dass keine Drift mehr zwischen generiertem Code und State besteht — generierter Code enthaelt oft Default-Werte, die nicht 1:1 dem entsprechen, was du eigentlich pflegen willst.

State direkt manipulieren: die terraform state-Subcommands

Neben Import gibt es weitere State-CLI-Befehle fuer den Alltag:

terraform state list                              # alle Ressourcen im State auflisten
terraform state show azurerm_linux_virtual_machine.web
terraform state mv azurerm_subnet.old azurerm_subnet.new   # Ressource umbenennen ohne Neuerstellung
terraform state rm azurerm_resource_group.legacy   # aus State entfernen, OHNE die reale Ressource zu loeschen

terraform state mv ist besonders nuetzlich bei Refactorings (z. B. eine Ressource in ein Modul verschieben), weil Terraform sonst die alte Ressource loeschen und eine neue anlegen wuerde — bei einer VM oder Datenbank ein potenziell zerstoerender Vorgang.

State und Secrets: Das ungeloeste Grundproblem entschaerfen

Wie eingangs erwaehnt: Alles, was ein Provider als Attribut zurueckgibt, landet im Klartext im State — unabhaengig davon, ob du es mit sensitive = true markiert hast. Fuer ein KMU heisst das konkret:

  • Zugriff auf den State-Bucket/Container ist gleichbedeutend mit Zugriff auf alle darin verwalteten Secrets. Schuetze ihn wie einen Passwort-Tresor: minimale IAM-Rollen, kein oeffentlicher Zugriff, idealerweise eigener Storage Account/Bucket nur fuer State.
  • Verschluesselung at rest aktivieren — bei Azure Blob per Standard-Verschluesselung des Storage Accounts (aktiv per Default), bei S3 per SSE-KMS mit eigenem Key statt SSE-S3.
  • Versionierung/Soft-Delete aktivieren, damit ein versehentliches Ueberschreiben oder Loeschen des State nicht sofort final ist.
  • Wo moeglich, Secrets gar nicht erst durch Terraform verwalten lassen. Ab Terraform 1.10/1.11 gibt es Ephemeral Resources und Write-Only Attributes: Werte wie initiale Passwoerter koennen an einen Provider uebergeben werden, ohne dass Terraform sie dauerhaft im State speichert.
  • Secrets-Referenzen statt Secrets-Werte nutzen: Ein Azure Key Vault Secret per Data Source referenzieren ist besser, als das Secret als Terraform-Variable zu definieren, die dann im Plan und State landet.
# Beispiel: Passwort aus Key Vault referenzieren statt hart im State abzulegen
data "azurerm_key_vault_secret" "sql_admin_pw" {
  name         = "sql-admin-password"
  key_vault_id = data.azurerm_key_vault.main.id
}

resource "azurerm_mssql_server" "sql" {
  name                         = "sql-app-prod"
  administrator_login          = "sqladmin"
  administrator_login_password = data.azurerm_key_vault_secret.sql_admin_pw.value
  # Achtung: Der aufgeloeste Wert landet trotzdem im State!
}

Praxis-Checkliste fuers Schweizer KMU

  • Remote Backend mit nativem Locking einrichten, bevor das erste Team-Mitglied dazustoesst — nicht erst nachtraeglich migrieren.
  • State-Bucket/Container physisch in einer Schweizer oder zumindest EU-Region betreiben, wenn Datenresidenz-Anforderungen (NDSG) relevant sind.
  • Zugriff auf das Backend so eng wie moeglich fassen (dedizierte Service Principals/IAM-Rollen statt persoenlicher Admin-Konten).
  • terraform plan -refresh-only als wiederkehrenden, automatisierten Check etablieren statt Drift erst beim naechsten manuellen Apply zu entdecken.
  • Pro dauerhafter Umgebung (dev/test/prod) einen eigenen State mit eigenem Backend-Key statt Workspaces, wenn unterschiedliche Cloud-Konten oder ein hohes Blast-Radius-Risiko im Spiel sind.
  • Neue Module und Import-Blocks per Pull Request reviewen, bevor sie gegen Produktion laufen.
  • State-Backups/Versionierung regelmaessig stichprobenartig testen (kann ich wirklich eine alte Version zuruckholen?), nicht nur aktivieren und vergessen.

Weiterlernen

Verwandte Themen im Wiki: Infrastructure as Code im Vergleich, Git-Workflows fuer IT-Admins, Ansible Playbooks, Inventory und Vault, Azure-Grundlagen, Passwort-Management im Unternehmen.

Kommentare

Frage, Verbesserungsvorschlag oder eigene Erfahrung zu diesem Artikel? Schreib einen Kommentar. Neue Beiträge erscheinen nach kurzer Moderation.

  • Lade Kommentare …
Kommentar schreiben