Skip to main content

YAML Configuration Reference

YAML is the configuration language for Atmos stacks. Understanding YAML's features, gotchas, and best practices is essential for writing maintainable stack configurations at scale.

This guide covers everything you need to know about YAML in Atmos, from anchors and references to avoiding common pitfalls when mixing YAML with Go templates.

YAML is a Superset of JSON

Any valid JSON is also valid YAML. This means you can embed JSON objects anywhere in your YAML configuration:

stacks/prod.yaml

vars:
# YAML-style map
tags:
Environment: production
Team: platform

# JSON-style map (also valid!)
metadata: {"created": "2024-01-01", "version": "1.0"}

components:
terraform:
vpc:
# Mixing YAML and JSON
vars:
cidr_block: "10.0.0.0/16"
tags: {"Name": "prod-vpc", "ManagedBy": "atmos"}

When to use JSON in YAML:

  • ✅ Compact, single-line objects
  • ✅ Generated configuration from scripts
  • ✅ Copying from JSON sources

When to use YAML syntax:

  • ✅ Multi-line configuration (more readable)
  • ✅ Configuration that needs comments
  • ✅ Nested structures (cleaner indentation)

Dot Notation for Nested Keys

When you only need to set a single value deep in a nested structure, you can use dot notation instead of creating the full hierarchy. This makes your configuration more concise and readable.

Traditional Nesting vs Dot Notation

Traditional approach (verbose):

stacks/prod.yaml

components:
terraform:
vpc:
metadata:
component: vpc-base
inherits:
- vpc-defaults
settings:
spacelift:
workspace_enabled: true
autodeploy: false
validation:
check_cloudformation: true

Dot notation (concise):

stacks/prod.yaml

components:
terraform:
vpc:
metadata.component: vpc-base
metadata.inherits:
- vpc-defaults
settings.spacelift.workspace_enabled: true
settings.spacelift.autodeploy: false
settings.validation.check_cloudformation: true

Both configurations produce the exact same result. Dot notation is purely a syntax convenience for writing cleaner YAML.

Mixing Dot Notation and Traditional Nesting

You can mix both approaches in the same configuration. Use dot notation for single values, traditional nesting for groups:

stacks/prod.yaml

components:
terraform:
vpc:
# Dot notation for single deep values
metadata.component: vpc-base
settings.spacelift.workspace_enabled: true

# Traditional nesting for related groups
vars:
cidr_block: "10.0.0.0/16"
enable_dns_hostnames: true
enable_dns_support: true
availability_zones:
- us-east-1a
- us-east-1b
- us-east-1c

# Dot notation again for another single value
settings.validation.check_cloudformation: true

When to Use Dot Notation

✅ Use dot notation when:

  • Setting a single value deep in the tree
  • You want more concise, readable configuration
  • The nested path is clear and self-documenting

❌ Use traditional nesting when:

  • Setting multiple related values in the same section
  • You want to emphasize the structure
  • You're defining complex nested objects with many fields

Dot Notation Works Everywhere

Dot notation isn't limited to component configuration—it works in any YAML section:

stacks/globals.yaml

# Top-level vars with dot notation
vars.tags.ManagedBy: Atmos
vars.tags.Environment: production

# Settings with dot notation
settings.spacelift.workspace_enabled: true
settings.github.default_branch: main

# Environment variables
env.TF_LOG: DEBUG
env.AWS_DEFAULT_REGION: us-east-1

This is equivalent to:

stacks/globals.yaml

vars:
tags:
ManagedBy: Atmos
Environment: production

settings:
spacelift:
workspace_enabled: true
github:
default_branch: main

env:
TF_LOG: DEBUG
AWS_DEFAULT_REGION: us-east-1
Best Practice

Use dot notation when it improves readability. If you have 2+ values in the same section, traditional nesting is often clearer.

Anchors and References (Aliases)

YAML anchors (&) and references (*) let you reuse configuration within a single file. This is YAML's built-in DRY mechanism.

Basic Anchor and Reference

stacks/prod.yaml

# Define an anchor with &
vars:
common_tags: &common_tags
ManagedBy: Atmos
Environment: production
Team: Platform

components:
terraform:
vpc:
vars:
# Reference the anchor with *
tags: *common_tags

eks:
vars:
# Same tags reused
tags: *common_tags

Result after YAML processing:

components:
terraform:
vpc:
vars:
tags:
ManagedBy: Atmos
Environment: production
Team: Platform
eks:
vars:
tags:
ManagedBy: Atmos
Environment: production
Team: Platform

Merging with << (Merge Key)

