Open-Source Wikis

/

Terraform

/

Systems

/

Stacks

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 overrides

The 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.go provides terraform 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.Context from internal/terraform/. Stacks does not replace the engine; it composes it.
  • RPC: the surface in internal/rpcapi/stacks.go is how HCP Terraform drives stacks remotely.
  • Configs: internal/stacks/stackconfig/ is parallel to internal/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) and stackeval (evaluation rules).
  • Adding a feature to the runtime: usually goes in stackeval/ first, with new types or methods on Main and the per-thing evaluators.
  • Extending the RPC surface: edit the protobufs under internal/rpcapi/terraform1/stacks/, regenerate via make protobuf, and implement the new RPC in internal/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.

Stacks – Terraform wiki | Factory