If you've ever managed a Kubernetes cluster, set up a GitHub Actions workflow, or written a Docker Compose file, you've already written YAML — probably lots of it. YAML is the config format that powers most of modern DevOps tooling. But despite being everywhere, it's a format that surprises people constantly. I've debugged more than my share of YAML indentation errors at 11pm with a deploy blocked. Let's make sure you don't have to.

YAML stands for YAML Ain't Markup Language — yes, it's a recursive acronym. It was originally "Yet Another Markup Language" but was renamed to emphasise that it's a data serialisation format, not a document markup language. The YAML 1.2 spec is the current standard, though many tools still implement YAML 1.1, which has some genuinely dangerous differences we'll cover below.

The Basics: YAML Syntax at a Glance

YAML uses indentation (spaces only — never tabs) to express structure. A YAML document is a mapping (key-value pairs), a sequence (a list), or a scalar (a single value). Here's a real Kubernetes-style config that covers all the essentials:

yaml
# This is a comment — YAML supports them, JSON does not
app:
  name: payment-service
  version: "2.1.0"         # quoted to keep it a string
  port: 8080
  debug: false
  timeout: 30.5
  tags:
    - payments
    - backend
    - critical
  database:
    host: postgres.internal
    port: 5432
    ssl: true
    password: null          # explicitly no value

No curly braces, no quotes on most strings, no commas. For a file humans edit every day, that clarity adds up fast. But YAML pays for that readability with complexity — there's a lot happening under the hood.

Scalar Types: Strings, Numbers, Booleans, Null

YAML infers types from the value's appearance. This is convenient most of the time and quietly catastrophic in a few specific cases.

yaml
# Strings — quotes are optional unless value is ambiguous
name: Alice
greeting: "Hello, world"
message: 'single quotes work too'
path: /usr/local/bin           # unquoted — still a string

# Numbers
integer: 42
negative: -7
float: 3.14159
scientific: 6.022e23

# Booleans
ssl_enabled: true
verbose: false

# Null
api_key: null
legacy_field: ~               # tilde is also null in YAML
The Norway Problem: In YAML 1.1 (used by PyYAML, many older tools), the unquoted values yes, no, on, off, true, false, y, n, Y, N — and their uppercase variants — are all parsed as booleans. This means the ISO country code NO (Norway) becomes boolean false. YAML 1.2 fixed this. Always quote strings that look like booleans. See the Wikipedia entry on the Norway Problem for the full backstory.
yaml
# YAML 1.1 implicit type coercion — these silently become booleans:
country: NO       # → false  (Norway Problem!)
enabled: yes      # → true
toggle: on        # → true

# The fix: always quote when there's any ambiguity
country: "NO"
enabled: "yes"
version: "1.0"    # also quote version numbers — 1.0 would be a float

Sequences: Lists in YAML

Lists use a hyphen-space prefix. Each item can be a scalar, a mapping, or another sequence:

yaml
# Simple list
languages:
  - Python
  - Go
  - TypeScript

# List of objects (common in Kubernetes)
containers:
  - name: api
    image: my-api:latest
    port: 8080
  - name: sidecar
    image: envoy:v1.28
    port: 9901

# Inline flow style (valid YAML, less readable)
tags: [payments, backend, v2]

Multi-Line Strings: Literal Block vs Folded

This is where YAML pulls ahead of JSON for config files. Two special operators handle multi-line strings gracefully:

yaml
# Literal block scalar (|) — preserves newlines exactly
startup_script: |
  #!/bin/bash
  set -e
  echo "Starting service..."
  npm start

# Folded scalar (>) — folds newlines into spaces (good for long descriptions)
description: >
  This service handles payment processing
  for all regions. It connects to Stripe
  and falls back to PayPal.
# Result: "This service handles payment processing for all regions. It connects to Stripe and falls back to PayPal."

# Chomp modifiers
keep_trailing: |+
  line one
  line two

strip_trailing: |-
  line one
  line two

The | operator is what makes GitHub Actions' multi-line run: scripts readable. The yaml-multiline.info cheat sheet is the quickest way to remember which chomp modifier does what. Without these block scalars, you'd be writing escaped newlines into a single string — which is what JSON forces you to do.

Tabs vs Spaces — The Cardinal Rule

YAML forbids tabs for indentation. Completely. If your editor inserts a tab character anywhere in a YAML file's indentation, the file will either fail to parse or — worse — parse incorrectly. Configure your editor to use spaces for .yml and .yaml files. Two spaces is the near-universal convention.

This is the #1 cause of cryptic YAML errors. A tab looks identical to spaces in most editors. Enable "show whitespace characters" in your editor when debugging YAML, or paste your file into the YAML Validator to get a clear error message pointing to the exact line.

Real Example: GitHub Actions Workflow

Here's a real GitHub Actions workflow snippet. Notice how the multi-line run: block uses the literal block scalar, and how indentation determines structure:

yaml
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: |
          npm run lint
          npm run test -- --coverage
          npm run build

Real Example: Kubernetes Deployment Manifest

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-api
  namespace: production
  labels:
    app: payment-api
    version: "2.1.0"
spec:
  replicas: 3
  selector:
    matchLabels:
      app: payment-api
  template:
    metadata:
      labels:
        app: payment-api
    spec:
      containers:
        - name: api
          image: payment-api:2.1.0
          ports:
            - containerPort: 8080
          env:
            - name: NODE_ENV
              value: production
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-secret
                  key: password
          resources:
            requests:
              memory: "128Mi"
              cpu: "100m"
            limits:
              memory: "512Mi"
              cpu: "500m"

Parsing YAML in Python and JavaScript

In Python, the PyYAML library is the standard choice. Always use safe_load(), not load() — the unsafe version can execute arbitrary Python code from a YAML file:

python
import yaml

# Reading a YAML config file
with open('config.yaml', 'r') as f:
    config = yaml.safe_load(f)

print(config['app']['name'])        # payment-service
print(config['app']['port'])        # 8080 (integer, not string)
print(config['app']['tags'])        # ['payments', 'backend', 'critical']

# Writing Python data to YAML
data = {
    'service': 'auth-api',
    'replicas': 2,
    'endpoints': ['/login', '/logout', '/refresh']
}
with open('output.yaml', 'w') as f:
    yaml.dump(data, f, default_flow_style=False)

In JavaScript/Node.js, js-yaml is the most widely used library:

js
import yaml from 'js-yaml';
import fs from 'fs';

// Parse YAML string
const raw = fs.readFileSync('config.yaml', 'utf8');
const config = yaml.load(raw);

console.log(config.app.name);     // payment-service
console.log(config.app.tags);     // ['payments', 'backend', 'critical']

// Dump an object to YAML string
const output = yaml.dump({
  name: 'my-service',
  port: 3000,
  tags: ['api', 'public']
});
console.log(output);

Wrapping Up

YAML's indentation-based syntax, support for comments, and readable multi-line strings make it the right choice for config files that humans write and maintain. But it comes with real footguns: the Norway Problem, tab/space confusion, and implicit type coercion from YAML 1.1 parsers. Know the rules, quote anything ambiguous, configure your editor to show whitespace, and use a YAML Formatter to keep files consistent. YAML is a powerful tool once you understand where it bites.