Skip to main content

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.

Experimental

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:

  1. File-Scoped: Locals are only available within the file where they are defined. They do not inherit across imports.
  2. Dependency Resolution: Locals can reference other locals using {{ .locals.name }} syntax. Atmos automatically determines the correct resolution order.
  3. Cycle Detection: Circular references are detected and reported with clear error messages.
  4. 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

# These locals are ONLY available in this file
locals:
default_region: us-east-1

stacks/deploy/production.yaml

import:
- catalog/defaults

# The locals from catalog/defaults are NOT available here
locals:
namespace: acme # Define your own locals

This design ensures predictabilityβ€”you can understand a file without tracing imports.

How does processing work?

Locals are resolved before imports are merged:

  1. Per-File Processing: Each file is processed independently
  2. Locals Resolution: Locals are resolved using only that file's context
  3. Import Merging: Sections (vars, settings, env, components) are merged
  4. 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:

SectionSyntaxDescription
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 }}"
Same-File Access Only

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:

FunctionDescriptionExample
!envEnvironment variables!env API_ENDPOINT
!execCommand execution!exec echo hello
!storeStore lookups!store secrets/db .password
!terraform.stateTerraform state queries!terraform.state vpc .vpc_id
!terraform.outputTerraform 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 Function Processing Order

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​

Aspectlocalsvars
ScopeFile-scoped onlyInherits across imports
PurposeTemporary values for DRYInput variables for components
OutputNot passed to componentsPassed to Terraform/Helmfile/Packer
Cross-File AccessCannot access imported valuesCan 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.

Conflicting Template Syntax

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​

  1. Extract repetition to locals instead of duplicating values.
  2. Build incrementallyβ€”compose complex values from simpler locals.
  3. Keep locals close to where they're used (component-level when possible).
  4. Use vars for cross-file sharingβ€”locals are file-scoped by design.

Try It​

Example: Locals

Reduce repetition and build computed values using file-scoped locals.

Learn more about Locals.

What You'll See

  • Basic locals: Define reusable values within a file
  • Dependency resolution: Locals can reference other locals
  • Context access: Locals can access settings, vars, and env from the same file
  • File-scoped isolation: Each stack file has independent locals

Try It

cd examples/locals

# View resolved locals for the dev stack
atmos describe locals -s dev

# View resolved locals for a specific component
atmos describe locals myapp -s dev

# Compare dev vs prod
atmos describe locals myapp -s prod

Key Files

FilePurpose
stacks/deploy/dev.yamlDevelopment stack with locals
stacks/deploy/prod.yamlProduction stack with locals
components/terraform/myapp/main.tfTerraform component