Skip to content
Terrateam

YAML Anchors for Configuration Reuse

YAML anchors enable DRY (Don’t Repeat Yourself) principles in your Terrateam configuration by defining reusable templates. This reduces configuration size, ensures consistency, and makes updates easier.

Use YAML anchors when you have:

  • Multiple workflows with similar configurations
  • Repeated engine settings across environments
  • Common workflow step sequences
  • Standardized access control patterns
  • Shared directory configurations

Define anchors in the definitions section and reference them throughout your configuration. Terrateam ignores the contents of definitions; every other key is validated against the configuration schema, so an anchored value must be valid where it is used.

definitions:
std_plan: &std_plan
- type: init
- type: plan
workflows:
- tag_query: ""
plan: *std_plan

Terrateam resolves anchors, aliases, and the << merge key when it parses the file:

  • *anchor inserts the anchored value (scalar, list, or map).
  • <<: *anchor merges the keys of an anchored map into the current map. Keys written in the current map win over merged keys.
  • <<: [*first, *second] merges several maps. Writing <<: twice in one map is a parse error.
  • Merging is shallow. Nested maps are replaced, not merged.
  • *anchor.key is not valid YAML. Anchor the nested value separately.
  • A list alias inside another list creates a nested list, which Terrateam rejects. Use the alias as the whole value or anchor the individual entries.

Define once, use everywhere:

definitions:
standard_engine: &standard_engine
name: terraform
version: "1.5.7"
workflows:
- tag_query: "dev"
engine: *standard_engine
- tag_query: "prod"
engine:
<<: *standard_engine
version: "1.5.5"

Anchor each step individually so they can be combined into a flat list:

definitions:
# Anchor individual steps, not the surrounding list
step_init: &step_init
type: init
step_fmt: &step_fmt
type: run
cmd: ["terraform", "fmt", "-check"]
step_validate: &step_validate
type: run
cmd: ["terraform", "validate"]
step_checkov: &step_checkov
type: checkov
step_tfsec: &step_tfsec
type: run
cmd: ["tfsec", "."]
workflows:
- tag_query: ""
plan:
- *step_init
- *step_fmt
- *step_validate
- type: plan
- *step_checkov
- *step_tfsec

If you want to reuse a complete step list, anchor it and reference it as the entire value (not as one entry in a larger list):

definitions:
full_plan: &full_plan
- type: init
- type: plan
- type: checkov
workflows:
- tag_query: ""
plan: *full_plan

Environment variables for a step are set with the step’s env key. Define the variables once per environment and reference them from the steps that need them:

definitions:
# AWS environment configurations
aws_dev: &aws_dev
AWS_REGION: us-east-1
AWS_ROLE_ARN: arn:aws:iam::123456789012:role/terrateam-dev
ENVIRONMENT: development
aws_staging: &aws_staging
AWS_REGION: us-east-1
AWS_ROLE_ARN: arn:aws:iam::123456789012:role/terrateam-staging
ENVIRONMENT: staging
aws_prod: &aws_prod
AWS_REGION: us-east-1
AWS_ROLE_ARN: arn:aws:iam::123456789012:role/terrateam-prod
ENVIRONMENT: production
workflows:
- tag_query: "dev"
plan:
- type: init
env: *aws_dev
- type: plan
env: *aws_dev
- tag_query: "staging"
plan:
- type: init
env: *aws_staging
- type: plan
env: *aws_staging
- tag_query: "production"
plan:
- type: init
env: *aws_prod
- type: plan
env: *aws_prod

Approval rules live in apply_requirements, not in workflows. Anchor them the same way:

definitions:
basic_requirements: &basic_requirements
approved:
enabled: true
any_of: ["team:developers"]
any_of_count: 1
status_checks:
enabled: true
strict_requirements: &strict_requirements
approved:
enabled: true
any_of: ["team:platform"]
any_of_count: 2
status_checks:
enabled: true
merge_conflicts:
enabled: true
apply_requirements:
checks:
- tag_query: "dev"
<<: *basic_requirements
- tag_query: "staging"
<<: *basic_requirements
- tag_query: "production"
<<: *strict_requirements

Define access patterns once:

definitions:
dev_team_access: &dev_team_access
plan: ["*"]
apply: ["team:developers", "team:platform"]
platform_only: &platform_only
plan: ["*"]
apply: ["team:platform"]
apply_force: ["team:sre"]
access_control:
enabled: true
policies:
- tag_query: "dev or staging"
<<: *dev_team_access
- tag_query: "production"
<<: *platform_only
- tag_query: "infrastructure"
<<: *platform_only

Anchor individual steps so you can compose them into different workflow lists without producing nested arrays. Steps that should run even after an earlier failure use run_on: always:

