Terraform Stacks and the Myth of Contained Blast Radius
I’ve found that Terraform Stacks works really well as long as the dependency chain is purely infrastructure. One component produces outputs, another consumes them, and everything converges deterministically. The problems start as soon as I introduce a runtime dependency — where a downstream component depends on an upstream application actually being alive and serving traffic. At that point, the model shifts from “infrastructure graph” to “distributed system health,” and Terraform Stacks doesn’t always communicate that distinction clearly.
This matters because one of the main reasons I’m using Terraform Stacks in the first place is to contain blast radius. I want failures to be scoped, attributable, and localized to the components where they actually occur.
In my case, I’m working with a stack that looks like this: global → regional-stamp → api. The intent is straightforward. The global component lays down shared foundations. The regional-stamp component provisions the per-region runtime, including an Azure Container App that hosts my service. The api component depends on regional-stamp, but not because it needs a subnet ID or a resource group name—it depends on the application itself being healthy and reachable.
That distinction turns out to be the entire point of the problem.
The “Expectation”
When I model this as separate stack components, I’m making an explicit architectural statement: these things are related, but they are not the same thing. I’m deliberately drawing boundaries so that failures don’t ripple farther than they need to.
If a runtime deployment fails inside regional-stamp—for example, the Container App never becomes healthy—I expect Terraform Stacks to help me contain that failure. I expect it to say: global is healthy, regional-stamp is healthy from an infrastructure perspective, and api failed because a runtime dependency wasn’t available.
That’s the blast radius I’m trying to achieve. The failure should stop at the api component, because that’s where the dependency on runtime behavior actually exists. The whole value of breaking a stack into components is to avoid a single failure making everything look broken.
The “Actual”
What I see in practice is that Terraform Stacks reports failures across all components, even when global resources are fully provisioned and the regional infrastructure largely exists. From the outside, it looks like the entire stack failed, even though the real problem is narrow and well understood.
The root cause is that my api component uses a data source to read the OpenAPI specification hosted by the Azure Container App created in regional-stamp. If that Container App is unavailable—bad image, misconfiguration, startup failure, temporary outage—the OpenAPI endpoint can’t be reached. Terraform can’t evaluate the data source, and the apicomponent fails.
So far, that’s expected. What’s not expected is how that failure is surfaced.
Because api depends on regional-stamp, Terraform Stacks treats the run as a single orchestrated operation across the dependency chain. When the downstream component fails to evaluate, the entire run is marked as failed, and that failure is reflected across components. The reporting doesn’t preserve the idea that “these upstream components converged successfully, but a downstream runtime consumer failed.”
At that point, the blast radius is no longer contained. The component boundaries exist in code, but not in how failure is communicated.
Why this undermines the promise of stacks
The reason this is frustrating is that it cuts directly against the motivation for using Terraform Stacks. I’m not just organizing code; I’m trying to express operational boundaries. I want to be able to say, with confidence, “this part of the system is fine, that part isn’t.”
Instead, a runtime failure in a downstream component effectively poisons the entire stack execution. From HCP Terraform’s point of view, this makes sense — it couldn’t compute a value it needed. From an operator’s point of view, it’s noisy and misleading. Infrastructure that is clearly provisioned gets lumped into a failure state, and the signal I care about is buried.
The OpenAPI data source makes this especially visible, but it’s really just a symptom. Any time a component depends on a live runtime endpoint during evaluation, the same problem appears: operational availability becomes a hard prerequisite for convergence, and failures stop being local.
The real issue I’m running into
At the core, I’m asking Terraform to retrieve a runtime artifact from a runtime endpoint and then use that artifact to drive further provisioning. Terraform is very good at managing infrastructure state transitions. It is not designed to reason about partial runtime health while still converging infrastructure elsewhere.
Once I do that, Terraform can’t distinguish between “this upstream infrastructure is broken” and “this downstream runtime dependency isn’t ready yet.” And Terraform Stacks, built on top of that model, can’t present the failure in a way that respects the component boundaries I defined.
So even though I’ve structured my stack to limit blast radius, the behavior and reporting don’t reflect that structure.
How I’m thinking about evolving this
I don’t actually want to remove the dependency between my service and the API layer. What I want is for Terraform Stacks to stop amplifying a localized runtime failure into a stack-wide failure.
The most reliable way I see to do that — right now — is to stop making Terraform depend on a live service during the same run that’s still bringing that service up. Treating the OpenAPI spec as a versioned build artifact, stored somewhere stable, removes the runtime dependency from Terraform’s evaluation path. The api component can still depend on the spec, but not on the liveness of the service at that moment.
However, sometimes that’s not possible. When we are dealing with components of our Terraform Stack that is provisioning the downstream component’s control plane for a different provider that is going to provisiong resources to that new control plane. A great example of this is an AKS cluster and the kubernetes or helm provider. I might have a component provision the AKS cluster and then a downstream component provision Kubernetes resources to it. There is an inherent runtime dependency there — just like in my case. Granted Kubernetes is probably less buggy then my application code but the principle remains the same.
Another option is to split responsibilities more explicitly. Let Terraform Stacks handle provisioning and structural dependencies, and move runtime-derived configuration — like importing an OpenAPI spec from a live endpoint — into a separate step that’s allowed to fail without invalidating upstream success. That keeps failures scoped and makes the blast radius explicit. I like this option but it requires some updates to Terraform Stacks.
Conclusion
The core point of this article is not that runtime dependencies are hard — that’s expected. The point is that when I use Terraform Stacks, I expect it to help me contain the blast radius of failures between components. In a global → regional-stamp → api stack, a runtime failure in api should not make global and regional infrastructure look broken.
Right now, using data sources that depend on live runtime endpoints breaks that promise. A downstream failure collapses the component boundaries I carefully defined and turns a localized issue into a stack-wide failure. The more I rely on Terraform Stacks for orchestration, the more important it becomes to align my design with what Terraform can actually model — and to keep runtime health out of the critical path if I want blast radius containment to mean something.
I am still relatively new to Terraform Stacks. Maybe I am doing this wrong?