Terraform infrastructure managed with Terramate for stack orchestration.
| Tool | Purpose |
|---|---|
| Terraform ≥ 1.0 | Apply infrastructure |
| Terramate | Stack orchestration and code generation |
| pre-commit | Git hooks |
| terraform-docs | Module README generation (used by the terraform_docs hook) |
Install Terramate:
brew install terramateInstall pre-commit if you haven't already:
pip install pre-commit# After cloning
pre-commit installThat's it. The hooks run automatically on every commit.
envs/
<env>/ # dev | staging | prod | logs
terramate.tm.hcl # env globals: environment, aws_account_id, regions, etc.
<service>/ # leaf stack — gets terraform apply'd directly
stack.tm.hcl # stack name, description, watch list
providers.tf # generated by Terramate — do not edit
terraform.tf
main.tf
variables.tf
outputs.tf
README.md
modules/
<name>/ # reusable module called via source = "../../../modules/<name>"
_generated_tags.tf # generated — tagging vars with null defaults
main.tf
variables.tf
outputs.tf
terraform.tf
README.md
scripts/
update-watches.sh # rewrites watch = [...] blocks; see scripts/README.md
envs/ contains root Terraform configurations — one per service per environment. Each is an independent state. Run terraform apply (or terramate run) from here.
modules/ contains reusable modules. They are never applied directly; they are called from stacks via source = "../../../modules/<name>".
Shared variables are defined as Terramate globals and code-generated into every stack as providers.tf (envs stacks) or _generated_tags.tf (modules). You never write these by hand.
| Global | Defined in | Value |
|---|---|---|
environment |
envs/<env>/terramate.tm.hcl |
"dev", "prod", etc. |
aws_account_id |
envs/<env>/terramate.tm.hcl |
AWS account number for the env |
aws_primary_region |
envs/<env>/terramate.tm.hcl |
Default region (e.g. "us-west-2") |
aws_regions |
envs/<env>/terramate.tm.hcl |
List of regions to create aliased providers for |
repo |
envs/terramate.tm.hcl |
"meshy-infra" |
managed_by |
envs/terramate.tm.hcl |
"terraform" |
service |
derived at generation time | stack directory name |
Each envs/ leaf stack gets four Terraform variables (var.env, var.service, var.repo, var.managed_by) with their defaults pre-populated, plus a local.default_tags map:
# Use in any resource inside an envs/ stack:
resource "aws_instance" "example" {
# ...
tags = local.default_tags
}
# Pass through when calling a module:
module "vpc" {
source = "../../../modules/vpc"
env = var.env
service = var.service
repo = var.repo
managed_by = var.managed_by
}-
Add the global to the appropriate level:
- Repo-wide default →
envs/terramate.tm.hcl - Per-env override →
envs/<env>/terramate.tm.hcl
Example adding
cost_centerrepo-wide:globals { repo = "meshy-infra" managed_by = "terraform" cost_center = "platform" # ← new }
- Repo-wide default →
-
Add the corresponding
variableblock and update thedefault_tagslocal inside thegenerate_file "providers.tf"block inenvs/<env>/terramate.tm.hcl(repeat for each env, or extract the pattern to a root-level generate block if all envs share it). -
Regenerate:
terramate generate
This rewrites every
providers.tfinenvs/and_generated_tags.tfinmodules/. Commit the result.
If Terramate is not installed locally, edit the
providers.tffiles manually to match whatterramate generatewould produce, following the pattern of the existing variables.
The watch = [...] field in stack.tm.hcl tells Terramate which paths outside a stack's own directory affect it. Without this, terramate run --changed won't detect that envs/dev/meshy is affected by a change to modules/vpc.
scripts/update-watches.sh keeps these in sync automatically (the update-terramate-watches pre-commit hook runs it on every .tf commit). After manually adding or removing a module block, you can also run it directly:
./scripts/update-watches.shcd envs/dev/meshy
terraform init
terraform plan
terraform applyterramate run -- terraform init
terramate run -- terraform planterramate run --changed -- terraform planTerramate computes the changed set using git diff against the default branch, plus the watch entries.
-
Add a
moduleblock in the stack'smain.tf:module "vpc" { source = "../../../modules/vpc" }
-
The
update-terramate-watchespre-commit hook will updatewatchinstack.tm.hclautomatically on commit. To update it immediately:./scripts/update-watches.sh
Each directory under envs/<env>/ is an independently applied unit of infrastructure — its own Terraform state, its own plan, its own apply. Adding a new one means adding a new directory. Here is the full sequence:
-
Create the directory and write
stack.tm.hcl. This two-line file is what registers the directory with Terramate. Without it,terramate generatewill not produce any files here andterramate runwill not include it:mkdir -p envs/<env>/<name>
# envs/<env>/<name>/stack.tm.hcl stack { name = "<name>" # should match the folder name description = "<one-line description>" }
-
Run
terramate generate. This producesproviders.tfandstate.tfin the new directory — you do not write these by hand:terramate generate
-
Write your
.tffiles (main.tf,variables.tf,outputs.tf, etc.). -
If your
.tffiles call any shared modules, run the watch script so Terramate knows to re-plan this directory when those modules change:./scripts/update-watches.sh
-
Initialize and plan:
cd envs/<env>/<name> terraform init terraform plan
Adding a brand-new environment (not
dev,staging,prod, orlogs): you will also need to createenvs/<env>/terramate.tm.hclwith the AWS account ID and region list before runningterramate generate. Copyenvs/dev/terramate.tm.hclas a starting point.
| Hook | What it does |
|---|---|
terraform_fmt |
Formats all .tf files |
terraform_docs |
Injects input/output tables into README.md for any changed module |
update-terramate-watches |
Rewrites watch = [...] in stack.tm.hcl for any stack whose .tf files changed |
Run all hooks manually against every file:
pre-commit run --all-files| File | Owner | How to update |
|---|---|---|
providers.tf (in envs/) |
Terramate | Edit globals in envs/<env>/terramate.tm.hcl or envs/terramate.tm.hcl, then run terramate generate |
_generated_tags.tf (in modules/) |
Terramate | Edit globals in modules/terramate.tm.hcl, then run terramate generate |
watch = [...] in stack.tm.hcl |
update-watches.sh |
Add/remove module blocks in .tf files and commit (hook runs automatically) |
README.md in modules/ (between the <!-- BEGIN/END_TF_DOCS --> markers) |
terraform_docs hook |
Update variables.tf / outputs.tf and commit |