# Terraform Stacks

> Guide for HashiCorp Terraform Stacks — creating, modifying, and validating Stack configurations (.tfcomponent.hcl, .tfdeploy.hcl), managing multi-region/multi-environment infrastructure.

- Skill: `lidge-jun/terraform-stacks` (Agent Skill, multi-file: 7 files)
- Install (CLI): `npx skillmds@latest add lidge-jun/terraform-stacks`
- Raw SKILL.md: https://api.skillmd.com/api/skills/lidge-jun/terraform-stacks/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: lidge-jun (https://skillmd.com/u/lidge-jun)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/lidge-jun/terraform-stacks

---


# Terraform Stacks

## Core Concepts

- **Stack**: Infrastructure unit composed of components and deployments, managed together.
- **Component**: Abstraction around a Terraform module — specifies source, inputs, and providers.
- **Deployment**: Instance of all components with specific input values (per environment, region, or account).
- **Stack Language**: Separate HCL-based language with distinct blocks and file extensions (not regular Terraform HCL).

## File Structure

| Extension | Purpose |
|-----------|---------|
| `.tfcomponent.hcl` | Component configuration |
| `.tfdeploy.hcl` | Deployment configuration |
| `.terraform.lock.hcl` | Provider lock file (generated) |

All files live at the Stack repository root. HCP Terraform processes them in dependency order.

### Recommended Layout

```
my-stack/
├── variables.tfcomponent.hcl
├── providers.tfcomponent.hcl
├── components.tfcomponent.hcl
├── outputs.tfcomponent.hcl
├── deployments.tfdeploy.hcl
├── .terraform.lock.hcl              # generated
└── modules/                         # only if using local modules
    ├── s3/
    └── compute/
```

**Module sources** (no local `modules/` dir needed for remote sources):
- Local: `./modules/vpc`
- Public registry: `terraform-aws-modules/vpc/aws`
- Private registry: `app.terraform.io/<org-name>/vpc/aws`
- Git: `git::https://github.com/org/repo.git//path?ref=v1.0.0`

## Component Configuration (.tfcomponent.hcl)

### Variable Block

Variables require a `type` field. The `validation` argument is not supported.

```hcl
variable "aws_region" {
  type        = string
  description = "AWS region for deployments"
  default     = "us-west-1"
}

variable "identity_token" {
  type        = string
  description = "OIDC identity token"
  ephemeral   = true  # prevents persistence in state
}

variable "instance_count" {
  type     = number
  nullable = false
}
```

Use `ephemeral = true` for credentials and tokens (prevents state persistence). Use `stable` for longer-lived values like license keys.

### Required Providers Block

```hcl
required_providers {
  aws = {
    source  = "hashicorp/aws"
    version = "~> 6.0"
  }
  random = {
    source  = "hashicorp/random"
    version = "~> 3.5.0"
  }
}
```

### Provider Block

Differences from traditional Terraform:
1. Supports `for_each` meta-argument
2. Aliases defined in block header (not as argument)
3. Configuration via nested `config` block

```hcl
# Single provider
provider "aws" "this" {
  config {
    region = var.aws_region
    assume_role_with_web_identity {
      role_arn           = var.role_arn
      web_identity_token = var.identity_token
    }
  }
}

# Multiple providers with for_each
provider "aws" "configurations" {
  for_each = var.regions
  config {
    region = each.value
    assume_role_with_web_identity {
      role_arn           = var.role_arn
      web_identity_token = var.identity_token
    }
  }
}
```

Prefer **workload identity** (OIDC) — avoids long-lived static credentials, provides temporary scoped credentials per deployment run. Configure with `identity_token` blocks and `assume_role_with_web_identity`. Setup: https://developer.hashicorp.com/terraform/cloud-docs/dynamic-provider-credentials

### Component Block

Each Stack needs at least one component. Components reference modules from local paths, registries, or Git.

```hcl
component "vpc" {
  source  = "app.terraform.io/my-org/vpc/aws"
  version = "2.1.0"

  inputs = {
    cidr_block  = var.vpc_cidr
    name_prefix = var.name_prefix
  }

  providers = {
    aws = provider.aws.this
  }
}
```

**Referencing outputs:**
- Single instance: `component.<name>.<output>`
- With `for_each`: `component.<name>[key].<output>`
- Aggregate: `[for x in component.s3 : x.bucket_name]`
- Provider refs: `provider.<type>.<alias>` or `provider.<type>.<alias>[each.value]`

Dependencies are inferred automatically from component references.

See `references/component-blocks.md` for dependencies, for_each, public registry, Git sources.

### Output Block

Outputs require a `type` argument. `preconditions` not supported.

```hcl
output "vpc_id" {
  type        = string
  description = "VPC ID"
  value       = component.vpc.vpc_id
}

output "endpoint_urls" {
  type  = map(string)
  value = {
    for region, comp in component.api : region => comp.endpoint_url
  }
}
```

### Locals Block

Works identically in `.tfcomponent.hcl` and `.tfdeploy.hcl`:

```hcl
locals {
  common_tags = {
    Environment = var.environment
    ManagedBy   = "Terraform Stacks"
    Project     = var.project_name
  }
}
```

### Removed Block

Safely remove components. Requires the component's providers for teardown.

```hcl
removed {
  from   = component.old_component
  source = "./modules/old-module"
  providers = {
    aws = provider.aws.this
  }
}
```

## Deployment Configuration (.tfdeploy.hcl)

### Identity Token Block

