hashicorp/terraform
Stacks
Active contributors: terraform-core team, Liam Cervante.
Purpose
Stacks is the newest major subsystem in Terraform — an orchestration layer above individual Terraform modules. A stack is a collection of components, each of which is itself a Terraform configuration. The stacks runtime plans and applies the whole stack as one unit, with explicit input/output wiring between components.
Stacks have their own configuration language (*.tfstack.hcl), their own plan and state formats, their own scheduler (internal/promising/), and their own RPC-API surface so that HCP Terraform can drive them. As of 1.16-dev, stacks are still under active development; many parts are gated behind experiment flags.
Directory layout
internal/stacks/
├── README.md
├── stackaddrs/ # address types for stack-level objects
├── stackconfig/ # parser + static decoder for the stacks language
├── stackplan/ # stack-specific plan model + marshaling
├── stackstate/ # stack-specific state model + marshaling
├── stackruntime/ # runtime: plan, apply, evaluation
│ └── internal/stackeval/ # internal evaluation engine (see its README)
├── stackmigrate/ # migration helpers (e.g. promote module → component)
├── stackutils/ # shared utilities
├── tfstackdata1/ # internal protobuf for plan/state durability
└── staticcheck.conf # local linter overridesThe CLI front door is internal/command/stacks.go, and the RPC surface is in internal/rpcapi/stacks.go (a 57 KB file — the largest in rpcapi/).
Key types
| Type | Package | Description |
|---|---|---|
stackconfig.Config |
internal/stacks/stackconfig/ |
The whole stack configuration tree. |
stackconfig.Component |
internal/stacks/stackconfig/ |
One component "name" { source = "..." } block — points at a Terraform module to instantiate as part of the stack. |
stackaddrs.Component, Stack, Provider |
internal/stacks/stackaddrs/ |
Stack-level addresses, layered above addrs. |
stackplan.Plan |
internal/stacks/stackplan/ |
The stack-wide plan; embeds module-level plans.Plans for each component. |
stackstate.State |
internal/stacks/stackstate/ |
The stack-wide state. |
stackruntime.Stack |
internal/stacks/stackruntime/ |
The runtime entry point: planning and applying a stack. |
stackeval.Main |
internal/stacks/stackruntime/internal/stackeval/ |
Internal evaluator; orchestrates promises across components. |
How it works
graph TD
User[stack config files] --> Cfg[stackconfig.Load]
Cfg --> Plan[stackruntime.Plan]
Plan --> Eval[stackeval.Main]
Eval --> Promising[internal/promising]
Promising --> Component[per-component plan]
Component --> Modules[terraform.Context.Plan for each module]
Modules --> Plans[modules-level *plans.Plan]
Plans --> StackPlan[stackplan.Plan]
StackPlan --> Save[Save to stack store via rpcapi]Why a different scheduler
Stacks introduces dynamic dependencies that don't fit a static DAG:
- Components can pass outputs to other components.
- The set of components in a stack can be
for_each-expanded based on input variables. - Components can have their own provider configurations defined at the stack level.
internal/dag/ requires the graph to be known at construction time. The stacks runtime needed graph edges to materialize lazily as components are evaluated. The promise-based library at internal/promising/ is the answer: a component's evaluation produces promises, downstream components await those promises, and the scheduler runs as much as it can in parallel until everything is settled.
This is why stacks coexist with the regular Terraform engine instead of replacing it: at the component level, ordinary terraform.Context runs over an ordinary configuration; the stacks layer just orchestrates which components run when.
Configuration
A stack is configured via .tfstack.hcl files. The shape is:
component "network" {
source = "./modules/network"
inputs = {
region = var.region
}
}
component "database" {
source = "./modules/database"
inputs = {
vpc_id = component.network.vpc_id
region = var.region
}
}stackconfig parses these into typed Go values, much like internal/configs does for .tf files but with a smaller block vocabulary.
Plan and state
stackplan.Plan is not a plans.Plan — it's a wrapper that embeds component-level plans. Similarly, stackstate.State wraps per-component states. The on-disk format is protobuf, defined in internal/stacks/tfstackdata1/. The README explicitly states this format is internal — external callers must use the RPC API.
RPC API integration
The vast majority of stacks usage flows through the RPC API rather than direct CLI invocation. HCP Terraform talks to a Terraform Core process via gRPC, asking it to "plan this stack" or "apply this stack." The implementation is in internal/rpcapi/stacks.go (57 KB), which exposes the stacks runtime over a gRPC interface defined in internal/rpcapi/terraform1/stacks/. See rpcapi.
The CLI command (terraform stacks ...) is mostly for local development and testing; production usage is RPC-driven.
Migration
stackmigrate/ helps users move existing Terraform configurations into stacks. The use case: a user has several modules each managed as a separate workspace, and wants to combine them into a stack. The migrator preserves their existing state so the resulting stack apply is a no-op against real infrastructure.
Stacks evaluator (stackeval)
The internal evaluator at internal/stacks/stackruntime/internal/stackeval/ has its own README that goes into more detail. The structure follows the engine's pattern (per-thing types like Component, Provider, Variable, Output) but each type implements a Promising interface that produces and consumes promises rather than fitting into a static graph.
Integration points
- CLI:
internal/command/stacks.goprovidesterraform stacks .... Many subcommands route to the runtime via the RPC API even when invoked locally. - Engine: at the component level, the stack runtime instantiates the regular
terraform.Contextfrominternal/terraform/. Stacks does not replace the engine; it composes it. - RPC: the surface in
internal/rpcapi/stacks.gois how HCP Terraform drives stacks remotely. - Configs:
internal/stacks/stackconfig/is parallel tointernal/configs/— they don't share parser code, but they share the underlying HCL machinery. - Promising:
internal/promising/is used exclusively by stacks (and indirectly by the rpcapi surface).
Entry points for modification
- Adding a new top-level construct to the stacks language: extend
stackconfig(block schema, parsed model, decoder) andstackeval(evaluation rules). - Adding a feature to the runtime: usually goes in
stackeval/first, with new types or methods onMainand the per-thing evaluators. - Extending the RPC surface: edit the protobufs under
internal/rpcapi/terraform1/stacks/, regenerate viamake protobuf, and implement the new RPC ininternal/rpcapi/stacks.go.
For the existing engine that stacks builds on top of, see terraform-core. For the RPC layer, see rpcapi.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.