Terramate: Orchestrating Terraform at Scale with Stacks and Blueprints
Terramate adds a stack-and-blueprint orchestration layer above Terraform to eliminate copy-paste drift across accounts and regions. This post walks through a concrete VPC blueprint, generated HCL, and the run-time orchestration that keeps large fleets consistent.
16 Nov 2025, 22:06 UTC

The Problem: Terraform Sprawl Across Environments
As infrastructure grows, teams often copy Terraform modules across dozens of accounts, regions, and environments. Each copy drifts: naming conventions diverge, tags are inconsistent, and updating a shared pattern means editing every directory. Terraform workspaces help, but they don't solve code duplication or cross-stack dependencies. Terramate addresses this by adding a thin orchestration layer that keeps Terraform as the execution engine while managing the fleet.
Stacks and Blueprints: The Core Abstraction
A stack in Terramate is a directory that represents a single Terraform root module — one state file, one set of variables. Stacks live in a nested directory tree that mirrors your organizational structure (e.g., aws/prod/eu-west-1/network, aws/staging/us-east-1/app). A blueprint is a reusable template that generates Terraform HCL into multiple stacks. You define the pattern once — resource naming, common tags, provider configuration — and instantiate it wherever needed.
This separation means the blueprint owns the what (standard VPC layout, security group rules) while each stack owns the where (account ID, region, CIDR blocks). Changes to the blueprint propagate to all stacks on the next generate step.
Worked Example: Generating a Standard VPC Across Three Accounts
Assume a blueprint at blueprints/vpc/ containing a main.tf.tm.hcl template:
# blueprints/vpc/main.tf.tm.hcl
terraform {
backend "s3" {
bucket = "${global.terraform_bucket}"
key = "${terramate.stack.path}/terraform.tfstate"
region = "${global.terraform_region}"
dynamodb_table = "${global.terraform_lock_table}"
}
}
provider "aws" {
region = var.aws_region
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "${var.environment}-${var.workload}-vpc"
cidr = var.vpc_cidr
tags = merge(global.common_tags, {
Environment = var.environment
Workload = var.workload
})
}
Global values live in terramate.tm.hcl at the repo root:
# terramate.tm.hcl
globals {
terraform_bucket = "my-tf-state-bucket"
terraform_region = "us-east-1"
terraform_lock_table = "tf-locks"
common_tags = {
ManagedBy = "terramate"
Repository = "infra-platform"
}
}
Each stack declares its inputs in a stack.tm.hcl:
# aws/prod/eu-west-1/network/stack.tm.hcl
stack {
name = "prod-eu-network"
description = "Production VPC in EU West"
id = "prod-eu-network"
}
globals {
environment = "prod"
workload = "network"
aws_region = "eu-west-1"
vpc_cidr = "10.10.0.0/16"
}
Run terramate generate from the repository root (the directory containing terramate.tm.hcl). Terramate walks the directory tree, evaluates globals and stack-specific values, and writes a concrete main.tf into each stack directory. Generation needs only local filesystem access; applying the resulting Terraform requires cloud credentials configured for the provider. The generated file is valid Terraform HCL — no Terramate runtime required to apply.
Orchestrating Plans and Applies
With stacks generated, terramate run terraform plan executes terraform plan in every stack directory, streaming output with stack prefixes. You can filter: terramate run --changed -- terraform plan only touches stacks where generated files differ from the last commit. For CI/CD, terramate list --changed --output=json feeds a matrix to parallel jobs.
Dependencies between stacks (e.g., network before app) are expressed in stack.tm.hcl:
stack {
# ...
depends_on = ["../../network"]
}
Terramate topologically sorts the run order. Circular dependencies produce an error at generate time.
Trade-offs and Limitations
- Abstraction overhead: Teams must agree on directory layout, global schema, and blueprint contracts. Onboarding requires understanding both Terraform and Terramate templating.
- State backend still yours: Terramate does not manage remote state, locking, or encryption. You still need a robust S3 + DynamoDB (or equivalent) backend strategy.
- Generated files drift: If someone edits a generated main.tf directly, the next generate overwrites it. Enforce no manual edits via code review or pre-commit hooks.
- Debugging templates: Errors in .tm.hcl templates surface only after generation. Use terramate generate --dry-run to preview output before committing.
Getting Started Checklist
- Install Terramate CLI (brew install terramate or download binary).
- Run terramate init in your Terraform repo root.
- Create a blueprint directory with a .tm.hcl template.
- Define globals in terramate.tm.hcl.
- Add stack.tm.hcl to each existing Terraform directory (or create new stacks).
- Run terramate generate and inspect generated files before committing.
- Execute terramate run terraform plan to verify end-to-end behavior.
Start with one blueprint and a handful of stacks. Expand once the pattern proves stable.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.