Generate JWT tokens for OIDC authentication:

```hcl
identity_token "aws" {
  audience = ["aws.workload.identity"]
}
```

Reference in deployments: `identity_token.<name>.jwt`

### Store Block

Access HCP Terraform variable sets:

```hcl
store "varset" "aws_credentials" {
  id       = "varset-ABC123"
  source   = "tfc-cloud-shared"
  category = "terraform"
}

deployment "production" {
  inputs = {
    aws_access_key = store.varset.aws_credentials.AWS_ACCESS_KEY_ID
  }
}
```

See `references/deployment-blocks.md` for details.

### Deployment Block

Minimum 1, maximum 20 deployments per Stack:

```hcl
deployment "production" {
  inputs = {
    aws_region     = "us-west-1"
    instance_count = 3
    role_arn       = local.role_arn
    identity_token = identity_token.aws.jwt
  }
}

deployment "development" {
  inputs = {
    aws_region     = "us-east-1"
    instance_count = 1
    identity_token = identity_token.aws.jwt
  }
}
```

To destroy: set `destroy = true`, upload configuration, approve destroy run, then remove the block. See `references/deployment-blocks.md`.

### Deployment Group Block

Group deployments for shared settings (Premium tier):

```hcl
deployment_group "canary" {
  auto_approve_checks = [deployment_auto_approve.safe_changes]
}

deployment "dev" {
  inputs           = { /* ... */ }
  deployment_group = deployment_group.canary
}
```

### Deployment Auto-Approve Block

Auto-approve rules for deployment plans (Premium tier):

```hcl
deployment_auto_approve "safe_changes" {
  deployment_group = deployment_group.canary
  check {
    condition = context.plan.changes.remove == 0
    reason    = "Cannot auto-approve plans with resource deletions"
  }
}
```

Context variables: `context.plan.applyable`, `context.plan.changes.add/change/remove/total`, `context.success`

`orchestrate` blocks are deprecated — use `deployment_group` and `deployment_auto_approve` instead.

### Publish Output and Upstream Input

Link Stacks by publishing outputs from one and consuming in another:

```hcl
# Network Stack — publish
publish_output "vpc_id_network" {
  type  = string
  value = deployment.network.vpc_id
}

# Application Stack — consume
upstream_input "network_stack" {
  type   = "stack"
  source = "app.terraform.io/my-org/my-project/networking-stack"
}

deployment "app" {
  inputs = {
    vpc_id = upstream_input.network_stack.vpc_id_network
  }
}
```

See `references/linked-stacks.md` for complete documentation.

## CLI Commands

GA as of Terraform CLI v1.13+. Stacks count toward RUM for HCP Terraform billing.

### Initialize and Validate

```bash
terraform stacks init              # Download providers, modules, generate lock file
terraform stacks providers-lock    # Regenerate lock file
terraform stacks validate          # Check syntax without uploading
```

### Deployment Workflow

No `plan` or `apply` commands — uploading configuration triggers deployment runs.

```bash
terraform stacks configuration upload
terraform stacks deployment-run list
terraform stacks deployment-group watch -deployment-group=...
terraform stacks deployment-run approve-all-plans -deployment-run-id=...
terraform stacks deployment-group approve-all-plans -deployment-group=...
terraform stacks deployment-run cancel -deployment-run-id=...
```

### Other Commands

```bash
terraform stacks create              # Create new Stack (interactive)
terraform stacks fmt                 # Format Stack files
terraform stacks list                # Show all Stacks
terraform stacks version             # Display version
terraform stacks configuration list
terraform stacks configuration fetch -configuration-id=...
terraform stacks deployment-group rerun -deployment-group=...
```

## API Monitoring

For automation/CI/CD, use the HCP Terraform API instead of CLI watch commands (which stream indefinitely).

- Artifacts endpoint for outputs: `GET /api/v2/stack-deployment-steps/{step-id}/artifacts?name=apply-description`
- Diagnostics endpoint requires `stack_deployment_step_id` query parameter
- Artifacts returns HTTP 307 redirect (use `curl -L`)

See `references/api-monitoring.md` for full workflow, authentication, and polling patterns.

## Common Patterns

- **Component dependencies**: Inferred automatically from output references.
- **Multi-region**: Use `for_each` on providers and components.
- **Deferred changes**: Handles values only known after apply (cluster endpoints, generated passwords).

See `references/examples.md` for complete working examples.

## Best Practices

1. Create components for logical infrastructure units that share a lifecycle
2. Modules used with Stacks cannot include provider blocks — configure providers in Stack config
3. Test public registry modules before production (some have compatibility issues with Stacks)
4. Each deployment has isolated state
5. Use variables for deployment-specific values; locals for shared values
6. Commit `.terraform.lock.hcl` to version control
7. Test in dev/staging before production

## Troubleshooting

- **Circular dependencies**: Refactor to break cycles or use intermediate components
- **Deployment destruction**: Set `destroy = true`, upload, approve destroy run
- **Empty diagnostics**: Add `stack_deployment_step_id` query parameter

See `references/troubleshooting.md` for detailed solutions.

## References

- `references/component-blocks.md` — Component block syntax and arguments
- `references/deployment-blocks.md` — Deployment block configuration options
- `references/linked-stacks.md` — Publishing outputs and upstream inputs
- `references/examples.md` — Multi-region and dependency examples
- `references/api-monitoring.md` — API workflow for automation
- `references/troubleshooting.md` — Common issues and solutions

