Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"group": "Infrastructure",
"pages": [
"infrastructure/overview",
"infrastructure/iac-tooling",
{
"group": "Repos",
"pages": [
Expand Down
100 changes: 100 additions & 0 deletions infrastructure/iac-tooling.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
title: "IaC tooling: OpenTofu and Terragrunt"
description: "OpenTofu is the canonical engine across every dryvist IaC repo. Terraform survives only in legacy repo names. Terragrunt stays where it does real work and is being phased out where it is a thin wrapper."
tier: 1
---

> The engine is OpenTofu. "Terraform" is gone from everything except the repo names — and renaming those would cost more than it's worth.

This page is the decision record for two questions that come up every time someone opens an IaC repo here: *are we on Terraform or OpenTofu?* and *do we actually need Terragrunt?* Short answers: OpenTofu, and only sometimes.

## The engine is OpenTofu

Every active IaC repo in the org already runs OpenTofu, not Terraform. This isn't aspirational — it's the current state:

- CI uses [`opentofu/setup-opentofu`](https://github.com/opentofu/setup-opentofu) and invokes `tofu fmt` / `init` / `validate` / `test`.
- Lockfiles pin `registry.opentofu.org/*` providers, not `registry.terraform.io`.
- The Nix dev shell ships both binaries with the intent spelled out in a comment: `opentofu # canonical binary`, `terraform # shipped for compatibility`.
- `.envrc` files set `TERRAGRUNT_TFPATH=tofu` / `PCT_TFPATH=tofu` so every wrapper dispatches to OpenTofu.

For a homelab and personal org, OpenTofu is the no-regrets default and there is no reason to walk it back:

- **Licensing.** OpenTofu ships under MPL 2.0 (OSI-approved) under the Linux Foundation. Terraform since 1.6 ships under BSL 1.1, which the OSI does not approve, and is now owned by IBM (the HashiCorp acquisition closed December 2024). None of the cases that justify staying on Terraform — HCP Stacks dependencies, IBM Cloud Pak environments, procurement that mandates HashiCorp as a vendor — apply here. ([scalr](https://scalr.com/learning-center/opentofu-vs-terraform), [oneuptime](https://oneuptime.com/blog/post/2026-03-20-opentofu-mpl-terraform-bsl-licensing/view))
- **Native state encryption.** OpenTofu encrypts state (including remote state) natively, without an external KMS workflow.
- **Dynamic config.** OpenTofu 1.8+ allows variables and locals inside `backend` and `provider` blocks — the single biggest reason teams historically reached for a wrapper. ([encore](https://encore.dev/articles/opentofu-vs-terraform-2026))

## "Terraform" is just a legacy prefix

Repos named `terraform-proxmox`, `terraform-unifi`, `terraform-aws`, and friends predate the migration. The prefix is historical; the engine inside is `tofu`. Newer repos already broke the pattern — `.github-tofu` uses the `tofu-*` prefix.

**Convention going forward:** new IaC repos use `tofu-*`. Existing `terraform-*` repos keep their names.

We do **not** mass-rename. A rename would churn module `source` references (`git::https://github.com/dryvist/terraform-aws-template.git?ref=v0.1.0`), GitHub redirect history, CI configuration, and every docs link — for cosmetic gain. This is the same reasoning that keeps `JacobPEvans/*` references in place rather than rewriting them to `dryvist/*`: the redirect holds, and the churn would have to be maintained forever. When a `terraform-*` reference reads as the engine rather than the repo name, fix the word; never rename the repo on sight.

## Where Terragrunt earns its place

Terragrunt is used in most repos, but in this environment it is mostly a *configuration wrapper*, not the multi-module orchestrator it was built to be. It does real work in exactly two repos — `terraform-proxmox` and `terraform-unifi` — where it:

- decrypts SOPS files through the `sops_decrypt_file` data source,
- merges a three-layer input model — committed `deployment.json`, SOPS-encrypted config, and Doppler environment variables,
- resolves IP addressing in `locals` with `cidrhost()`,
- and runs an `after_hook` that syncs OpenTofu output into the downstream Ansible inventory.

OpenTofu has no native `after_hook`, and SOPS decryption needs a provider rather than a built-in function. Until those two patterns have a native replacement (a SOPS provider plus a CI step), Terragrunt stays in these two repos.

{/* Shape: parallel convergence. 5 nodes. Three sources converge into the Terragrunt merge, one edge to the engine. Boundary crossings: 0. Pass. */}

```mermaid
%%{init: {'theme':'base','look':'handDrawn','themeVariables':{'fontFamily':'Geist','fontSize':'14px','primaryColor':'#102937','primaryTextColor':'#F4EFE6','primaryBorderColor':'#4FB3A9','lineColor':'#4FB3A9','secondaryColor':'#0B1D2A','tertiaryColor':'#1A2A38','clusterBkg':'rgba(79,179,169,0.08)','clusterBorder':'#4FB3A9'}}}%%
flowchart LR
DJ([deployment.json<br/>committed])
SOPS([SOPS<br/>encrypted-at-rest])
DOP([Doppler<br/>runtime secrets])
TG([Terragrunt<br/>merge + IP resolution])
TOFU([OpenTofu<br/>apply])

DJ --> TG
SOPS --> TG
DOP --> TG
TG --> TOFU

classDef source fill:#102937,stroke:#4FB3A9,stroke-width:2px,color:#F4EFE6;
classDef engine fill:#102937,stroke:#E06B4A,stroke-width:2px,color:#F4EFE6;

class DJ,SOPS,DOP,TG source
class TOFU engine

linkStyle 0,1,2,3 stroke:#F4EFE6,stroke-width:1.5px;
```

This layered merge is the actual justification for Terragrunt here — not the backend block it also happens to generate.

## What we're phasing out

Everywhere else, Terragrunt only generates a backend and a provider block. OpenTofu 1.8+ does both natively now, and native state encryption removes the KMS rationale — so the wrapper buys nothing. The direction:

| Repo | Engine | Terragrunt today | Direction |
| --- | --- | --- | --- |
| `terraform-proxmox` / `terraform-unifi` | tofu | SOPS + 3-layer merge + `after_hook` | Keep — earns its place |
| `terraform-github` / `terraform-runs-on` / `terraform-aws` (basic) | tofu | Backend + provider generation only | Phase out → native OpenTofu config |
| `tf-splunk-aws` | tofu | Multi-env `include` (dev/stg/prod) | Re-evaluate → native per-env `tfvars` |
| `.github-tofu` | tofu | None | Already native — the target shape |

We lose nothing we actually use: no repo uses `dependency` / `dependencies` cross-module wiring, `run-all`, stacks, or multi-account fan-out. The orchestration features that justify Terragrunt at scale ([terragrunt.com](https://terragrunt.com/), [terramate comparison](https://terramate.io/rethinking-iac/terramate-vs-terragrunt-a-2026-comparison/)) simply aren't in play. The removals themselves are tracked in each affected repo, not here — this page records the *why*, not the migration steps.

## See also

<CardGroup cols={2}>
<Card title="Check placement" icon="list-check" href="/infrastructure/terraform-check-placement">
Where IaC checks run — static in pre-commit, credentialed in CI via OIDC. Already mandates deleting Terragrunt hooks.
</Card>
<Card title="SOPS for IaC" icon="key" href="/infrastructure/secrets-sops">
The encrypted-at-rest layer that keeps Terragrunt necessary in the two complex repos.
</Card>
<Card title="CI/CD policy" icon="scale-balanced" href="/infrastructure/cicd/policy">
Marketplace actions, release-please, version pinning, plan/apply via OIDC.
</Card>
<Card title="Infrastructure overview" icon="server" href="/infrastructure/overview">
The provision-then-configure chain this engine sits at the head of.
</Card>
</CardGroup>
12 changes: 6 additions & 6 deletions infrastructure/overview.mdx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
---
title: "Infrastructure"
description: "Terraform modules for Proxmox, AWS disaster recovery, Bedrock agents, static sites, and self-hosted GitHub Actions runners."
description: "OpenTofu modules for Proxmox, AWS disaster recovery, Bedrock agents, static sites, and self-hosted GitHub Actions runners."
tier: 1
---

> Provision once with Terraform, configure with Ansible, run forever.
> Provision once with OpenTofu, configure with Ansible, run forever.

The infrastructure layer is Terraform-managed. Every module is opinionated about deployment shape: LXC for production homelab workloads, Docker on a dedicated VM only when vendor-locked, AWS for disaster recovery and managed services.
The infrastructure layer is OpenTofu-managed — the `terraform-*` repo names are legacy; the engine inside is `tofu` (see [IaC tooling](/infrastructure/iac-tooling)). Every module is opinionated about deployment shape: LXC for production homelab workloads, Docker on a dedicated VM only when vendor-locked, AWS for disaster recovery and managed services.

## The Proxmox stack

Expand All @@ -15,7 +15,7 @@ The infrastructure layer is Terraform-managed. Every module is opinionated about
```mermaid
%%{init: {'theme':'base','look':'handDrawn','themeVariables':{'fontFamily':'Geist','fontSize':'14px','primaryColor':'#102937','primaryTextColor':'#F4EFE6','primaryBorderColor':'#4FB3A9','lineColor':'#4FB3A9','secondaryColor':'#0B1D2A','tertiaryColor':'#1A2A38','clusterBkg':'rgba(79,179,169,0.08)','clusterBorder':'#4FB3A9'}}}%%
flowchart LR
TP([Proxmox provisioning<br/>Terraform])
TP([Proxmox provisioning<br/>OpenTofu])
AP([Proxmox config<br/>Ansible])
APA([Apps on Proxmox<br/>Ansible])

Expand All @@ -30,7 +30,7 @@ flowchart LR
linkStyle 0,1 stroke:#F4EFE6,stroke-width:1.5px;
```

Terraform builds VMs and LXCs (coral). Ansible takes the inventory and configures hosts (green), then deploys the app stack on top.
OpenTofu builds VMs and LXCs (coral). Ansible takes the inventory and configures hosts (green), then deploys the app stack on top.

## AWS module map

Expand Down Expand Up @@ -79,7 +79,7 @@ Terraform builds VMs and LXCs (coral). Ansible takes the inventory and configure
The four-question decision tree for every new workload.
</Card>
<Card title="SOPS for IaC" icon="key" href="/infrastructure/secrets-sops">
Encrypted-at-rest secrets across Terraform and Ansible repos.
Encrypted-at-rest secrets across OpenTofu and Ansible repos.
</Card>
<Card title="Self-hosted Netflix" icon="film" href="/infrastructure/media-stack">
Media library on a dedicated VLAN.
Expand Down
3 changes: 3 additions & 0 deletions infrastructure/terraform-check-placement.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ Direnv already activates the Nix dev shell on `cd` — no `nix develop` wrapper
## Where to go next

<CardGroup cols={2}>
<Card title="IaC tooling" icon="layer-group" href="/infrastructure/iac-tooling">
Why OpenTofu is the engine and where Terragrunt is being phased out — the decision behind these rules.
</Card>
<Card title="CI/CD policy" icon="scale-balanced" href="/infrastructure/cicd/policy">
Marketplace actions, release-please, version pinning, runner choice.
</Card>
Expand Down
Loading