The merge key << combines anchors with additional keys:

stacks/prod.yaml

vars:
base_tags: &base_tags
ManagedBy: Atmos
Environment: production

components:
terraform:
vpc:
vars:
tags:
<<: *base_tags # Merge base_tags
Name: prod-vpc # Add additional keys
Type: network

rds:
vars:
tags:
<<: *base_tags
Name: prod-database
Type: database

Result:

components:
terraform:
vpc:
vars:
tags:
ManagedBy: Atmos
Environment: production
Name: prod-vpc
Type: network

Real-World Example: Component Defaults

stacks/prod-us-east-1.yaml

# Define VPC defaults as an anchor
vars: &vpc_defaults
enable_dns_hostnames: true
enable_dns_support: true
enable_nat_gateway: true
single_nat_gateway: false
create_igw: true
map_public_ip_on_launch: false

components:
terraform:
vpc-prod:
metadata:
component: vpc
vars:
<<: *vpc_defaults # Reuse VPC defaults
cidr_block: "10.0.0.0/16"
availability_zones: ["us-east-1a", "us-east-1b", "us-east-1c"]
single_nat_gateway: true # Override for prod
tip

For cross-file reuse, use Atmos imports and inheritance instead of YAML anchors.

Multiple Anchors with Merge

You can merge multiple anchors:

stacks/prod.yaml

vars:
security_tags: &security_tags
Compliance: SOC2
DataClassification: sensitive

cost_tags: &cost_tags
CostCenter: engineering
Project: infrastructure

components:
terraform:
rds:
vars:
tags:
<<: [*security_tags, *cost_tags] # Merge both
Name: prod-database

Result:

tags:
Compliance: SOC2
DataClassification: sensitive
CostCenter: engineering
Project: infrastructure
Name: prod-database
Anchor Scope

Anchors are file-scoped only. They don't work across imports. Use Atmos imports and inheritance for cross-file reuse.

Type Coercion Gotchas

YAML automatically converts values based on their appearance. This causes unexpected behavior if you're not careful.

The NO Problem (Country Codes)

stacks/countries.yaml (WRONG)

vars:
allowed_countries:
- US
- GB
- NO # ❌ Treated as boolean false!
- SE
- DK

What YAML sees:

vars:
allowed_countries:
- US
- GB
- false # NO became false!
- SE
- DK

Fix: Quote it

stacks/countries.yaml (CORRECT)

vars:
allowed_countries:
- US
- GB
- "NO" # ✅ String
- SE
- DK

Boolean Conversion Table

YAML interprets these as booleans:

StringBecomes
yes, Yes, YEStrue
no, No, NOfalse
true, True, TRUEtrue
false, False, FALSEfalse
on, On, ONtrue
off, Off, OFFfalse

Always quote these if you need them as strings:

stacks/config.yaml

vars:
# WRONG - Becomes boolean
confirmation: yes # → true

# CORRECT - Stays string
confirmation: "yes" # → "yes"

# Country codes that need quoting
countries: ["NO", "ON", "OFF", "YES"]

Numeric Type Coercion

stacks/config.yaml

vars:
# These become numbers, not strings
version: 1.20 # → 1.2 (float, trailing zero lost!)
zip_code: 07094 # → 7094 (integer, leading zero lost!)
port: 8080 # → 8080 (integer)

# Quote to preserve as strings
version: "1.20" # → "1.20"
zip_code: "07094" # → "07094"
port: "8080" # → "8080"

Octal Number Trap

stacks/config.yaml

vars:
# Leading zero = octal in YAML 1.1!
file_mode: 0644 # → 420 in decimal (octal conversion!)

# Quote to preserve
file_mode: "0644" # → "0644"

Quoting Best Practices

When You MUST Quote

1. Strings that look like other types:

vars:
country: "NO" # Looks like boolean
version: "1.20" # Looks like number
value: "true" # Looks like boolean
code: "07094" # Leading zero

2. Strings with special characters:

vars:
url: "https://example.com" # Contains :
path: "/var/log/app.log" # Fine, but quote for safety
regex: "^[a-z]+$" # Contains special chars
template: "{{ .vars.name }}" # Contains braces

3. Multi-line strings (see next section)

Consistent Quoting Strategy

Recommended: Quote all string values for consistency

stacks/prod.yaml (Consistent Style)

vars:
environment: "production" # Quoted
region: "us-east-1" # Quoted
namespace: "myapp" # Quoted
cidr_block: "10.0.0.0/16" # Quoted
enable_vpc: true # Boolean (no quotes)
instance_count: 3 # Number (no quotes)

