YAML for DevOps — The Complete Guide (Stop Googling YAML Syntax)
You have written YAML hundreds of times. You have also Googled "YAML multiline string" hundreds of times. And you have definitely been bitten by that one time no was interpreted as false. Let us fix that today — one guide to rule them all.
YAML Basics: Key-Value Pairs, Lists, and Nesting
YAML (YAML Ain't Markup Language) uses indentation to represent structure. No brackets, no braces, no commas. Just clean, human-readable data.
# Key-value pairs
name: my-web-app
version: 2.1.0
port: 8080
debug: true
# Lists (sequences)
languages:
- Python
- JavaScript
- Go
# Inline list
tags: [devops, cicd, cloud]
# Nested objects (mappings)
database:
host: localhost
port: 5432
credentials:
username: admin
password: secret123
# Inline object
limits: {cpu: "500m", memory: "256Mi"}
Golden rule: Use 2 spaces for indentation. Never tabs. Tabs in YAML will break everything and the error message will not help you.
Data Types
YAML auto-detects types, which is both convenient and dangerous.
# Strings
name: hello # Unquoted string
name: "hello" # Double-quoted (supports escape sequences)
name: 'hello' # Single-quoted (literal, no escapes)
# Numbers
count: 42 # Integer
price: 19.99 # Float
hex: 0xFF # Hexadecimal (= 255)
octal: 0o777 # Octal (= 511)
scientific: 1.2e+5 # Scientific notation
# Booleans
enabled: true # Boolean true
disabled: false # Boolean false
# Null
value: null # Explicit null
value: ~ # Also null
value: # Empty value = null
# Dates
created: 2025-04-12 # ISO 8601 date
timestamp: 2025-04-12T10:30:00Z # Full timestamp
Multi-Line Strings: The Part You Always Google
This is the section you came for. Bookmark it.
# LITERAL BLOCK ( | ) — preserves newlines exactly as written
script: |
#!/bin/bash
echo "Line 1"
echo "Line 2"
echo "Line 3"
# Result: "#!/bin/bash\necho \"Line 1\"\necho \"Line 2\"\necho \"Line 3\"\n"
# FOLDED BLOCK ( > ) — folds newlines into spaces (like a paragraph)
description: >
This is a really long description
that spans multiple lines but will
be folded into a single paragraph.
# Result: "This is a really long description that spans multiple lines but will be folded into a single paragraph.\n"
# STRIP trailing newline ( |- or >- )
clean: |-
No trailing newline here
# Result: "No trailing newline here" (no \n at end)
# KEEP all trailing newlines ( |+ or >+ )
keep: |+
Keep the trailing newlines
# Result: "Keep the trailing newlines\n\n\n"
# INDENT INDICATOR ( |2, >2 ) — specify indent width
indented: |2
This text starts with 2 extra spaces
that are preserved in the output.
# Result: " This text starts with 2 extra spaces\n that are preserved in the output.\n"
Here is the cheat sheet:
| Syntax | Newlines | Trailing Newline |
|---|---|---|
| | Preserved | One trailing \n (clip) |
|- | Preserved | No trailing \n (strip) |
|+ | Preserved | All trailing \n kept (keep) |
> | Folded to spaces | One trailing \n (clip) |
>- | Folded to spaces | No trailing \n (strip) |
>+ | Folded to spaces | All trailing \n kept (keep) |
Anchors and Aliases: DRY YAML
Anchors (&) define reusable blocks. Aliases (*) reference them. The merge key (<<) merges anchor content into a mapping.
# Define reusable defaults with anchors
defaults: &default-settings
restart: always
networks:
- app-network
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
services:
web:
<<: *default-settings # Merge all default settings
image: nginx:alpine
ports:
- "80:80"
api:
<<: *default-settings # Same defaults
image: my-api:latest
ports:
- "3000:3000"
restart: on-failure # Override the default
worker:
<<: *default-settings # Same defaults
image: my-worker:latest
# Anchor for list items
common-env: &common-env
- NODE_ENV=production
- LOG_LEVEL=info
services:
app:
environment:
<<: *common-env # Does NOT work for lists
# For lists, you need to repeat or use a different approach
Important caveat: The merge key (<<) only works with mappings (objects), not sequences (lists). You cannot merge lists with anchors.
Common YAML Mistakes (and How to Avoid Them)
Mistake 1: The Boolean Trap
# DANGER: These are all booleans, not strings!
country: no # false (Norway would like a word)
answer: yes # true
status: on # true
status: off # false
value: NO # false
value: YES # true
# FIX: Quote them!
country: "no" # String "no"
answer: "yes" # String "yes"
status: "on" # String "on"
# Full list of boolean gotchas in YAML 1.1:
# true: y, Y, yes, Yes, YES, true, True, TRUE, on, On, ON
# false: n, N, no, No, NO, false, False, FALSE, off, Off, OFF
This is the infamous "Norway problem." In 2021, it broke multiple production systems when country codes like NO were silently converted to false.
Mistake 2: Tabs Instead of Spaces
# WRONG — tabs will cause a parse error
server:
port: 8080 # This is a tab. YAML hates tabs.
# RIGHT — use 2 spaces
server:
port: 8080 # Two spaces. YAML is happy.
Mistake 3: Unquoted Special Characters
# WRONG — colon followed by space starts a mapping
message: Error: something failed # Parse error!
path: C:\Users\admin # Escape issues
# RIGHT — quote strings with special characters
message: "Error: something failed"
path: "C:\\Users\\admin"
# WRONG — curly braces are inline mappings
regex: {[a-z]+} # Parse error!
# RIGHT
regex: "{[a-z]+}"
Mistake 4: Forgetting That Numbers Are Numbers
# WRONG — these are numbers, not strings
version: 1.0 # Float 1.0, not string "1.0"
zipcode: 01onal234 # Octal number, not "01234"
phone: 1234567890 # Integer, not string
# RIGHT — quote version strings and codes
version: "1.0"
zipcode: "01234"
phone: "1234567890"
YAML in Practice: DevOps Tool Configs
Kubernetes Manifest
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
labels:
app: web-app
environment: production
spec:
replicas: 3
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web
image: my-app:v2.1.0
ports:
- containerPort: 8080
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secret
key: url
Docker Compose
version: "3.8"
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DB_HOST=postgres
depends_on:
postgres:
condition: service_healthy
volumes:
- ./uploads:/app/uploads
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin"]
interval: 10s
timeout: 5s
retries: 5
volumes:
pgdata:
Ansible Playbook
---
- name: Configure web servers
hosts: webservers
become: true
vars:
app_port: 8080
app_user: deploy
tasks:
- name: Install required packages
apt:
name:
- nginx
- nodejs
- npm
state: present
update_cache: true
- name: Copy nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/default
notify: Restart nginx
handlers:
- name: Restart nginx
service:
name: nginx
state: restarted
Linting YAML: Catch Mistakes Before They Bite
# Install yamllint
pip install yamllint
# Lint a file
yamllint docker-compose.yml
# Lint with a custom config
yamllint -c .yamllint.yml .
# Sample .yamllint.yml config
cat <<'EOF' > .yamllint.yml
extends: default
rules:
line-length:
max: 120
allow-non-breakable-words: true
truthy:
check-keys: true # Catches the boolean trap!
indentation:
spaces: 2
indent-sequences: true
comments:
min-spaces-from-content: 1
EOF
# Lint all YAML files in a project
yamllint -s $(find . -name "*.yml" -o -name "*.yaml")
YAML vs JSON vs TOML
| Feature | YAML | JSON | TOML |
|---|---|---|---|
| Readability | Excellent | Good | Excellent |
| Comments | Yes (#) | No | Yes (#) |
| Multi-line strings | Yes (6 styles) | No (use \n) | Yes (""") |
| Data types | Rich (dates, nulls) | Basic (no dates) | Rich (dates, times) |
| Anchors/Aliases | Yes | No | No |
| Trailing commas | N/A (no commas) | No | Yes |
| Complexity | High (gotchas) | Low (strict) | Medium |
| Used By | K8s, Ansible, CI/CD | APIs, configs | Cargo, pyproject |
| Superset of | JSON (YAML is a superset) | - | - |
Fun fact: Valid JSON is valid YAML. YAML 1.2 is a superset of JSON. So if you are ever stuck with YAML syntax, you can just write JSON inside a .yml file and it will work.
YAML is the lingua franca of DevOps. Love it or hate it, you are going to write a lot of it. Bookmark this guide and stop Googling the same syntax questions every week.
Next in our DevOps series, we will cover Version Control Best Practices — conventional commits, branch naming, code reviews, and everything else that makes your Git history actually useful.
