Terraform
auf einem Blatt.
Dichte Referenz für Senior Platform Engineers und SREs zu HashiCorps Infrastructure-as-Code-Werkzeug. HCL-Kern, State und Locking, Module, Meta-Argumente, Import und Refactoring, Tests, CI/CD-Patterns und Anti-Patterns. Keine Einsteiger-Folien.
Vorschau (2 Seiten A4 quer + Brand-Rückseite)


PDF herunterladen
Direkter Download, keine Mail-Adresse nötig. CC BY-SA 4.0 — kopieren, drucken, weiterverteilen ist ausdrücklich erlaubt, solange die Quellenangabe sichtbar bleibt.
Was drin steht
HCL-Kern & Provider
resource/data/locals/variables/outputs, Typen + Validation, sensitive. Version-Pinning mit ~>, .terraform.lock.hcl, Provider-Alias für Multi-Region.
State & Drift
Remote Backends, natives S3-Locking (use_lockfile), state mv/rm, plan -refresh-only. Ephemeral Values und write-only Attribute gegen Klartext-Secrets.
Module & Meta-Argumente
Struktur, Registry-Versionierung, Git-ref-Pinning. for_each vs count, depends_on, lifecycle, dynamic blocks.
Plan/Apply & Import
plan -out als Artefakt, -detailed-exitcode, -replace, -target-Vorsicht. import-Block, -generate-config-out, moved/removed statt State-Chirurgie.
Workspaces & Tests
Workspaces und ihre Grenzen bei Prod-Trennung. terraform test mit .tftest.hcl, run/assert, Mocking seit 1.7.
CI/CD & Anti-Patterns
Plan-Artefakt + Approval-Gate, Pipeline-Hygiene, BUSL-Lizenzstand. Anti-Patterns aus der Praxis.
Cheatsheet im Volltext
Derselbe Inhalt wie im PDF — zum Mitlesen, Durchsuchen und direkten Kopieren der HCL-Snippets. Stand: Terraform v1.15 (Edition 2026.07).
HCL-Kern
Bausteine
resource: verwaltetes Objekt (Create/Update/Delete durch Terraform). data: Read-only-Lookup bestehender Objekte. variable: Eingabe pro Root-/Child-Modul. locals: benannte Zwischenwerte, DRY für Ausdrücke. output: Rückgabewerte, Schnittstelle zwischen Modulen und nach aussen.
Typen, Validation, sensitive
Typen explizit setzen: string, number,
bool, list(...), map(...),
object({...}) — fängt Fehler beim Plan
statt beim Apply.
validation-Block prüft Eingaben mit
condition + error_message.
sensitive = true maskiert Werte im CLI-Output,
entfernt sie aber nicht aus State oder Plan-Datei.
variable "env" {
type = string
validation {
condition = contains(["dev","prod"], var.env)
error_message = "env muss dev oder prod sein."
}
}
locals {
name_prefix = "app-${var.env}"
}
output "lb_dns" {
value = aws_lb.this.dns_name
description = "Public LB-Endpoint"
}
Provider & Versionen
Version-Pinning
required_version pinnt die CLI,
required_providers pinnt Provider
(source + version).
Pessimistic-Operator ~> erlaubt nur
Patch-/Minor-Updates innerhalb der Grenze.
.terraform.lock.hcl einchecken
— exakte Provider-Versionen + Hashes für alle
Umgebungen. terraform init -upgrade ist eine
bewusste Aktion, kein Default.
Provider-Alias
Mehrere Regionen/Accounts: gleicher Provider mit
alias, Zuordnung per provider = am
Resource- bzw. providers =-Map am Modul-Block.
terraform {
required_version = ">= 1.10, < 2.0"
required_providers {
aws = { source = "hashicorp/aws",
version = "~> 6.0" }
}
}
provider "aws" { region = "eu-central-1" }
provider "aws" {
alias = "dr"
region = "eu-west-1"
}
resource "aws_s3_bucket" "replica" {
provider = aws.dr
bucket = "${local.name_prefix}-replica"
}
State
Remote Backend, Locking
State gehört in ein Remote Backend (S3, azurerm, gcs, …): gemeinsamer Stand + Locking gegen parallele Applies.
S3: natives Locking via use_lockfile = true
(seit 1.10, GA in 1.11); die DynamoDB-Argumente sind seit 1.11
deprecated.
terraform {
backend "s3" {
bucket = "tf-state-prod"
key = "network/terraform.tfstate"
region = "eu-central-1"
use_lockfile = true # natives S3-Locking
encrypt = true
}
}
State ist sensibel
Der State enthält Resource-Attribute im Klartext — auch Passwörter und Keys. Zugriff wie auf ein Secret beschränken, Verschlüsselung at rest aktivieren.
Werte aus dem State heraushalten: ephemeral Values/Resources (1.10+) und write-only Attribute (1.11+) werden nicht persistiert.
State-Kommandos, Drift
state list / state show: Inventar
inspizieren. state mv / state rm:
imperative Chirurgie — für geplantes Refactoring
besser moved/removed-Blöcke.
Drift: plan -refresh-only zeigt
Abweichungen der Realität vom State,
apply -refresh-only übernimmt sie in den State
(ohne Infrastruktur-Änderung).
terraform state list terraform state show aws_s3_bucket.logs terraform state mv aws_iam_role.a aws_iam_role.b terraform state rm aws_iam_role.orphan terraform plan -refresh-only # Drift-Report
Module
Struktur
Standard-Layout: main.tf,
variables.tf, outputs.tf
(+ README). Kleine, fachlich geschnittene Module;
Komposition flach im Root-Modul. Tiefe
Modul-in-Modul-Ketten machen Plans träge und Schnittstellen
unklar.
Quellen, Versionierung
Registry-Quelle <ns>/<name>/<provider>
+ version (nur Registry-Quellen unterstützen
version).
Git-Quelle über ?ref= auf Tag/Commit pinnen
— nie auf einen beweglichen Branch.
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 6.0"
name = local.name_prefix
cidr = "10.20.0.0/16"
}
module "baseline" {
source = "git::https://git.example.com/tf/baseline.git?ref=v2.3.1"
}
Meta-Argumente
for_each, count, depends_on
for_each über Map/Set: stabile
Keys als Adresse — Einträge löschen
verschiebt keine Nachbarn.
count: nur für identische Kopien oder das
An/Aus-Muster (count = var.enabled ? 1 : 0). Bei
benannten Objekten drohen Index-Shifts.
depends_on: nur wenn die implizite
Abhängigkeit über Referenzen nicht ausreicht (z.B.
IAM-Policy vor Nutzung).
lifecycle
create_before_destroy: Ersatz zuerst hochziehen.
prevent_destroy: Plan schlägt fehl, statt z.B.
eine Datenbank zu löschen.
ignore_changes: einzelne Attribute extern
verwalten lassen — chirurgisch einsetzen.
replace_triggered_by: Ersatz an fremde
Änderung koppeln.
resource "aws_iam_user" "team" {
for_each = toset(["ci", "audit", "ops"])
name = "svc-${each.key}"
}
resource "aws_db_instance" "main" {
# ...
lifecycle {
prevent_destroy = true
ignore_changes = [password]
}
}
dynamic blocks
Wiederholte Nested Blocks
dynamic "<block>" + for_each +
content erzeugt wiederholte verschachtelte
Blöcke; Iterator heisst wie das Block-Label.
Sparsam einsetzen: verschachtelte dynamics sind
schwer lesbar — oft ist die ausgeschriebene Liste klarer.
resource "aws_security_group" "web" {
name = "${local.name_prefix}-web"
dynamic "ingress" {
for_each = var.service_ports # z.B. {http=80, https=443}
content {
from_port = ingress.value
to_port = ingress.value
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"]
}
}
}
Plan / Apply / Destroy
Kern-Workflow
fmt -check + validate als schnelles
Gate, dann plan -out=tfplan und
apply tfplan — es wird exakt der
reviewte Plan angewandt.
plan -detailed-exitcode: 0 = keine
Änderung, 2 = Änderungen — sauber für
Pipelines. -replace=<addr> erzwingt gezielte
Neuerstellung. destroy nur für ganze
(Wegwerf-)Umgebungen.
-target ist ein Notfall-Werkzeug
-target schneidet den
Abhängigkeitsgraphen ab — das Ergebnis entspricht
nicht mehr der Konfiguration. Nur zur Incident-Reparatur, danach
sofort ein voller Plan ohne -target, bis er leer
ist.
terraform fmt -check -recursive terraform validate terraform plan -out=tfplan -detailed-exitcode terraform apply tfplan terraform apply -replace=aws_instance.worker[2]
Import & Refactoring
import-Block (1.5+)
Bestehende Infrastruktur deklarativ
adoptieren: plannbar, reviewbar, im normalen Plan/Apply-Zyklus.
-generate-config-out=<datei> erzeugt HCL
für importierte Objekte ohne Config. for_each
am import-Block seit 1.7.
moved / removed
moved (1.1+): Adresse geändert (Rename,
Modul-Umzug) — Terraform migriert den State beim Plan,
kein state mv.
removed (1.7+): Objekt aus Config + State
entfernen, ohne es zu zerstören
(destroy = false).
import {
to = aws_s3_bucket.logs
id = "legacy-logs-bucket"
}
moved {
from = aws_instance.app
to = module.compute.aws_instance.app
}
removed {
from = aws_s3_bucket.archiv
lifecycle { destroy = false }
}
Workspaces & Tests
Workspaces und ihre Grenzen
Ein Workspace = eigener State bei gleicher Config und gleichem Backend. Gut für kurzlebige Varianten (Feature-Test, Review-Umgebung).
Grenze: Prod/Non-Prod-Trennung —
gleiche Credentials, gleicher State-Bucket,
terraform.workspace-Verzweigungen im Code.
Dafür getrennte Root-Module + Backends verwenden.
terraform workspace new review-142
terraform workspace select review-142
terraform workspace list
# im Code: name = "app-${terraform.workspace}"
terraform test (1.6+)
Tests in *.tftest.hcl: run-Blöcke
führen command = plan (schnell, ohne echte
Ressourcen) oder apply aus und prüfen
assert-Bedingungen. Seit 1.7:
mock_provider + Overrides —
Provider-Antworten simulieren statt echte Infrastruktur
anzulegen.
# tests/naming.tftest.hcl
run "prod_prefix" {
command = plan
variables { env = "prod" }
assert {
condition = local.name_prefix == "app-prod"
error_message = "Prefix-Schema verletzt"
}
}
CI/CD-Patterns
Plan-Artefakt, Approval
Build-Job: plan -out=tfplan, Plan-Artefakt +
lesbaren show-Output an den Review hängen.
Nach Approval wendet der Apply-Job dasselbe
Artefakt an — kein zweiter Plan zwischen Review
und Apply, keine unbemerkte Differenz.
Pipeline-Hygiene
init -input=false (keine interaktiven Prompts),
fmt -check + validate als frühes
Gate, Lock-Datei committed.
State-Zugriff nur für die Pipeline-Identität; kurzlebige Cloud-Credentials statt statischer Keys.
terraform init -input=false terraform plan -input=false -out=tfplan terraform show -no-color tfplan > plan.txt # Review # --- nach Approval --- terraform apply -input=false tfplan
Version & Lizenz
1.x-Release-Linie, BUSL
Aktuelle stabile Linie: 1.15 (Stand Juli 2026).
Seit 1.6 steht Terraform unter der Business Source License 1.1 (Change License: MPL 2.0 nach vier Jahren); die Einschränkung zielt auf konkurrierende Angebote. Versionen bis 1.5.x bleiben MPL 2.0. OpenTofu ist ein MPL-2.0-lizenzierter Community-Fork auf Basis von Terraform 1.5.
Anti-Patterns
Was du nicht tun solltest
-target als Routine: zerschnittener Graph, Code und Realität divergieren — nur im Notfall.
State von Hand editieren: JSON-Chirurgie
korrumpiert Serial/Lineage — state-Kommandos
oder moved/removed nutzen.
count für benannte Objekte:
Löschen in der Mitte verschiebt Indizes — Nachbarn
werden zerstört/neu erzeugt. for_each
nehmen.
Provider/Module unpinned: nächstes
init zieht ein Major-Update in den Apply —
pinnen + Lock-Datei committen.
Secrets als Klartext-Variable: landen in State und Plan-Datei — ephemeral/write-only nutzen, State-Zugriff beschränken.
Workspaces als Prod-Trennung: ein Backend, eine Credential-Grenze für alles — getrennte Root-Module + Backends.
Apply ohne Plan-Artefakt in CI: zwischen
Review und Apply entsteht ein anderer Plan — immer
apply tfplan.
Monolith-Root-Modul: ein State für alles = maximaler Blast-Radius und träge Plans — nach Lebenszyklus/Team schneiden.
ignore_changes als Dauerpflaster: verdeckt Drift und echte Ownership-Fragen — klären, wer das Attribut verwaltet.
Aus der OMNI52 Cheatsheet-Fabrik
Verwandte Sheets in dichter Senior-Qualität:
vault-cheatsheet.de — Secrets-Management
flux-cheatsheet.de — GitOps mit Flux
argocd-cheatsheet.de — GitOps mit Argo CD
rancher-cheatsheet.de — Cluster-Management
Lizenz & Weiterverteilung
Nicht erlaubt: Logo, Marken oder den Eindruck zu vermitteln, dass der Inhalt von dir/euch stammt oder dass OMNI52 GmbH die Weiterverwendung sponsort.
Volltext der Lizenz: creativecommons.org/licenses/by-sa/4.0/deed.de.
Terraform and the Terraform logo are trademarks of HashiCorp, Inc. (HashiCorp is an IBM company). OMNI52™ is a trademark of OMNI52 GmbH (filed, not yet registered). This website is operated by OMNI52 GmbH and is not affiliated with, endorsed by, or sponsored by HashiCorp, Inc. or IBM. “Terraform” is used in a nominative / descriptive sense to indicate the technology this cheatsheet documents.