TL;DR: Once an infrastructure repo has several concerns — account hardening, the security baseline, the eventual signing stack — there is pressure to split them into separate stacks with separate state, wired together with remote-state lookups or Terragrunt. The infra repo keeps them in one OpenTofu graph instead. The reason: dependency ordering and shared values are strongest when the engine enforces them, and splitting buys back a weaker version of exactly what you gave up.
The pressure to split
The infra repo’s src/ has several concerns in it, and more coming, the signing stack among them. Once a repo reaches that point, there is a steady pressure to split: one stack per concern, each with its own state file.
It is an appealing pressure. Separate stacks feel modular. Each apply touches less, so the blast radius of any one run is smaller. And Terragrunt exists, popular and well-regarded, precisely to orchestrate a fleet of separate stacks. The path is well trodden.
infra did not take it. src/ is a single OpenTofu root stack: each concern is a module block, in its own main.<concern>.tf file, all sharing one state and one graph.
What one graph gives you
The thing a single graph gives you is engine-enforced truth about ordering and data.
Inside one OpenTofu graph, the tool builds the full dependency DAG itself. When the signing stack needs a value the security baseline produced, you reference it directly, module.baseline.something, and OpenTofu guarantees two things: the baseline is created before the thing that depends on it, and the value handed across is the current one from this same apply. Ordering and data-passing are not things you arranged. They are facts the engine checks and enforces, every plan, every apply.
What splitting costs
Split src/ into per-concern stacks with separate state, and that guarantee is the thing you spend.
Now one stack reads another’s outputs through terraform_remote_state. That is a lookup of a snapshot: the other stack’s last applied state, whatever it was, whenever that was. It is not a live edge in a graph. Ordering is no longer enforced by the engine either; it becomes something you arrange yourself, in CI stage sequencing or in Terragrunt’s own dependency blocks.
That is the trade, stated plainly. You give up a strong, engine-checked guarantee, and you buy back a weaker, hand-arranged imitation of it. Terragrunt is a good tool for managing that weaker world tidily. But the question worth asking first is whether you should be in the weaker world at all.
When splitting is genuinely right
This is not an argument that splitting is always wrong. Separate states genuinely earn their place when concerns have different change cadences, different access boundaries, or different teams owning them: when you actively want an apply of one to be unable to touch another, and you want different people holding different state.
infra has none of those. It is a single account, a single operator, one cohesive set of concerns. The only thing splitting would buy here is a smaller per-apply blast radius, and that is better handled by reviewing the plan before it applies, which the next post is about, than by fragmenting the dependency graph. So src/ stays one graph, and Terragrunt was considered and deliberately not adopted.
If ordering between graphs is ever needed
If infra ever does genuinely need more than one stack, the plan is not Terragrunt. It is to keep each stack a single strong graph internally, and to sequence the stacks with CI stages. Keep the engine-enforced guarantee where it is strongest, inside each graph, and reach for hand-arranged ordering only at the one seam where it is unavoidable.
Boiling it down
A multi-concern infrastructure repo feels like it should be split into per-concern stacks, and Terragrunt is right there to manage the result. infra keeps src/ as one OpenTofu graph instead.
Inside one graph, OpenTofu enforces dependency ordering and passes current values across module boundaries as checked facts. Split into separate states and that becomes a terraform_remote_state snapshot lookup plus ordering you arrange by hand: a weaker version of what you gave up. Splitting is right when concerns have different cadences, boundaries or owners; for a single-account, single-operator repo none of that applies, so the strong guarantee is worth keeping, and Terragrunt is the tool for a problem infra chose not to have.