YAML (“YAML Ain’t Markup Language”) is a human-readable data format used heavily in config files, Docker Compose, Kubernetes, Ansible, CI/CD pipelines, and more.

Mental model: YAML is JSON without the brackets and quotes — identical data structure, friendlier syntax.


Core Rules

  • Indentation = structure (use spaces only, never tabs)
  • Case-sensitive
  • # = comment
  • No quotes needed for simple strings (but they’re allowed)

Data Types

YAMLJSON
Stringname: my-service"name": "my-service"
Quoted stringquoted: "hello world""quoted": "hello world"
Numberport: 8080"port": 8080
Floatratio: 0.75"ratio": 0.75
Booleanenabled: true"enabled": true
Nullvalue: null or value: ~"value": null

Multiline strings (YAML only — no JSON equivalent)

# Literal block — preserves newlines
message: |
  line one
  line two
 
# Folded block — newlines become spaces
description: >
  this becomes
  one long line
// JSON has no multiline syntax — you escape manually
{ "message": "line one\nline two" }

Collections

Mapping (object)

server:
  host: localhost
  port: 3000
  ssl: true
{
  "server": {
    "host": "localhost",
    "port": 3000,
    "ssl": true
  }
}

Sequence (array)

ports:
  - 80
  - 443
  - 8080
 
# Inline style (also valid)
tags: [web, api, prod]
{
  "ports": [80, 443, 8080],
  "tags": ["web", "api", "prod"]
}

Nested

services:
  web:
    image: nginx
    ports:
      - "80:80"
  db:
    image: postgres
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: admin
{
  "services": {
    "web": {
      "image": "nginx",
      "ports": ["80:80"]
    },
    "db": {
      "image": "postgres",
      "environment": {
        "POSTGRES_DB": "myapp",
        "POSTGRES_USER": "admin"
      }
    }
  }
}

Anchors & Aliases (DRY reuse — YAML only)

JSON has no reuse mechanism. In YAML, define an anchor with &name and merge it with <<: *name.

defaults: &defaults     # define anchor
  restart: always
  logging:
    driver: json-file
 
web:
  <<: *defaults         # merge anchor
  image: nginx
 
api:
  <<: *defaults         # reuse again
  image: node:20
{
  "web": {
    "restart": "always",
    "logging": { "driver": "json-file" },
    "image": "nginx"
  },
  "api": {
    "restart": "always",
    "logging": { "driver": "json-file" },
    "image": "node:20"
  }
}

(JSON equivalent requires full repetition — no shorthand)


Environment Variables (Compose / K8s)

# As a list
environment:
  - NODE_ENV=production
  - PORT=3000
 
# As a map (preferred)
environment:
  NODE_ENV: production
  PORT: 3000
{
  "environment": ["NODE_ENV=production", "PORT=3000"]
}
// or
{
  "environment": {
    "NODE_ENV": "production",
    "PORT": "3000"
  }
}

Real-World Patterns

Docker Compose

version: "3.9"
services:
  app:
    build: .
    ports:
      - "3000:3000"
    depends_on:
      - db
    environment:
      DATABASE_URL: postgres://admin:secret@db:5432/myapp
  db:
    image: postgres:15
    volumes:
      - pgdata:/var/lib/postgresql/data
volumes:
  pgdata:
{
  "version": "3.9",
  "services": {
    "app": {
      "build": ".",
      "ports": ["3000:3000"],
      "depends_on": ["db"],
      "environment": {
        "DATABASE_URL": "postgres://admin:secret@db:5432/myapp"
      }
    },
    "db": {
      "image": "postgres:15",
      "volumes": ["pgdata:/var/lib/postgresql/data"]
    }
  },
  "volumes": { "pgdata": {} }
}

GitHub Actions

on:
  push:
    branches: [main]
 
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm test
{
  "on": {
    "push": { "branches": ["main"] }
  },
  "jobs": {
    "test": {
      "runs-on": "ubuntu-latest",
      "steps": [
        { "uses": "actions/checkout@v4" },
        { "run": "npm ci" },
        { "run": "npm test" }
      ]
    }
  }
}

Kubernetes Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: my-app
          image: my-app:latest
          ports:
            - containerPort: 3000
{
  "apiVersion": "apps/v1",
  "kind": "Deployment",
  "metadata": { "name": "my-app" },
  "spec": {
    "replicas": 3,
    "selector": { "matchLabels": { "app": "my-app" } },
    "template": {
      "metadata": { "labels": { "app": "my-app" } },
      "spec": {
        "containers": [
          {
            "name": "my-app",
            "image": "my-app:latest",
            "ports": [{ "containerPort": 3000 }]
          }
        ]
      }
    }
  }
}

Common Gotchas

ProblemFix
Tabs instead of spacesAlways use spaces
yes/no parsed as booleansQuote them: "yes"
Colon in a valueQuote the string: "http://example.com"
Leading zeros (08) parsed as octalQuote it: "08"
Inconsistent indentationPick 2 or 4 spaces and stick to it