Benefits:

  • ✅ No mental overhead deciding when to quote
  • ✅ Prevents type coercion surprises
  • ✅ Easier to read and maintain
  • ✅ Tools like prettier/yamllint can enforce it

Multi-Line Strings

YAML has multiple ways to handle multi-line strings. Choose the right one for your use case.

Literal Block Scalar (|)

Preserves line breaks and trailing newline:

stacks/config.yaml

vars:
script: |
#!/bin/bash
set -e
echo "Starting deployment"
terraform apply -auto-approve

Result:

#!/bin/bash\nset -e\necho "Starting deployment"\nterraform apply -auto-approve\n

Folded Block Scalar (>)

Folds lines into a single line, preserves final newline:

stacks/config.yaml

vars:
description: >
This is a very long description that spans
multiple lines but will be folded into a
single line when processed.

Result:

This is a very long description that spans multiple lines but will be folded into a single line when processed.\n

Block Chomping (Control Trailing Newlines)

| or > alone: Keep final newline

|- or >-: Strip final newline (clip)

|+ or >+: Keep all trailing newlines (keep)

stacks/config.yaml

vars:
# Default: Keep final newline
script_keep: |
echo "hello"
echo "world"

# Strip trailing newline
script_strip: |-
echo "hello"
echo "world"

# Keep all trailing newlines
script_keep_all: |+
echo "hello"
echo "world"

Real-World Example: User Data Script

stacks/prod.yaml

components:
terraform:
ec2:
vars:
user_data: |
#!/bin/bash
set -euo pipefail

# Install dependencies
apt-get update
apt-get install -y docker.io

# Start application
docker run -d -p 80:80 myapp:latest

Go Templates in YAML (Gotchas!)

Atmos uses Go templates for dynamic configuration. Go template braces conflict with YAML flow-style maps.

The Brace Problem

stacks/config.yaml (WRONG)

vars:
# ❌ YAML sees this as incomplete flow-style map!
bucket_name: {{ .vars.namespace }}-{{ .vars.environment }}-bucket

Error:

Error parsing YAML: mapping values are not allowed in this context

Solution: Quote Template Strings

stacks/config.yaml (CORRECT)

vars:
# ✅ Quote any string containing Go templates
bucket_name: "{{ .vars.namespace }}-{{ .vars.environment }}-bucket"

# ✅ Also works for complex expressions
cluster_name: '{{ printf "%s-%s-eks" .vars.namespace .vars.region }}'

# ✅ Multi-line templates need literal block with quotes
complex_value: |
{{ range .vars.availability_zones }}
- {{ . }}
{{ end }}

Template Delimiters and YAML Conflicts

Problem: YAML flow-style syntax looks like templates:

stacks/config.yaml

vars:
# This is a YAML flow-style map (not a template!)
inline_map: {key: value, another: thing}

# This is a Go template (quote it!)
templated: "{{ .vars.value }}"

Best practice: Avoid flow-style maps when using templates

stacks/config.yaml (Better)

vars:
# Use block-style maps for clarity
inline_map:
key: value
another: thing

# Templates are clearly quoted
templated: "{{ .vars.value }}"

Nested Templates in Lists

stacks/prod.yaml

components:
terraform:
vpc:
vars:
# ✅ Each template quoted
availability_zones:
- "{{ .vars.region }}a"
- "{{ .vars.region }}b"
- "{{ .vars.region }}c"

# ✅ Complex template in list
subnet_cidrs:
- '{{ printf "10.%d.1.0/24" .vars.vpc_index }}'
- '{{ printf "10.%d.2.0/24" .vars.vpc_index }}'

Common YAML Gotchas

1. Indentation Matters (2 Spaces Standard)

WRONG: Mixed indentation

components:
terraform:
vpc: # ❌ Tab here
vars: # ❌ Spaces here (inconsistent)
cidr: "10.0.0.0/16"

CORRECT: Consistent 2-space indentation

components:
terraform:
vpc:
vars:
cidr: "10.0.0.0/16"

Best practice:

  • ✅ Use 2 spaces (Atmos convention)
  • ✅ Configure editor to insert spaces for Tab
  • ✅ Never mix tabs and spaces
  • ✅ Use .editorconfig to enforce (see EditorConfig Validation)

2. Colons in Unquoted Strings

WRONG

vars:
url: https://example.com # ❌ Colon confuses parser
time: 12:30:00 # ❌ Also problematic

CORRECT

vars:
url: "https://example.com" # ✅ Quoted
time: "12:30:00" # ✅ Quoted