definitions:
# Base configuration for all workflows
base_engine: &base_engine
name: terraform
version: "1.5.7"
# Individual step anchors (compose these into flat lists)
step_init: &step_init
type: init
step_fmt: &step_fmt
type: run
cmd: ["terraform", "fmt", "-check"]
step_validate: &step_validate
type: run
cmd: ["terraform", "validate"]
step_plan: &step_plan
type: plan
step_checkov: &step_checkov
type: checkov
run_on: always
step_tfsec: &step_tfsec
type: run
cmd: ["tfsec", ".", "--format", "json"]
run_on: always
step_notify: &step_notify
type: run
cmd: ["echo", "Deployment complete"]
run_on: success
workflows:
- tag_query: "feature"
engine: *base_engine
plan:
- *step_init
- *step_fmt
- *step_validate
- *step_plan
- *step_checkov
- *step_tfsec
- tag_query: "main"
engine: *base_engine
plan:
- *step_init
- *step_fmt
- *step_validate
- *step_plan
- *step_checkov
- *step_tfsec
apply:
- type: init
- type: apply
- *step_notify

Share when_modified settings and tags across directories. dirs is a map keyed by directory path (globs are allowed):

definitions:
# Anchor individual tag values so they can be composed into flat lists
tag_aws: &tag_aws aws
tag_managed: &tag_managed managed
# Shared when_modified configuration
with_shared_modules: &with_shared_modules
file_patterns: ["${DIR}/*.tf", "shared/*.tf"]
autoplan: true
autoapply: false
# Module directories are never planned on their own
module_config: &module_config
file_patterns: []
dirs:
terraform/networking:
tags:
- *tag_aws
- *tag_managed
- "networking"
- "core"
when_modified: *with_shared_modules
terraform/compute:
tags:
- *tag_aws
- *tag_managed
- "compute"
- "application"
when_modified: *with_shared_modules
modules/**:
when_modified: *module_config

Combine several maps with one <<: key that takes a list. A key written after the merge overrides the merged value:

definitions:
base: &base
name: terraform
pinned: &pinned
version: "1.5.5"
no_outputs: &no_outputs
outputs:
collect: false
workflows:
- tag_query: "production"
engine:
<<: [*base, *pinned, *no_outputs]
version: "1.5.7"

Anchor each step individually and assemble per-environment lists from those steps. YAML cannot merge two lists into one, so the production list has to repeat the base entries, but each step is still defined exactly once.

definitions:
# Anchor individual steps
step_init: &step_init
type: init
step_plan: &step_plan
type: plan
step_checkov: &step_checkov
type: checkov
step_compliance: &step_compliance
type: run
cmd: ["compliance-check"]
# Development workflow - basic
dev_plan: &dev_plan
- *step_init
- *step_plan
# Production workflow - full compliance
prod_plan: &prod_plan
- *step_init
- *step_plan
- *step_checkov
- *step_compliance
workflows:
- tag_query: "dev"
plan: *dev_plan
- tag_query: "production"
plan: *prod_plan
  1. Name anchors descriptively - Use clear names that indicate purpose
  2. Group related anchors - Keep similar configurations together
  3. Document complex anchors - Add comments explaining usage
  4. Start simple - Begin with basic patterns and evolve as needed
  5. Test thoroughly - Run terrateam repo-config on a pull request to verify the resolved configuration before relying on it
definitions:
# Terraform version
tf_engine: &tf_engine
name: terraform
version: "1.5.7"
# Individual step anchors (compose into flat lists below)
step_init: &step_init
type: init
step_fmt: &step_fmt
type: run
cmd: ["terraform", "fmt", "-check"]
step_validate: &step_validate
type: run
cmd: ["terraform", "validate"]
step_plan: &step_plan
type: plan
step_checkov: &step_checkov
type: checkov
# Environment credentials
dev_env: &dev_env
AWS_ROLE_ARN: arn:aws:iam::111111111111:role/terrateam-dev
AWS_REGION: us-east-1
prod_env: &prod_env
AWS_ROLE_ARN: arn:aws:iam::222222222222:role/terrateam-prod
AWS_REGION: us-east-1
# Access patterns
dev_access: &dev_access
plan: ["*"]
apply: ["team:developers"]
prod_access: &prod_access
plan: ["*"]
apply: ["team:platform"]
apply_force: ["team:sre"]
# Approval rules
dev_checks: &dev_checks
approved:
enabled: true
any_of: ["team:developers"]
any_of_count: 1
prod_checks: &prod_checks
approved:
enabled: true
any_of: ["team:platform"]
any_of_count: 2
status_checks:
enabled: true
workflows:
- tag_query: "dev"
engine: *tf_engine
plan:
- *step_init
- *step_fmt
- *step_validate
- type: plan
env: *dev_env
- *step_checkov
apply:
- *step_init
- type: apply
env: *dev_env
- tag_query: "production"
engine: *tf_engine
plan:
- *step_init
- *step_fmt
- *step_validate
- type: plan
env: *prod_env
- *step_checkov
apply:
- *step_init
- type: apply
env: *prod_env
apply_requirements:
checks:
- tag_query: "dev"
<<: *dev_checks
- tag_query: "production"
<<: *prod_checks
access_control:
enabled: true
policies:
- tag_query: "dev"
<<: *dev_access
- tag_query: "production"
<<: *prod_access

This approach keeps every setting defined once while producing a configuration Terrateam accepts as written.