Configure Locals
The locals section defines file-scoped temporary variables for use within templates. Unlike vars, settings, and env, locals do not inherit across file boundariesβthey are resolved within a single file and can reference each other with automatic dependency resolution.
Use Casesβ
- Reduce Repetition: Define common values once and reference them throughout the file.
- Build Complex Values: Construct naming conventions, tags, or resource identifiers from simpler components.
- Improve Readability: Give meaningful names to computed values instead of repeating expressions.
- Template Composition: Build values incrementally by referencing other locals.
How Locals Workβ
Locals are similar to Terraform locals and Terragrunt locals:
- File-Scoped: Locals are only available within the file where they are defined. They do not inherit across imports.
- Dependency Resolution: Locals can reference other locals using
{{ .locals.name }}syntax. Atmos automatically determines the correct resolution order. - Cycle Detection: Circular references are detected and reported with clear error messages.
- Template Support: Locals support Go templates with Sprig functions.
Configuration Scopesβ
Locals can be defined at three levels within a file, each inheriting from its parent:
Global locals β Component-type locals (terraform/helmfile/packer) β Component-level locals
When the same key exists at multiple levels, the most specific scope wins.
locals:
namespace: acme
environment: prod
name_prefix: "{{ .locals.namespace }}-{{ .locals.environment }}"
terraform:
locals:
# Inherits from global, adds terraform-specific locals
backend_bucket: "{{ .locals.namespace }}-{{ .locals.environment }}-tfstate"
components:
terraform:
vpc:
locals:
# Component-specific locals (inherits from global + terraform)
vpc_type: production
vars:
name: "{{ .locals.name_prefix }}-{{ .locals.vpc_type }}-vpc"
bucket: "{{ .locals.backend_bucket }}"
File-Scoped Isolationβ
Unlike vars, settings, and env, locals do not inherit across file imports. Each file has its own isolated locals scope.
stacks/catalog/defaults.yaml
stacks/deploy/production.yaml
This design ensures predictabilityβyou can understand a file without tracing imports.
How does processing work?
Locals are resolved before imports are merged:
- Per-File Processing: Each file is processed independently
- Locals Resolution: Locals are resolved using only that file's context
- Import Merging: Sections (
vars,settings,env,components) are merged - Final Template Processing: Templates in other sections use the merged context
Dependency Resolutionβ
Locals can reference other locals in any orderβAtmos resolves them automatically:
locals:
full_name: "{{ .locals.name_prefix }}-{{ .locals.component }}" # Resolved last
name_prefix: "{{ .locals.namespace }}-{{ .locals.environment }}" # Resolved second
namespace: acme # Resolved first
environment: prod # Resolved first
component: vpc # Resolved first
Circular references are detected and reported with clear error messages showing the dependency cycle.
Accessing Other Sectionsβ
Locals can access settings, vars, and env defined in the same file:
| Section | Syntax | Description |
|---|---|---|
locals | {{ .locals.name }} | Other locals in the same file |
settings | {{ .settings.key }} | Settings defined in the same file |
vars | {{ .vars.key }} | Variables defined in the same file |
env | {{ .env.KEY }} | Environment variables defined in the same file |
settings:
version: v1
vars:
stage: dev
locals:
namespace: acme
label: "{{ .locals.namespace }}-{{ .vars.stage }}-{{ .settings.version }}"
Locals cannot access settings, vars, or env from imported files. If you need values from imports, use vars insteadβthey inherit across files.
# β Won't work - imported settings not available to locals
import:
- catalog/defaults # Has settings.region = us-east-1
locals:
region: "{{ .settings.region }}" # Error!
# β
Use vars instead - they inherit across imports
vars:
computed_name: "{{ .vars.region }}-cluster"
Using Templatesβ
Locals support full Go template syntax with Sprig functions:
locals:
name: myapp
environment: production
upper_name: "{{ .locals.name | upper }}"
log_level: '{{ if eq .locals.environment "production" }}warn{{ else }}debug{{ end }}'
YAML Functions in Localsβ
Locals support all Atmos YAML functions, enabling dynamic value resolution from external sources:
| Function | Description | Example |
|---|---|---|
!env | Environment variables | !env API_ENDPOINT |
!exec | Command execution | !exec echo hello |
!store | Store lookups | !store secrets/db .password |
!terraform.state | Terraform state queries | !terraform.state vpc .vpc_id |
!terraform.output | Terraform outputs | !terraform.output vpc .vpc_id |
Example: Environment Variablesβ
Use !env to inject environment-specific values into locals:
locals:
# Fetch from environment variables
api_endpoint: !env API_ENDPOINT
db_host: !env DATABASE_HOST
# Combine with Go templates
api_url: "https://{{ .locals.api_endpoint }}/api/v1"
connection_string: "postgresql://app@{{ .locals.db_host }}:5432/mydb"
components:
terraform:
backend:
vars:
api_url: "{{ .locals.api_url }}"
database_url: "{{ .locals.connection_string }}"
Example: Cross-Component Referencesβ
Use !terraform.state or !terraform.output to reference outputs from other components:
locals:
# Fetch VPC outputs from another component's state
vpc_id: !terraform.state vpc .vpc_id
private_subnets: !terraform.state vpc .private_subnet_ids
# Build derived values
cluster_name: "eks-{{ .locals.vpc_id }}"
components:
terraform:
eks:
vars:
vpc_id: "{{ .locals.vpc_id }}"
subnet_ids: "{{ .locals.private_subnets }}"
name: "{{ .locals.cluster_name }}"
Example: Secret Managementβ
Use !store to fetch secrets from your configured store:
locals:
# Fetch secrets from store
db_password: !store secrets/database .password
api_key: !store secrets/api .key
# Build connection strings
database_url: "postgresql://app:{{ .locals.db_password }}@db.example.com/mydb"
components:
terraform:
app:
vars:
database_url: "{{ .locals.database_url }}"
api_key: "{{ .locals.api_key }}"
Example: Dynamic Values with Execβ
Use !exec to run commands and capture output:
locals:
# Get current git commit
git_commit: !exec git rev-parse --short HEAD
# Get current timestamp
build_time: !exec date -u +%Y%m%d%H%M%S
# Build version string
version: "{{ .locals.git_commit }}-{{ .locals.build_time }}"
components:
terraform:
app:
vars:
image_tag: "{{ .locals.version }}"
Combining YAML Functions with Templatesβ
YAML functions are resolved first, then Go templates are processed. This allows you to build complex values:
locals:
# Step 1: YAML function fetches the base value
endpoint: !env GRAFANA_ENDPOINT
# Step 2: Go template uses the resolved value
dashboard_url: "https://{{ .locals.endpoint }}/d/ray-workers"
metrics_url: "https://{{ .locals.endpoint }}/api/v1/query"
vars:
monitoring:
dashboard: "{{ .locals.dashboard_url }}"
metrics: "{{ .locals.metrics_url }}"
YAML functions in locals are processed during stack configuration loading. The resolved values become available to other locals and component vars through Go templates.
Complex Valuesβ
Locals can contain maps and lists:
locals:
namespace: acme
environment: prod
default_tags:
Namespace: "{{ .locals.namespace }}"
Environment: "{{ .locals.environment }}"
ManagedBy: Atmos
availability_zones:
- us-east-1a
- us-east-1b
vars:
tags: "{{ .locals.default_tags }}"
Component-Level Localsβ
Components can define their own locals that inherit from global and section-level locals. They also support inheritance from base components via metadata.inherits:
components:
terraform:
vpc/base:
metadata:
type: abstract
locals:
vpc_type: standard
enable_nat: false
vpc/production:
metadata:
inherits:
- vpc/base
locals:
vpc_type: production # Overrides base
enable_nat: true # Overrides base
vars:
name: "{{ .locals.vpc_type }}-vpc"
nat_enabled: "{{ .locals.enable_nat }}"
Resolution order (later overrides earlier): Global β Section β Base Component β Component
Debuggingβ
Use atmos describe locals to inspect resolved values:
atmos describe locals -s dev # All locals for a stack
atmos describe locals vpc -s dev # Locals for a specific component
atmos describe locals -s dev --format json # Output as JSON
Example output
locals:
namespace: acme
environment: dev
name_prefix: acme-dev
terraform:
locals:
backend_bucket: acme-dev-tfstate
Locals vs Varsβ
| Aspect | locals | vars |
|---|---|---|
| Scope | File-scoped only | Inherits across imports |
| Purpose | Temporary values for DRY | Input variables for components |
| Output | Not passed to components | Passed to Terraform/Helmfile/Packer |
| Cross-File Access | Cannot access imported values | Can access merged values from all imports |
Use locals for intermediate computations within a single file, and vars for values that need to be passed to components or shared across files.
Template Processingβ
When a file defines locals, template processing is automatically enabled. Any {{ ... }} syntax will be processed.
If your YAML files contain Helm templates or other {{ }} syntax, use skip_templates_processing:
import:
- path: catalog/helm-values
skip_templates_processing: true
Error Handlingβ
Unresolved template references produce clear error messages:
locals:
domain: "{{ .settings.region }}.example.com" # Error if no settings defined
Error: map has no entry for key "settings"
For optional values, use Go template conditionals:
locals:
safe_region: '{{ with .settings }}{{ .region }}{{ else }}us-west-2{{ end }}'
Best Practicesβ
- Extract repetition to locals instead of duplicating values.
- Build incrementallyβcompose complex values from simpler locals.
- Keep locals close to where they're used (component-level when possible).
- Use vars for cross-file sharingβlocals are file-scoped by design.