# Infrastructure as code

Optional

Declaring the platform's infrastructure, such as buckets, catalogs, clusters, roles and permissions, in version-controlled files, and letting tools create and update it to match, instead of configuring it by hand.

> [!NOTE]
>
> **Before reading**
>
> [Environments and CI](<https://wiki.4tm0.net/data/environments-and-ci.html>)

## Why it matters

A platform configured by clicking through consoles cannot be reproduced, reviewed or recovered reliably. Nobody knows exactly how production differs from test, a forgotten setting breaks a restore, and changes leave no record of who made them or why. Declaring infrastructure as code gives it the same review, history and repeatability as pipeline code, and makes new environments, regions or tenants a matter of configuration.

## How it works

Infrastructure as code (IaC) tools work declaratively: files describe the desired state, such as a bucket with versioning and a given encryption key, and the tool compares it with the actual state and makes the changes needed. Running the same files again changes nothing if nothing drifted.

Terraform is the most widely used tool for cloud resources: storage, networks, IAM roles, managed databases and services, through providers for each platform. From version 1.6 (licence change announced in August 2023) Terraform is licensed under the Business Source License 1.1; OpenTofu is an open-source fork of 1.5 under the Mozilla Public License 2.0, a Linux Foundation project in the CNCF sandbox since April 2025, and largely compatible (checked October 2026). Cloud providers also offer their own tools. For applications on Kubernetes, such as a catalog service, query engine or orchestrator, Helm charts package the deployment and its settings. GitOps tools, such as Argo CD or Flux, keep a Kubernetes cluster in sync with configuration in Git, applying changes when they are merged.

IaC tools keep state: a record of which real resources correspond to which declarations. State is stored remotely, locked during changes so two runs cannot conflict, and protected, because it can contain sensitive values.

The workflow mirrors pipeline code. A change is proposed in a pull request; CI runs a plan that shows exactly what will be created, changed or destroyed; reviewers check the plan; on merge, the change is applied, first to test, then to production. A plan that destroys a production bucket should be impossible to miss.

```
flowchart LR
  PR[Pull request:<br/>add EU bucket] --> PL[CI: plan<br/>shows changes]
  PL --> RV[Review plan]
  RV -->|merge| AT[Apply to test]
  AT --> AP[Apply to production]
  AP --> ST[(Remote state,<br/>locked)]
  DR[Drift check:<br/>schedule] -.->|differences| AL[Alert]
```

Environments and regions reuse the same code with different variables: one module for a regional stack, applied for the EU and the US with their own names, keys and sizes. This is what makes environments truly comparable and new regions quick to add.

Drift is the difference between code and reality, from manual changes in consoles or emergency fixes. Scheduled plans detect it: `terraform plan -detailed-exitcode` (or `tofu plan`) exits with code 2 when real infrastructure differs from the code; the fix is either to bring the change into code or to revert it. Consoles can be made read-only for most people to prevent drift in the first place.

Not everything belongs in the same tool. Long-lived infrastructure fits IaC tools well. Data objects, such as tables and models, are better managed by dbt and the catalog; grants and policies tied to data are often managed by governance-as-code tooling. The boundary should be explicit, so that two tools never manage the same object.

## Key terms

- Infrastructure as code (IaC) Managing infrastructure through declarative, version-controlled files.
- Declarative Describing the desired end state rather than the steps to reach it.
- State The tool's record mapping declarations to real resources.
- Plan A preview of the changes an apply would make.
- Drift Differences between declared and actual infrastructure.
- Module A reusable package of infrastructure declarations.
- GitOps Keeping systems in sync with configuration stored in Git.

## Example

An excerpt of the shop's EU stack in Terraform: the lake bucket, the catalog database and the read policy for the query engine (the role and policy document are declared elsewhere in the module):

```
module "lake_eu" {
  source      = "./modules/lake-bucket"
  name        = "lake-eu"
  region      = "eu-central-1"
  kms_key_arn = module.kms_eu.key_arn
  versioning  = true
  retention_noncurrent_days = 30
}

resource "aws_glue_catalog_database" "shop" {
  name = "shop"
}

resource "aws_iam_role_policy" "trino_eu_read" {
  role   = aws_iam_role.trino_eu.id
  policy = data.aws_iam_policy_document.read_lake_eu.json
}
```

Adding a US stack is a second module call with US values. When an engineer changed the bucket's lifecycle rule in the console during an incident, the nightly drift check flagged it, and the change was added to the code the next day.

## Pitfalls

- Console changes alongside code. Drift accumulates until applies undo emergency fixes.
- Applying without reading the plan. Destroys and replacements slip through.
- Local or unlocked state. Two runs at once can corrupt it.
- Secrets in code or state without protection. Store secrets in a secrets manager and restrict state access.
- Two tools managing one object. They overwrite each other's changes.

## Learn more

- [OpenTofu documentation](<https://opentofu.org/docs/>)
- [Terraform documentation](<https://developer.hashicorp.com/terraform/docs>)
- Kief Morris, Infrastructure as Code, 3rd edition (O'Reilly, 2025).

## Related

[Governance as code](<https://wiki.4tm0.net/data/governance-as-code.html>) [Disaster recovery](<https://wiki.4tm0.net/data/disaster-recovery.html>) [Encryption and key management](<https://wiki.4tm0.net/data/encryption-and-key-management.html>) [Cost and FinOps](<https://wiki.4tm0.net/data/cost-finops.html>)