3. Lists vs Maps

stacks/config.yaml

vars:
# This is a LIST of maps
servers:
- name: web1
ip: 10.0.1.10
- name: web2
ip: 10.0.1.11

# This is a MAP with nested maps
server_config:
web1:
ip: 10.0.1.10
web2:
ip: 10.0.1.11

In Atmos, this matters for deep merge:

  • Maps merge recursively
  • Lists replace entirely

4. Empty Values

stacks/config.yaml

vars:
# These are different!
null_value: null # null
tilde_value: ~ # null (YAML 1.1)
empty_string: "" # empty string
no_value: # null (key with no value)

# For Atmos, prefer explicit empty string or null
description: "" # Empty string
optional_field: null # Explicitly null

5. Duplicate Keys (Last Wins)

stacks/config.yaml

vars:
environment: dev # This gets overwritten
region: us-east-1
environment: prod # ✅ This wins (last occurrence)

Result: environment: prod

Best practice: Use linters to detect duplicates (see YAML Schema Validation)

Best Practices for Maintainable YAML

1. Consistent Quoting

stacks/prod.yaml

# ✅ GOOD: All strings quoted
vars:
namespace: "myapp"
environment: "production"
region: "us-east-1"
cidr_block: "10.0.0.0/16"

2. Use Block Style for Nested Data

stacks/prod.yaml

# ✅ GOOD: Block style (readable, commentable)
vars:
tags:
Environment: production
Team: platform
ManagedBy: atmos

# ❌ AVOID: Flow style (compact but hard to read/comment)
vars:
tags: {Environment: production, Team: platform, ManagedBy: atmos}

3. Leverage Comments

stacks/prod.yaml

components:
terraform:
vpc:
vars:
# Production VPC requires 3 AZs for HA
availability_zones:
- us-east-1a
- us-east-1b
- us-east-1c

# Enable NAT gateways in each AZ (higher cost but redundant)
single_nat_gateway: false

# DNS support required for EKS cluster
enable_dns_hostnames: true
enable_dns_support: true

4. Use Anchors for Repeated Config (Within File)

stacks/prod.yaml

vars:
prod_tags: &prod_tags
Environment: production
Team: platform
Compliance: SOC2

components:
terraform:
vpc:
vars:
tags: *prod_tags

eks:
vars:
tags: *prod_tags

5. Separate Concerns with Imports (Across Files)

stacks/_defaults/tags.yaml

vars:
common_tags:
ManagedBy: Atmos
Team: Platform

stacks/prod.yaml

import:
- _defaults/tags

vars:
environment: production

components:
terraform:
vpc:
vars:
tags:
# Inherits common_tags from import
Environment: production

6. Use YAML Schema Validation

Configure JSON Schema validation to catch errors:

atmos.yaml

schemas:
atmos:
manifest: "https://atmos.tools/schemas/atmos/manifest/1.0.json"
stacks:
manifest: "https://atmos.tools/schemas/stacks/stack-config/1.0.json"

See JSON Schema Validation for details.

7. Validate YAML with EditorConfig

Use .editorconfig to enforce consistent formatting:

.editorconfig

[*.{yaml,yml}]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

See EditorConfig Validation for details.

YAML Linting Tools

Use linters to catch issues early:

yamllint - General YAML linting

# Install
pip install yamllint

# Lint stacks
yamllint stacks/

Atmos Validation - Schema and policy validation

# Validate stack configurations
atmos validate stacks

# Validate specific component
atmos validate component vpc -s prod

See Validation for comprehensive validation strategies.

Quick Reference: YAML in Atmos

FeatureSyntaxUse Case
Anchor&nameDefine reusable config within file
Reference*nameReuse anchored config
Merge<<: *nameCombine anchor with new keys
Literal Block|Multi-line string, preserve newlines
Folded Block>Multi-line string, fold to single line
Quote Strings"string"Prevent type coercion, escape special chars
Go Template"{{ .vars.x }}"Dynamic values (MUST quote)
JSON in YAML{"key": "val"}Compact objects (use sparingly)
Comment# commentDocument configuration

Key Takeaways

  • Quote all string values - Prevents type coercion surprises
  • Quote Go templates - Avoids YAML parsing conflicts with braces
  • Use 2-space indentation - Atmos standard, configure your editor
  • Leverage anchors within files - Use imports across files
  • Prefer block style over flow style - More readable and commentable
  • Validate early and often - Use schema validation and linters
  • YAML is JSON-compatible - But prefer YAML syntax for readability
  • Watch for NO, ON, OFF - Country codes and words that become booleans

See Also