File-Scoped Locals: Simplify Stack Configuration with Temporary Variables
We're introducing file-scoped locals to Atmos stack configurations. Inspired by Terraform and Terragrunt, locals let you define temporary variables within a single file, reducing repetition and making your configurations more readable and maintainable.
The Problem: Repetition in Stack Configurations
Complex stack configurations often contain repeated values. You might have a naming convention that combines namespace, environment, and stage across multiple components:
# Before: Repetitive and error-prone
components:
terraform:
vpc:
vars:
name: acme-prod-us-east-1-vpc
tags:
Environment: prod
Namespace: acme
eks:
vars:
cluster_name: acme-prod-us-east-1-eks
tags:
Environment: prod
Namespace: acme
rds:
vars:
identifier: acme-prod-us-east-1-rds
tags:
Environment: prod
Namespace: acme
This approach has several problems:
- Repetition - Same values copied everywhere
- Inconsistency risk - Easy to mistype or forget to update all occurrences
- Hard to refactor - Changing a naming convention requires updates in many places
The Solution: File-Scoped Locals
Locals let you define variables once and reference them throughout the file:
# After: Clean and DRY
locals:
namespace: acme
environment: prod
stage: us-east-1
name_prefix: "{{ .locals.namespace }}-{{ .locals.environment }}-{{ .locals.stage }}"
tags:
Environment: "{{ .locals.environment }}"
Namespace: "{{ .locals.namespace }}"
components:
terraform:
vpc:
vars:
name: "{{ .locals.name_prefix }}-vpc"
tags: "{{ .locals.tags }}"
eks:
vars:
cluster_name: "{{ .locals.name_prefix }}-eks"
tags: "{{ .locals.tags }}"
rds:
vars:
identifier: "{{ .locals.name_prefix }}-rds"
tags: "{{ .locals.tags }}"
Key Features
Locals Can Reference Other Locals
Locals are resolved in dependency order using topological sorting. You can build complex values from simpler ones:
locals:
namespace: acme
environment: prod
stage: us-east-1
# References other locals - resolved in correct order
name_prefix: "{{ .locals.namespace }}-{{ .locals.environment }}"
full_name: "{{ .locals.name_prefix }}-{{ .locals.stage }}"
Circular Dependency Detection
Atmos automatically detects circular dependencies and provides clear error messages:
# This will error with a clear message
locals:
a: "{{ .locals.b }}"
b: "{{ .locals.c }}"
c: "{{ .locals.a }}" # Circular!
File-Scoped Isolation
Unlike vars, locals do not inherit across file boundaries via import. This is intentional:
# mixins/region.yaml
locals:
region_prefix: "us-west-2" # Only available in this file
vars:
region: us-west-2 # Inherited by importing files
# stacks/prod.yaml
import:
- mixins/region
# The 'region_prefix' local is NOT available here
# Only 'vars.region' is inherited
locals:
my_prefix: "prod" # This file's own locals
This keeps locals truly local, preventing unexpected interactions between files.
Multi-Level Scopes
Locals can be defined at three levels, each inheriting from its parent:
- Global (stack file root) - Available throughout the file
- Component-type (
terraform,helmfile,packersections) - Inherits from global - Component (individual component) - Inherits from component-type
# Global locals
locals:
namespace: acme
environment: prod
terraform:
# Terraform-scope locals (inherit from global)
locals:
backend_bucket: "{{ .locals.namespace }}-{{ .locals.environment }}-tfstate"
components:
terraform:
vpc:
# Component-level locals (inherit from terraform scope)
locals:
component_name: vpc
full_name: "{{ .locals.namespace }}-{{ .locals.component_name }}"
vars:
name: "{{ .locals.full_name }}"
Inspecting Locals with atmos describe locals
To see the resolved locals for any component, use the new describe locals command:
atmos describe locals vpc -s prod-ue2
Provenance Tracking
Add --provenance to see exactly where each local was defined:
atmos describe locals vpc -s prod-ue2 --provenance
JSON Output for Automation
For scripting and automation, use JSON format:
atmos describe locals vpc -s prod-ue2 --format json
{
"locals": {
"namespace": "acme",
"environment": "prod",
"name_prefix": "acme-prod"
},
"metadata": {
"component": "vpc",
"stack": "prod-ue2",
"component_type": "terraform"
}
}
Why File-Scoped?
You might wonder why locals don't inherit across imports like vars do. The design is intentional:
- Predictability - You know exactly what locals are available by looking at the current file
- No hidden dependencies - Locals won't mysteriously change based on import order
- Safer refactoring - Renaming a local in one file won't break other files
- Clear separation - Use
varsfor values that should propagate; uselocalsfor file-internal convenience
Best Practices
Use locals for DRY configuration within a file
locals:
common_tags:
Team: platform
CostCenter: infrastructure
components:
terraform:
vpc:
vars:
tags: "{{ .locals.common_tags }}"
eks:
vars:
tags: "{{ .locals.common_tags }}"
Build complex values from simple ones
locals:
namespace: acme
environment: prod
region: us-east-1
# Compose complex values
resource_prefix: "{{ .locals.namespace }}-{{ .locals.environment }}-{{ .locals.region }}"
s3_bucket: "{{ .locals.resource_prefix }}-artifacts"
dynamodb_table: "{{ .locals.resource_prefix }}-state-lock"
Keep locals close to their usage
Define locals at the appropriate scope level - don't put everything at the global level:
# Global - used everywhere
locals:
namespace: acme
terraform:
# Terraform-specific - only used by terraform components
locals:
state_bucket: "{{ .locals.namespace }}-tfstate"
components:
terraform:
vpc:
# Component-specific - only used by this component
locals:
vpc_name: "{{ .locals.namespace }}-vpc"
Get Started
File-scoped locals are available now. Try them in your stack configurations:
locals:
project: myproject
env: dev
vars:
name: "{{ .locals.project }}-{{ .locals.env }}"
Related Features
- Stack Templates - Go templating in stack manifests
- Configuration Provenance - Track where values come from
- YAML Functions - Dynamic configuration with
!terraform.output,!env, etc.
We'd love to hear how you're using locals in your configurations. Share your patterns in GitHub Discussions or open an issue if you encounter any problems.
