Having recently completed a migration from a pure Terraform monorepo to a Terragrunt-managed structure for a multi-region, multi-account AWS deployment, I am compelled to question the prevailing wisdom that Terragrunt is a de facto solution for DRY configurations. My experience suggests that the abstraction layer it introduces creates significant operational friction that often outweighs its theoretical benefits, particularly as team size and module complexity scale.
The core promise is sound: eliminate duplication in backend configurations, provider blocks, and common variables. However, the reality involves grappling with a new DSL on top of HCL, obscure error propagation through multiple layers of `terraform.hcl` and `terragrunt.hcl` files, and a loss of transparency. Debugging a `plan` failure becomes an exercise in tracing through `read_terragrunt_config`, `generate` blocks, and `dependency` blocks, rather than inspecting straightforward Terraform code. Consider the following comparison:
**Pure Terraform with shared module pattern:**
```hcl
# root/prod/us-east-1/vpc/main.tf
module "vpc" {
source = "../../../modules/vpc"
environment = "prod"
region = "us-east-1"
cidr_block = "10.1.0.0/16"
}
```
The flow is linear and entirely within Terraform's native semantics.
**Terragrunt implementation:**
```hcl
# terragrunt.hcl
terraform {
source = "git::ssh://git@github.com/company/modules.git//vpc?ref=v1.2.0"
}
include "root" {
path = find_in_parent_folders()
}
inputs = {
cidr_block = "10.1.0.0/16"
}
```
While DRY, you now must manage the `include` logic, the remote source resolution, and the merging of inputs from multiple files. The state management abstraction can also become a vulnerability; a misconfigured `remote_state` block can have catastrophic consequences with less intuitive guardrails.
The pain points crystallize during migration and advanced patterns:
* **State Operations:** Terragrunt's wrapper commands (`run-all`) can obscure the granular control sometimes needed for targeted state operations, requiring workarounds that bypass the tool itself.
* **Provider Management:** Handling multiple providers with complex authentication (e.g., for multi-cloud or private registry modules) often requires `generate` blocks, which is essentially meta-programming and breaks the mental model of a static configuration.
* **Cognitive Load:** New engineers must learn both Terraform and Terragrunt's specific patterns. The abstraction leaks constantly, forcing the team to understand both layers deeply anyway, negating the simplicity argument.
* **Toolchain Dependency:** You are now locked into Terragrunt's release cycle and its interpretation of Terraform's lifecycle. Upgrading Terraform itself requires verifying compatibility with Terragrunt, adding another matrix dimension to maintenance.
I have observed that a well-designed, conventional Terraform structure using a combination of symlinks, CI/CD orchestration, and disciplined use of shared modules can achieve 90% of the DRY benefits with 50% of the complexity. The question I pose to the community is whether my experience is an outlier. For those who have migrated to or from Terragrunt, did the added layer prove to be a net positive in the long term, or did it simply exchange one form of complexity for another, perhaps more opaque one? Specifically, in environments leveraging service meshes (like Istio) or complex Kubernetes deployments where the infrastructure topology is already non-trivial, does Terragrunt genuinely reduce entropy?
Boring is beautiful
I'm actually in the middle of evaluating Terragrunt for a similar multi-account setup, so this is timely. That point about error propagation really hits home - I've spent hours in trial runs just trying to understand where a variable got lost between nested configs.
Is there a specific tipping point where you felt the abstraction became too much? Like, was it when you added a third region, or when certain modules reached a particular complexity?
Our team is only three people right now, but we're growing. I'm worried about exactly this kind of debugging overhead for new hires.
The debugging overhead you're describing is real, and it hits hardest during incident response. When a critical module breaks at 3am, you need to see the problem, not decode a wrapper. Terragrunt's error messages are notoriously contextual, often pointing you to a generated file rather than the source.
That said, for teams with very high configuration duplication across dozens of nearly-identical stacks, the pain might shift from managing duplication to debugging abstraction. It's a trade-off. Have you looked at using a simpler wrapper script or CI pipeline to inject common backend/provider config, avoiding the extra DSL altogether? It's less elegant but often more transparent.
The tipping point? It's not about scale, it's about time. The moment you're onboarding someone new and you have to explain "the terragrunt way" for the umpteenth time instead of just Terraform, you've lost. Their docs are a maze, and that overhead you're worried about? It's guaranteed.
You're only three people. Run. Use symlinks and a few shared files. Ugly, but transparent. The alternative is spending hours next year untangling why a variable from `account.hcl` isn't overriding the one in `region/terragrunt.hcl`. Again.
—aB
I feel the "explain the umpteenth time" pain, but from the learner's side. I'm still new to Terraform itself, and adding Terragrunt on top just feels like another language to trip over. It's like you're not just onboarding them to your code, you're onboarding them to a whole new tool's logic.
Is the real issue that Terragrunt is a leaky abstraction? Like, you still need to know Terraform's details, but now you also have to know how Terragrunt layers it all together?
Precisely. A leaky abstraction is the perfect description, and it's costly. The entire premise of a tool like this is to manage complexity, but if you spend more cycles debugging the wrapper than you save by eliminating duplication, the abstraction has failed its primary job. New hires don't just learn two tools; they learn the fragile, undocumented interactions *between* them, which is the real tax.
You still need to understand the underlying Terraform module's inputs, outputs, and state. Terragrunt doesn't change that. It just adds a new, often inscrutable, layer of indirection for passing those values. When a plan fails because `dependency` outputs aren't resolved as you expect, you're debugging Terragrunt's execution graph, not your infrastructure.
That cognitive overhead directly impacts Mean Time to Repair during incidents. A transparent, if slightly repetitive, pure Terraform structure with clear variable files is often faster to diagnose, even if it looks less elegant in a diagram.
P99 or bust.