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β
The locals section can be defined at multiple levels within a single file. Each scope inherits from its parent scope within that file.
Global Levelβ
Locals defined at the root level are available to all sections in the file:
# stacks/orgs/acme/plat/prod/us-east-1.yaml
locals:
namespace: acme
environment: prod
stage: us-east-1
name_prefix: "{{ .locals.namespace }}-{{ .locals.environment }}"
vars:
cluster_name: "{{ .locals.name_prefix }}-eks"
Component-Type Levelβ
Locals defined under terraform, helmfile, or packer inherit from global locals and are available to all components of that type:
# stacks/orgs/acme/plat/prod/us-east-1.yaml
locals:
namespace: acme
environment: prod
terraform:
locals:
# Inherits namespace and environment from global
backend_bucket: "{{ .locals.namespace }}-{{ .locals.environment }}-tfstate"
backend_key_prefix: "{{ .locals.environment }}"
backend_type: s3
backend:
s3:
bucket: "{{ .locals.backend_bucket }}"
key: "{{ .locals.backend_key_prefix }}/terraform.tfstate"
Component Levelβ
Locals defined within a component inherit from the component-type scope:
# stacks/orgs/acme/plat/prod/us-east-1.yaml
locals:
namespace: acme
environment: prod
name_prefix: "{{ .locals.namespace }}-{{ .locals.environment }}"
components:
terraform:
vpc:
locals:
# Inherits name_prefix from global
vpc_name: "{{ .locals.name_prefix }}-vpc"
cidr_prefix: "10.0"
vars:
vpc_name: "{{ .locals.vpc_name }}"
vpc_cidr: "{{ .locals.cidr_prefix }}.0.0/16"
eks:
locals:
# Each component has its own locals scope
cluster_name: "{{ .locals.name_prefix }}-eks"
vars:
cluster_name: "{{ .locals.cluster_name }}"
Scope Inheritance (Within a File)β
Within a single file, locals follow this inheritance chain:
Global locals
β
Component-type locals (terraform/helmfile/packer)
β
Component locals
Each level can:
- Access locals from parent scopes
- Define new locals
- Override parent locals with new values
Exampleβ
locals:
env: prod # Global
terraform:
locals:
env: production # Overrides global for terraform components
tf_version: "1.5"
components:
terraform:
vpc:
locals:
component: vpc
# Can access: env (= "production"), tf_version (= "1.5")
full_name: "{{ .locals.env }}-{{ .locals.component }}"
File-Scoped Isolationβ
Important: 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/orgs/acme/plat/prod/us-east-1.yaml
This design is intentional:
- Predictability: Locals in a file only come from that file
- No Hidden Dependencies: You can understand a file without tracing imports
- Flexibility: Each file can define locals that make sense for its context
Dependency Resolutionβ
Locals can reference other locals, and Atmos automatically resolves them in the correct order using topological sorting:
locals:
# These can be defined in any order
full_name: "{{ .locals.name_prefix }}-{{ .locals.component }}"
name_prefix: "{{ .locals.namespace }}-{{ .locals.environment }}"
namespace: acme
environment: prod
component: vpc
Atmos resolves these in dependency order:
namespaceandenvironment(no dependencies)component(no dependencies)name_prefix(depends onnamespace,environment)full_name(depends onname_prefix,component)
Circular Dependency Detectionβ
Atmos detects circular references and provides clear error messages:
locals:
a: "{{ .locals.b }}"
b: "{{ .locals.c }}"
c: "{{ .locals.a }}" # Creates a cycle!
Error output:
circular dependency in locals at stacks/example.yaml
Dependency cycle detected:
a β b β c β a
Using Templates in Localsβ
Locals support full Go template syntax with Sprig functions:
locals:
name: myapp
environment: production
# String manipulation
upper_name: "{{ .locals.name | upper }}"
quoted_env: '{{ .locals.environment | quote }}'
# Conditionals
log_level: '{{ if eq .locals.environment "production" }}warn{{ else }}debug{{ end }}'
# Complex expressions
resource_name: "{{ .locals.name }}-{{ .locals.environment | lower | trunc 4 }}"
Complex Valuesβ
Locals can contain maps and lists, not just strings:
locals:
namespace: acme
environment: prod
# Map value
default_tags:
Namespace: "{{ .locals.namespace }}"
Environment: "{{ .locals.environment }}"
ManagedBy: Atmos
# List value
availability_zones:
- us-east-1a
- us-east-1b
- us-east-1c
# Nested structure
backend_config:
bucket: "{{ .locals.namespace }}-tfstate"
region: us-east-1
encrypt: true
vars:
tags: "{{ .locals.default_tags }}"
azs: "{{ .locals.availability_zones }}"
Complete Exampleβ
stacks/orgs/acme/plat/prod/us-east-1.yaml
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 |
| Dependencies | Can reference other locals | Can reference locals |
| Visibility | Internal to stack config | Visible in component execution |
Use locals for intermediate computations and vars for values that need to be passed to your components.
Best Practicesβ
-
Use for Repetition: If you find yourself repeating the same value or expression, extract it to a local.
-
Build Incrementally: Start with simple locals and compose them into more complex values:
locals:
namespace: acme
env: prod
prefix: "{{ .locals.namespace }}-{{ .locals.env }}" # Build on simpler locals
bucket: "{{ .locals.prefix }}-assets" # Build on prefix -
Keep Locals Close: Define locals near where they're used. If a local is only used in one component, define it at the component level.
-
Use Descriptive Names: Choose names that describe what the value represents, not how it's computed.
-
Avoid Deep Nesting: If you have many levels of local dependencies, consider simplifying or restructuring.
-
Remember File Scope: Don't expect locals from imported filesβdefine what you need in each file.