

Infrastructure as Code has revolutionised how we manage cloud resources, and Terraform has been at the forefront of this movement. Terraform Stacks, announced at HashiConf 2025 as generally available, represents a major evolution in how we manage infrastructure at scale. If you’re new to Terraform Stacks, this guide will walk you through everything you need to know using a practical Google Cloud example.
https://www.hashicorp.com/en/blog/terraform-stacks-explained
What Are Terraform Stacks?
Think of Terraform Stacks as a way to organise and deploy your infrastructure more efficiently. They solve a fundamental challenge: as infrastructure grows, managing it across multiple environments, regions, or accounts becomes increasingly complex.
The Problem Stacks Solve:
Traditional Terraform workflows work great for single configurations, but scaling brings challenges:
- Copy-paste fatigue — Duplicating configurations leads to drift and maintenance nightmares
- Manual dependency stitching — When you split infrastructure across multiple configurations, you must manually manage dependencies between them
- State file sprawl — Managing dozens of separate state files becomes overwhelming
- Inconsistent rollouts — Coordinating changes across environments is error-prone
Traditional Terraform approach:
- You write a configuration
- You deploy it once to one environment
- To deploy to another region or environment, you copy everything and maintain multiple copies
Terraform Stacks approach:
- You write your infrastructure components once
- You define where you want to deploy them (regions, environments, accounts)
- Terraform Stacks handles deploying the same infrastructure to all those places
- Each deployment is independent but uses the same base configuration
Real-world analogy: It’s like having a blueprint for a house. Instead of drawing a new blueprint for every house you build, you use the same blueprint but can customise details like paint colour or room sizes for each location.
Key Stacks Capabilities
Terraform Stacks brings powerful features for managing infrastructure at scale:
1. Component-Based Architecture
Deploy entire applications with networking, storage, and compute as a single unit without worrying about dependencies. Stack configurations can be handed to users without advanced Terraform experience, enabling them to stand up complex infrastructure with a single action.
2. Multi-Region/Multi-Account Deployment
Deploy across multiple regions, availability zones, and cloud provider accounts without duplicating code. When a change is made to the Stack configuration, it can be rolled out across all, some, or none of the deployments.
3. Advanced Features
- Deferred Changes — Produce partial plans when encountering unknown values (perfect for Kubernetes workflows)
- Linked Stacks — Define cross-stack dependencies with automatic downstream updates
- Declarative Orchestration — Auto-approve plans that meet specific conditions
- CLI-First Workflows — Create, manage, and iterate on Stacks without VCS integration
What This Example Demonstrates
My gcp-stacks-example repository creates a complete infrastructure on Google Cloud Platform:
- Compute Engine — Virtual machines to run your applications
- VPC Network — Private network for your resources
- Firewall Rules — Security controls for network traffic
- Service Account — Identity for your resources to access GCP services
- Workload Identity Federation — Secure authentication without long-lived credentials
The Two Types of Files You Need to Know
Terraform Stacks uses two main types of configuration files.
Type 1: Components (*.tfcomponent.hcl)
Defines WHAT infrastructure you want to create.
Components are like building blocks. Each component manages one piece of your infrastructure (network, servers, databases, etc.). Let’s look at an example:
# components.tfcomponent.hcl
component "common" {
source = "./01-common"
inputs = {
project_id = var.project_id
region = var.region
}
providers = {
google = provider.google.default
random = provider.random.default
}
}component "project" {
source = "./02-project"
inputs = {
project_id = var.project_id
unique_id = component.common.unique_id
}
providers = {
google = provider.google.default
}
}
component "network" {
source = "./03-network"
inputs = {
network_name = format("%s-%s", "vpc", component.common.unique_id)
project_id = var.project_id
region = var.region
router_name = format("%s-%s", "cr", component.common.unique_id)
router_nat_name = format("%s-%s", "nr", component.common.unique_id)
subnet = {
subnet_name = format("%s-%s", "snet", component.common.unique_id)
subnet_ip = "10.64.0.0/16"
subnet_region = var.region
subnet_private_access = "true"
subnet_flow_logs = "true"
subnet_flow_logs_interval = "INTERVAL_10_MIN"
subnet_flow_logs_sampling = 0.7
subnet_flow_logs_metadata = "INCLUDE_ALL_METADATA"
}
unique_id = component.common.unique_id
}
providers = {
google = provider.google.default
}
}
component "compute" {
source = "./04-compute"
inputs = {
compute_sa_email = component.project.compute_sa_email
subnetwork_self_link = component.network.subnet_self_link
unique_id = component.common.unique_id
zone = component.common.zone
}
providers = {
google = provider.google.default
}
}
Key things to understand:
Components reference each other: The compute
component uses component.network.subnet_self_link
to get the network created by the network
component
- Terraform handles the order: You don’t need to worry about creating the network before the compute instances — Terraform Stacks figures it out
- Each component wraps a module: The actual Terraform code lives in the
./03-network
directory - Variables flow down: Values you define in deployments (coming next!) flow into these components
Understanding Provider Configuration
Before we move to deployments, let’s quickly cover how authentication works:
# providers.tfcomponent.hcl
required_providers {
google = {
source = "hashicorp/google"
version = "~> 6.34.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.0"
}
}provider "random" "default" {}
provider "google" "default" {
config {
project = var.project_id
region = var.region
external_credentials {
audience = var.audience
service_account_email = var.service_account_email
identity_token = var.identity_token
}
}
}
Instead of using a Google service account key, this uses temporary tokens with a 1 hour TTL that are automatically generated.
Type 2: Deployments (deployments.tfdeploy.hcl)
Defines WHERE and HOW MANY TIMES to deploy your infrastructure
# deployments.tfdeploy.hcl
identity_token "gcp" {
audience = ["hcp.workload.identity"]
}deployment "us-central1" {
inputs = {
identity_token = identity_token.gcp.jwt
audience = "//iam.googleapis.com/projects/1234567890/locations/global/workloadIdentityPools/wi-pool-gcp-stacks-example/providers/wi-provider-gcp-stacks-example"
project_id = "prj-123456789"
service_account_email = "gcp-stacks-example@prj-123456789.iam.gserviceaccount.com"
region = "us-central1"
}
}
Key things to understand:
- Each deployment is independent: Changes to
us-central1
won’t affect other deployments. - Each has its own state file: Terraform tracks resources separately for each deployment
- Same infrastructure, different locations: Both deployments use the same components, just in different regions
- Easy to add more: Want to deploy to Europe? Just add another deployment block
Optional: Automation with Orchestration (HCP Terraform Premium tier only)
You can set up rules to automatically approve certain changes. This is optional and more advanced:
# deployments.tfdeploy.hcl (continued)
# Auto-approve if there are no changes
deployment_auto_approve "no_changes" {
check {
condition = (context.plan.changes.add == 0 &&
context.plan.changes.change == 0 &&
context.plan.changes.remove == 0)
reason = "Plan contains changes that require manual review"
}
}
# Auto-approve new deployments (new resources, no changes, no deletions)
deployment_auto_approve "safe_changes" {
check {
condition = (context.plan.changes.add > 0 &&
context.plan.changes.change == 0 &&
context.plan.changes.remove == 0)
reason = "Plan adds new resources, no changes or resources removed"
}
}deployment_group "us-central1" {
auto_approve_checks = [
deployment_auto_approve.no_changes,
deployment_auto_approve.safe_changes
]
}
How Everything Works Together: A Visual Flow
Here’s what happens when you deploy:
1. You write components (network, compute, firewall)
↓
2. You define deployments (us-central1, us-east1)
↓
3. HCP Terraform reads your configuration
↓
4. For EACH deployment:
- Generates identity token
- Authenticates to GCP
- Creates network component
- Creates compute component (using network outputs)
- Creates firewall component (using network outputs)
- Tracks state separately
↓
5. You now have identical infrastructure in multiple regions!
Setting Up: Step-by-Step Walkthrough
Let’s walk through the entire setup process together.
Prerequisites
First, install the Terraform CLI version 1.13.x or newer:
# Install Terraform CLI (includes Stacks commands)
brew install hashicorp/tap/terraform
Google provider version 6.34 or above is required for the external_credentials
block configured in the provider.
You’ll also need:
- A GCP account
- A HCP Terraform account
- A GitHub account
Phase 1: Bootstrap (One-Time Setup)
The bootstrap phase creates the foundation — think of it as setting up the construction site before building the house.
Step 1: Fork the repository to your GitHub account
Step 2: Create a file called terraform.tfvars
in the 00-setup/
directory:
# 00-setup/terraform.tfvars
tfc_organization_name = "your-terraform-cloud-org-name"
gcp_project_id = "your-gcp-project-id"
Step 3: Run the bootstrap:
cd 00-setup
terraform init # Downloads required providers
terraform plan # Shows what will be created
terraform apply # Creates the resources (type 'yes' when prompted)
What this creates:
- A GCP project configured for your Stack
- A Workload Identity Pool (for secure authentication)
- A service account (identity for your resources)
- A HCP Terraform workspace
Phase 2: Configure Your Stack
Step 5: Open deployments.tfdeploy.hcl
in your repository and update it with your values from Step 3:
# Replace the placeholder values with your actual values from bootstrap output
deployment "us-central1" {
inputs = {
identity_token = identity_token.gcp.jwt
audience = "YOUR_AUDIENCE_VALUE_HERE"
project_id = "YOUR_PROJECT_ID_HERE"
service_account_email = "YOUR_SERVICE_ACCOUNT_EMAIL_HERE"
region = "YOUR_REGION_HERE"
}
}
Step 5a (Optional): Validate your Stack configuration locally:
# Initialize the Stack (downloads providers and modules)
terraform stacks init# Validate your configuration
terraform stacks validate
If validation succeeds, you’ll see:
Success! Terraform Stacks configuration is valid and ready for use.
Step 6: Commit and push your changes to GitHub:
git add deployments.tfdeploy.hcl
git commit -m "Configure Stack with bootstrap values"
git push
Phase 3: Deploy via HCP Terraform
Step 7: Log in to HCP Terraform and navigate to your organisation
Step 8: You should see a project called “stacks” was created during bootstrap. Click on it.
Step 9: Create a new Stack:
- Click the New button in the upper right
- Select Stack
- Connect your GitHub repository
- Name it
gcp-stacks-example
- Click Create Stack
Step 10: Fetch and deploy:
- Click Fetch configuration from VCS
- HCP Terraform will read your files and show the
us-central1
deployment - Click on the us-central1 deployment
- Click on the latest plan (e.g., “Plan 1”)
- Review what will be created
- Click Approve Plan
Step 11: Watch it deploy! You’ll see:
- Network being created
- Compute instances being created
- Firewall rules being applied
That’s it! Your infrastructure is now deployed.
Real-World Usage Examples
Example 1: Adding a New Region
Want to deploy to Europe? Just add a new deployment block:
deployment "europe-west1" {
inputs = {
audience = "YOUR_AUDIENCE_VALUE"
identity_token_file = identity_token.gcp.jwt_filename
project_id = "YOUR_PROJECT_ID"
service_account_email = "YOUR_SERVICE_ACCOUNT_EMAIL"
region = "europe-west1" # New region!
}
}
Commit, push, and HCP Terraform will automatically create a new deployment.
Common Beginner Questions
Here are answers to questions that frequently come up when learning Terraform Stacks:
Q: What’s the difference between a module and a component?
A: A module is the traditional Terraform code (*.tf files) that defines resources. A component is a Stacks concept that wraps a module and makes it part of your Stack. Think of modules as the actual code, and components as how you use that code in Stacks.
Q: Why do I need both tfcomponent.hcl and tfdeploy.hcl files?
A: They have different jobs:
*.tfcomponent.hcl
= WHAT to build (the components)*.tfdeploy.hcl
= WHERE to build it (the deployments)
This separation lets you define your infrastructure once and deploy it many times.
Q: Can I use my existing Terraform modules with Stacks?
A: Yes, you don’t need to rewrite anything. Just point your components to your existing modules on GitHub or Private Module Registry.
Q: What happens if I change a component?
A: All deployments using that component will get the change. That’s the power of Stacks — change once, deploy everywhere. But don’t worry — each deployment still plans and applies independently, so you can review changes for each region.
Cleaning Up
When you’re done experimenting:
Step 1: Add the following destroy=true
line to the deployments.tfdeploy.hcl
file:
deployment "us-central1" {
...destroy = true
}
- Commit your changes to your GitHub repository
- Review and approve the plan in HCP Terraform
Step 2: Clean up bootstrap resources:
Best Practices for Beginners
- Start with one deployment: Get comfortable with the workflow before adding multiple regions
- Use clear names: Name your components and deployments descriptively
- Validate locally before pushing: Run
terraform stacks validate
to catch errors early - Check the plan: Always review the plan before approving — see what will be created, changed, or destroyed
- Use version control: Commit changes to Git so you can track and revert if needed
- Keep components focused: Each component should manage logical components with a shared lifecycle, e.g. network, compute, database
- Document your variables: Use descriptions so you remember what each variable does
Why Terraform Stacks Matter
Traditional Terraform is great, but as you grow, you face challenges:
- Copy/paste fatigue: Copying configurations for each environment leads to drift and errors
- Coordinating changes: Updating all environments consistently is manual and error prone
- State management: Managing separate state files and workspaces gets complex
- Dependency tracking: Ensuring resources are created in the right order across environments is tricky
What You’ve Learned
By following this guide, you now understand:
- What Terraform Stacks are and why they’re useful
- The difference between components and deployments
- How to configure authentication with Workload Identity
- How to bootstrap and deploy a Stack
- How to add new regions or environments
Terraform Stacks CLI Command Reference
Beyond init
and validate
, the Terraform CLI provides several useful commands for working with Stacks. Here’s a quick reference:
Local Development Commands
# Initialize your Stack (download providers and modules)
terraform stacks init# Upgrade providers and modules to latest versions
terraform stacks init -upgrade
# Validate your Stack configuration
terraform stacks validate
Stack Management Commands
These commands interact with HCP Terraform to manage your Stacks remotely:
# Create a new Stack in HCP Terraform
terraform stacks create \
-organization-name my-org \
-project-name my-project \
-stack-name my-stack# List all Stacks in a project
terraform stacks list \
-organization-name my-org \
-project-name my-project
# Watch deployment progress in real-time
terraform stacks configuration watch \
-organization-name my-org \
-project-name my-project \
-stack-name my-stack
# Fetch latest configuration from VCS
terraform stacks configuration fetch \
-organization-name my-org \
-project-name my-project \
-stack-name my-stack
# Upload configuration manually (useful for non-VCS workflows)
terraform stacks configuration upload \
-organization-name my-org \
-project-name my-project \
-stack-name my-stack
Example output from watch
:
[Configuration Sequence Number: 3]
✓ Configuration: 'stc-XtaVrkPv3X29E8zi' [Completed] [27s]
✓ Deployment Group: 'us-central1_default' [Succeeded] [19s]
When to Use Each Command
During development:
terraform stacks init
– First command after writing/cloning Stack configterraform stacks validate
– Check for syntax errors before committing
For CI/CD pipelines:
terraform stacks configuration upload
– Deploy without VCS integrationterraform stacks configuration watch
– Monitor deployment progress
For debugging:
terraform stacks configuration list
– Review configuration historyterraform stacks configuration fetch
– Force a refresh from VCS
Pro Tips
Set environment variables to avoid repeating flags:
export TF_STACKS_ORGANIZATION_NAME="my-org"
export TF_STACKS_PROJECT_NAME="my-project"
export TF_STACKS_STACK_NAME="my-stack"# Now you can run without flags
terraform stacks configuration fetch
Next Steps
- Experiment with the example: Add a new deployment or component
- Try the CLI commands: Use
terraform stacks configuration upload
for faster iteration - Read the official docs: Terraform Stacks Documentation
Resources
Source Credit: https://medium.com/google-cloud/getting-started-with-terraform-stacks-on-google-cloud-c7d0ba80f718?source=rss—-e52cf94d98af—4