Inhaltsverzeichnis26 Abschnitte
- Der Data-Platform-IaC-Stack
- Layer 1: Cloud-Ressourcen mit Terraform
- Modul-Struktur für Data-Platform-Infrastruktur
- Data-Lake-Modul-Beispiel
- Glue-Catalog as Code
- Layer 2: Data-Warehouse-Infrastruktur
- Databricks-Workspace mit Terraform
- Layer 3: Schema-as-Code mit dbt
- dbt-Projekt-Struktur für Platform-Teams
- dbt-Model mit Data-Contract
- Layer 4: GitOps-Workflows für Data-Pipelines
- CI/CD-Pipeline-Architektur
- GitHub-Actions-Workflow
- Environment-Promotion-Strategie
- Variable-Sets pro Environment
- State-Management und Secrets
- Remote-State mit Locking
- Secrets via Vault + Terraform
- Häufige Pitfalls und Mitigation
- FAQ
- Wo fange ich an, wenn ich noch gar kein IaC habe?
- Soll ich für Databricks Terraform oder DABs nutzen?
- Wie behandle ich DSGVO-Anforderungen in IaC?
- Wie verhindere ich, dass jemand manuell ändert?
- Wie schnell ist eine IaC-Migration realistisch?
- Schluss
TL;DR: IaC für Data-Plattformen spannt vier Layer: Cloud-Ressourcen (Terraform), Daten-Infrastruktur (Databricks/Snowflake-Provider), Schemas/Pipelines (dbt) und Data-Products. Teams, die das richtig machen, haben einen einzigen PR-Flow für alles — Dev ändert dbt-Model, Terraform-Modul und Airflow-DAG in einem Commit, CI validiert alle drei Layer.
Die Disziplin Infrastructure as Code hat verändert, wie wir Compute und Networking managen. Data-Plattformen haben das langsamer adoptiert — Pipelines lebten in Jupyter-Notebooks, Schema-Änderungen wurden manuell appliziert, "Environment-Promotion" hieß SQL-Files zwischen Ordnern kopieren. Diese Ära ist vorbei.
Dieser Guide deckt ab, wie du rigide IaC-Prinzipien auf moderne Data-Plattformen anwendest — von den zugrundeliegenden Cloud-Ressourcen bis zu den Schemas, Pipelines und Governance-Policies, die darauf laufen.
Der Data-Platform-IaC-Stack
Ein vollständiger IaC-Ansatz für Data-Plattformen wirkt auf vier Layern:
graph TB
L4[Layer 4: Data Products<br/>dbt models, ML pipelines, notebooks]
L3[Layer 3: Platform Services<br/>Airflow DAGs, Spark configs, catalog schemas]
L2[Layer 2: Data Infrastructure<br/>Warehouses, lakes, streaming brokers]
L1[Layer 1: Cloud Resources<br/>VPCs, IAM, storage, compute]
L4 --> L3 --> L2 --> L1
style L1 fill:#1e3a5f,color:#fff
style L2 fill:#1a5276,color:#fff
style L3 fill:#154360,color:#fff
style L4 fill:#0e2f44,color:#fff
Die meisten Teams haben L1 mit Terraform abgedeckt. L2 wird interessant. L3 und L4 sind, wo Teams üblicherweise fallen.
Layer 1: Cloud-Ressourcen mit Terraform
Modul-Struktur für Data-Platform-Infrastruktur
terraform/
├── modules/
│ ├── data-lake/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── README.md
│ ├── streaming-platform/
│ ├── data-warehouse/
│ └── governance/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ └── terraform.tfvars
│ ├── staging/
│ └── prod/
└── shared/
├── backend.tf
└── providers.tf
Data-Lake-Modul-Beispiel
# modules/data-lake/main.tf
locals {
bucket_name = "${var.project_prefix}-${var.environment}-lakehouse"
common_tags = merge(var.tags, {
Module = "data-lake"
Environment = var.environment
ManagedBy = "terraform"
})
}
resource "aws_s3_bucket" "lakehouse" {
bucket = local.bucket_name
tags = local.common_tags
}
resource "aws_s3_bucket_versioning" "lakehouse" {
bucket = aws_s3_bucket.lakehouse.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_lifecycle_configuration" "lakehouse" {
bucket = aws_s3_bucket.lakehouse.id
rule {
id = "bronze-to-glacier"
status = "Enabled"
filter { prefix = "bronze/" }
transition {
days = 90
storage_class = "GLACIER_IR"
}
}
rule {
id = "silver-intelligent-tiering"
status = "Enabled"
filter { prefix = "silver/" }
transition {
days = 30
storage_class = "INTELLIGENT_TIERING"
}
}
}
# Lake Formation permissions
resource "aws_lakeformation_resource" "lakehouse" {
arn = aws_s3_bucket.lakehouse.arn
role_arn = aws_iam_role.lakeformation_service.arn
}
resource "aws_lakeformation_permissions" "analyst_access" {
for_each = toset(var.analyst_role_arns)
principal = each.value
permissions = ["SELECT", "DESCRIBE"]
table {
database_name = aws_glue_catalog_database.silver.name
wildcard = true
}
}
Glue-Catalog as Code
# modules/data-lake/catalog.tf
resource "aws_glue_catalog_database" "bronze" {
name = "${var.project_prefix}_bronze"
description = "Raw ingested data — immutable, append-only"
create_table_default_permission {
permissions = ["ALL"]
principal {
data_lake_principal_identifier = "IAM_ALLOWED_PRINCIPALS"
}
}
}
resource "aws_glue_catalog_database" "silver" {
name = "${var.project_prefix}_silver"
description = "Cleaned, validated, conformed data"
}
resource "aws_glue_catalog_database" "gold" {
name = "${var.project_prefix}_gold"
description = "Business-ready aggregates and data products"
}
Layer 2: Data-Warehouse-Infrastruktur
Databricks-Workspace mit Terraform
# modules/databricks-workspace/main.tf
terraform {
required_providers {
databricks = {
source = "databricks/databricks"
version = "~> 1.38"
}
}
}
resource "databricks_cluster_policy" "data_engineering" {
name = "data-engineering-${var.environment}"
definition = jsonencode({
"spark_version" : {
"type" : "allowlist",
"values" : ["13.3.x-scala2.12", "14.3.x-scala2.12"],
"defaultValue" : "14.3.x-scala2.12"
},
"node_type_id" : {
"type" : "allowlist",
"values" : ["m5d.xlarge", "m5d.2xlarge", "m5d.4xlarge"]
},
"autotermination_minutes" : {
"type" : "fixed",
"value" : 60,
"hidden" : false
},
"custom_tags.team" : {
"type" : "fixed",
"value" : var.team_tag
}
})
}
resource "databricks_sql_warehouse" "main" {
name = "${var.project_prefix}-${var.environment}"
cluster_size = var.environment == "prod" ? "Medium" : "X-Small"
auto_stop_mins = 5
min_num_clusters = 1
max_num_clusters = var.environment == "prod" ? 3 : 1
tags {
custom_tags {
key = "environment"
value = var.environment
}
}
}
resource "databricks_catalog" "main" {
name = var.catalog_name
comment = "Main Unity Catalog for ${var.environment}"
properties = {
purpose = "data-platform"
}
}
Layer 3: Schema-as-Code mit dbt
Schema-Management verdient dieselbe Rigidität wie Infrastruktur. dbt liefert das für Transformations-Logik; kombiniere es mit Schema-Migrationen für deine operativen Datenbanken.
dbt-Projekt-Struktur für Platform-Teams
dbt_project/
├── dbt_project.yml
├── profiles.yml # managed via Vault / environment vars
├── models/
│ ├── staging/ # raw -> typed, renamed
│ ├── intermediate/ # business logic
│ └── marts/ # data products for consumers
├── tests/
│ ├── generic/ # reusable test macros
│ └── singular/ # one-off assertions
├── macros/
├── seeds/ # small reference tables, version-controlled
└── analyses/ # ad-hoc, not materialised
dbt-Model mit Data-Contract
# models/marts/finance/schema.yml
version: 2
models:
- name: fct_revenue
description: "Daily revenue fact table — SLA: 99.9% freshness within 2h of close"
config:
contract:
enforced: true # dbt will fail if types don't match
columns:
- name: revenue_id
data_type: varchar
constraints:
- type: not_null
- type: unique
- name: amount_usd
data_type: numeric(18,4)
constraints:
- type: not_null
- name: transaction_date
data_type: date
constraints:
- type: not_null
tests:
- dbt_expectations.expect_column_values_to_be_between:
column_name: amount_usd
min_value: 0
max_value: 10000000
Layer 4: GitOps-Workflows für Data-Pipelines
CI/CD-Pipeline-Architektur
flowchart LR
Dev[Developer<br/>local branch] -->|PR| GH[GitHub / GitLab]
GH -->|CI trigger| CI{CI Pipeline}
CI -->|terraform plan| TFP[Terraform Plan<br/>Preview]
CI -->|dbt compile| DBT[dbt Compile<br/>+ lint]
CI -->|schema tests| ST[Schema<br/>Regression Tests]
CI -->|contract tests| CT[API Contract<br/>Tests via Harbinger]
TFP & DBT & ST & CT -->|all pass| Merge[Merge to main]
Merge -->|CD trigger| CD{CD Pipeline}
CD -->|terraform apply dev| DEV[Dev Environment]
DEV -->|smoke tests pass| STG[Staging]
STG -->|integration tests pass| PROD[Production]
GitHub-Actions-Workflow
# .github/workflows/data-platform-ci.yml
name: Data Platform CI
on:
pull_request:
paths:
- 'terraform/**'
- 'dbt_project/**'
- 'airflow/dags/**'
jobs:
terraform-plan:
runs-on: ubuntu-latest
defaults:
run:
working-directory: terraform/environments/staging
steps:
- uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.7.x"
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_CICD_ROLE_ARN }}
aws-region: eu-west-1
- name: Terraform Init
run: terraform init -backend-config="key=staging/terraform.tfstate"
- name: Terraform Plan
run: terraform plan -out=tfplan.binary
- name: Convert Plan to JSON
run: terraform show -json tfplan.binary > tfplan.json
- name: Validate Plan (no deletes in prod tables)
run: |
python scripts/validate_plan.py tfplan.json --no-destroy-pattern "aws_glue_catalog_table" --no-destroy-pattern "aws_s3_bucket.lakehouse"
dbt-ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dbt
run: pip install dbt-spark dbt-expectations
- name: dbt deps
run: dbt deps
working-directory: dbt_project
- name: dbt compile (syntax check)
run: dbt compile --profiles-dir . --target ci
working-directory: dbt_project
- name: dbt test (dev schema)
run: dbt test --profiles-dir . --target ci --store-failures
working-directory: dbt_project
Environment-Promotion-Strategie
graph LR
CODE[Code / Config] -->|auto-deploy| DEV[dev<br/>shared cluster<br/>sample data]
DEV -->|PR merge| STG[staging<br/>prod-size cluster<br/>anonymised prod data]
STG -->|release tag| PROD[prod<br/>autoscaling<br/>full data]
style PROD fill:#c0392b,color:#fff
style STG fill:#d35400,color:#fff
style DEV fill:#27ae60,color:#fff
Variable-Sets pro Environment
# environments/prod/terraform.tfvars
environment = "prod"
databricks_cluster_size = "Large"
min_workers = 2
max_workers = 20
enable_spot = true
spot_bid_price_pct = 80
backup_retention = 30
alert_email = "data-platform-oncall@company.com"
State-Management und Secrets
Remote-State mit Locking
# shared/backend.tf
terraform {
backend "s3" {
bucket = "company-terraform-state"
key = "${var.environment}/data-platform/terraform.tfstate"
region = "eu-west-1"
dynamodb_table = "terraform-state-locks"
encrypt = true
kms_key_id = "arn:aws:kms:eu-west-1:123456789:key/mrk-xxx"
}
}
Secrets via Vault + Terraform
data "vault_generic_secret" "databricks_token" {
path = "secret/data-platform/${var.environment}/databricks"
}
resource "databricks_token" "pipeline_sa" {
comment = "CI/CD service account — managed by Terraform"
lifetime_seconds = 7776000 # 90 days, rotated by Vault
}
resource "vault_generic_secret" "databricks_token_output" {
path = "secret/data-platform/${var.environment}/databricks"
data_json = jsonencode({
token = databricks_token.pipeline_sa.token_value
})
}
Häufige Pitfalls und Mitigation
| Pitfall | Symptom | Mitigation |
|---|---|---|
| State-Drift | Terraform-Plan zeigt unerwartete Änderungen | terraform apply nur via CI/CD; State-Bucket schützen |
| Snowflake-Resources | Manuelle Schema-Änderungen -> Drift | Alle Changes via PR; Drift-Detection in CI |
| Secret-Sprawl | Credentials in Git, S3, Parameter Store, Vault | Single-Secrets-Backend; Terraform liest, speichert nie |
| Modul-Versionierung | Updates brechen alle Konsumenten | Modul-Versionen pinnen; Private-Registry nutzen |
| Lange Plan-Zeiten | 45-min-Plans -> Dev-Frustration | State in kleinere Files brechen; target für Hotfixes |
FAQ
Wo fange ich an, wenn ich noch gar kein IaC habe?
Mit L1 — Cloud-Ressourcen in Terraform abbilden. Erst Storage, dann IAM, dann Compute. L2/3/4 ergänzen, wenn L1 stabil läuft.
Soll ich für Databricks Terraform oder DABs nutzen?
DABs für reine Databricks-Workspaces (Jobs, Pipelines, Notebooks). Terraform für Cross-Cloud-Topologien und Identity. In der Praxis: beide zusammen, Terraform für Workspace-Provisionierung, DABs für Jobs/Pipelines.
Wie behandle ich DSGVO-Anforderungen in IaC?
Storage in EU-Region pinnen (Terraform-Variable), Audit-Logs aktivieren, Lifecycle-Policies für Retention. Sensible Daten in separaten Catalogs/Buckets mit strikteren Permissions.
Wie verhindere ich, dass jemand manuell ändert?
Terraform-Drift-Detection in CI laufen lassen (täglich), manuelle Änderungen detektieren, in Slack alerten. Bei strikten Setups: SCP/Service-Control-Policies in AWS, die manuelle Console-Änderungen blocken.
Wie schnell ist eine IaC-Migration realistisch?
Für ein Team mit 5 Engineers und einem mittelgroßen Stack: 3–6 Monate für L1, weitere 3–6 Monate für L2 und Schema-as-Code. L4 (Data-Products) ist laufend.
Schluss
IaC für Data-Plattformen ist nicht nur Terraform. Es ist eine Disziplin, die Cloud-Ressourcen (L1), Daten-Infrastruktur (L2), Schemas und Pipelines (L3) und Data-Products (L4) umspannt. Jeder Layer braucht Versionskontrolle, CI/CD-Gates und Environment-Promotion.
Die Teams, die das gut machen, haben einen einzigen Pull-Request-Flow für alles: ein Entwickler ändert ein dbt-Model, Terraform-Modul und Airflow-DAG im selben Commit, CI validiert alle drei Layer und Promotion nach Produktion ist ein One-Click-Release.
Stand: 14. Mai 2026.
Geschrieben von
Harbinger Team
Cloud-, Data- und AI-Engineer in DACH. Schreibt seit 2018 über infrastrukturkritische Tech-Entscheidungen — keine Marketing- Folien, sondern echte Trade-offs aus Production-Workloads.
Hat dir das geholfen?
Jede Woche ein neuer Artikel über DACH-Cloud, Data und AI — direkt in dein Postfach. Kein Spam, kein Marketing-Sprech.
Kein Spam. 1-Klick-Abmeldung. Datenschutz bei Loops.so.