Introduction

YAML (YAML Ain't Markup Language) is a human-readable data serialization format. It is widely used for configuration files, CI/CD pipelines, Kubernetes manifests, Ansible playbooks, and Docker Compose.

Human-Readable

Uses indentation and minimal punctuation so files are easy to read and write without tooling.

Superset of JSON

Any valid JSON is valid YAML. YAML extends JSON with comments, anchors, and multiline strings.

Whitespace-Sensitive

Indentation defines structure. Only spaces are allowed — never tabs — and consistency is mandatory.

Language-Agnostic

Parsers exist for Python, JavaScript, Go, Java, Ruby, and more. Maps to native data structures.

⚠️

Never use tabs YAML strictly forbids tab characters for indentation. Always use spaces — 2 spaces per level is the most common convention.

Basic Syntax Rules

YAML is built on a small set of rules: key-value pairs, indentation for nesting, and # for comments.

📄 YAML
# This is a comment — ignored by the parser

# Basic key: value pair
name: Akash
age: 27
active: true
score: 98.5
nothing: null     # or use ~ as shorthand for null

# Keys can contain spaces if quoted
"full name": Akash Chaubey
'home city': Lucknow
RuleDetail
Key-Value SeparatorColon followed by a space: key: value — the space is mandatory
CommentsStart with # — everything after is ignored on that line
IndentationSpaces only. Consistent depth per level (2 or 4 spaces recommended)
Case SensitiveName and name are different keys
Null valueOmit the value, write null, or use ~
Special CharactersCharacters like : { } [ ] , & * # ? | - < > = ! % @ \ need quoting in strings
💡

A colon with no trailing space (e.g. http://example.com) is treated as a plain string, not a key-value separator. Always add a space after : in mappings.

Data Types

YAML auto-detects types from the value's format. Understanding implicit typing prevents surprises.

📄 YAML — All scalar types
# ── Integers ──
decimal: 42
negative: -17
octal: 0o17        # YAML 1.2: 0o prefix
hex: 0xFF
binary: 0b1010

# ── Floats ──
pi: 3.14159
scientific: 1.2e+10
infinity: .inf
not_a_number: .nan

# ── Booleans ──
is_active: true       # also: True, TRUE
is_deleted: false     # also: False, FALSE

# ── Null ──
empty_a: null
empty_b: ~
empty_c:              # value omitted → null

# ── Timestamps ──
date: 2026-06-03
datetime: 2026-06-03T10:30:00Z
⚠️

Boolean gotcha — In YAML 1.1, values like yes, no, on, off were parsed as booleans. YAML 1.2 removed this. Quote them if you mean literal strings: "yes", "no".

Strings & Quoting

Strings in YAML do not require quotes in most cases. Quotes are needed when a value contains special characters or could be misinterpreted as another type.

📄 YAML — String styles
# ── Plain (unquoted) ──
city: Lucknow
greeting: Hello World

# ── Single-quoted — literal, no escape sequences ──
path: 'C:\Users\akash\documents'
price: '$100'
escaped_quote: 'It''s a trap'   # double '' for a literal '

# ── Double-quoted — supports escape sequences ──
newline: "Line one\nLine two"
tab: "Col1\tCol2"
unicode: "\u0041 is the letter A"
ampersand: "YAML & JSON"        # & needs quoting
StyleQuotesEscape SequencesWhen to use
PlainNoneNoneSimple values with no special characters
Single-quoted'…'None (literal backslash)Paths, regex, strings with : or #
Double-quoted"…"\n \t \u \\ etc.Strings needing escape sequences

Multiline Strings

Two block scalar indicators control how newlines and trailing whitespace are handled in multiline values.

| LITERAL BLOCK
📄 YAML
script: |
  echo "Hello"
  echo "World"
  exit 0
📌

Newlines are preserved as \n. Ideal for shell scripts, GitHub Actions steps, SQL queries.

> FOLDED BLOCK
📄 YAML
description: >
  This is a long description
  that wraps across multiple
  lines in the source file.
📌

Single newlines become spaces. Result is one long string. Good for long descriptions.

📄 YAML — Chomping modifiers
# Default: keeps ONE trailing newline
default: |
  Hello

# Strip (-): removes ALL trailing newlines
stripped: |-
  Hello

# Keep (+): preserves ALL trailing newlines
kept: |+
  Hello


# Same modifiers work with folded (>- and >+)
folded_strip: >-
  This becomes one long
  string with no trailing newline.
IndicatorNewlinesTrailing newlines
|Preserved as \nOne trailing newline (default)
|-Preserved as \nStripped — none
|+Preserved as \nAll preserved
>Folded to spacesOne trailing newline (default)
>-Folded to spacesStripped — none
>+Folded to spacesAll preserved

Lists & Mappings

YAML has two collection types: sequences (lists/arrays) and mappings (dictionaries/objects). Both support block and flow styles.

📄 YAML — Sequences (Lists)
# Block style — one item per line
fruits:
  - apple
  - banana
  - mango

# Flow style — JSON-like, on one line
colors: [red, green, blue]

# List of numbers
ports: [80, 443, 8080]

# Empty list
tags: []
📄 YAML — Mappings (Dictionaries)
# Block style
person:
  name: Akash
  age: 27
  city: Lucknow

# Flow style — JSON-like
point: {x: 10, y: 20}

# Empty mapping
metadata: {}
💡

Block style is more readable for humans; flow style is compact and useful for short inline collections. You can mix them — a block sequence can contain flow mappings.

Nested Structures

Combine sequences and mappings at any depth. Indentation defines the parent-child relationship.

📄 YAML — List of objects
users:
  - name: Alice
    role: admin
    permissions: [read, write, delete]

  - name: Bob
    role: viewer
    permissions: [read]
📄 YAML — Deeply nested mapping
app:
  server:
    host: 0.0.0.0
    port: 8080
    tls:
      enabled: true
      cert: /etc/ssl/cert.pem
      key: /etc/ssl/key.pem
  database:
    host: localhost
    port: 5432
    name: myapp_db
📄 YAML — Mixed nesting (list inside list)
matrix:
  - [1, 2, 3]
  - [4, 5, 6]
  - [7, 8, 9]

environments:
  - name: dev
    vars:
      DEBUG: true
      LOG_LEVEL: verbose
  - name: prod
    vars:
      DEBUG: false
      LOG_LEVEL: error
⚠️

Inconsistent indentation is the most common YAML error. Each level must be indented by the same number of spaces as its siblings. A linter like yamllint catches these early.

Anchors & Aliases

Anchors (&name) mark a node for reuse. Aliases (*name) reference it elsewhere — eliminating copy-paste in large config files.

📄 YAML — Basic anchor and alias
# & defines the anchor, * references it
original: &my_anchor Hello World

copy_a: *my_anchor   # → "Hello World"
copy_b: *my_anchor   # → "Hello World"
📄 YAML — Anchoring a mapping block
# Repeated config across three services — without anchors
service1:
  env: prod
  retries: 3
  version: 4.8
service2:
  env: prod
  retries: 3
  version: 4.8

# ── With anchors ──
defaults: &svc_config
  env: prod
  retries: 3
  version: 4.8

service1: *svc_config
service2: *svc_config
📌

Anchor must precede alias — An anchor (&name) must be defined before any alias (*name) that references it. Forward references are not allowed.

⚠️

A plain alias (*name) creates a complete copy of the anchored node. Modifications to the alias do not affect the original. Use merge keys (<<:) if you need to extend with overrides.

Merge Keys

The merge key <<: *anchor splices all keys from the anchored mapping into the current mapping. Keys in the current block override merged values.

📄 YAML — Merge with override
# Define shared defaults
db_defaults: &db_defaults
  adapter: postgres
  host: localhost
  port: 5432

# Development — inherits all defaults
development:
  <<: *db_defaults
  database: myapp_dev

# Production — inherits defaults, overrides host
production:
  <<: *db_defaults
  database: myapp_prod
  host: db.example.com    # overrides merged value
📄 YAML — Merging multiple anchors
logging: &logging
  log_level: info
  log_file: app.log

monitoring: &monitoring
  metrics: true
  tracing: true

# Merge two anchors at once using a list
service:
  <<: [*logging, *monitoring]
  name: auth-service
  port: 3000
💡

When two merged anchors have the same key, the first anchor in the list wins. Keys defined locally in the mapping always take highest priority over all merged values.

Tags & Type Casting

Tags explicitly declare the type of a value, overriding YAML's automatic type inference. Built-in tags use the !! prefix.

📄 YAML — Built-in type tags
# Force a number to be treated as a string
zip_code:   !!str 110001

# Force a quoted string to become an integer
timeout:    !!int "30"

# Force to float
version:    !!float "3"        # → 3.0

# Force a string that looks like boolean
answer:     !!str true          # → "true" (string)

# Sequence and mapping tags
items:      !!seq [a, b, c]
config:     !!map {key: val}
📄 YAML — Custom application tags
# Custom tags signal special handling to the parser
user:
  name: John Doe
  address: !address
    street: 123 Main St
    city: Delhi
    postal_code: 110001
TagTypeExample
!!strString!!str 42"42"
!!intInteger!!int "42"42
!!floatFloat!!float "3"3.0
!!boolBoolean!!bool "true"true
!!nullNull!!null ""null
!!seqSequence / ListExplicit list type
!!mapMapping / DictExplicit dict type
!customApplication-definedParser-specific handling

Multi-Document Files

A single .yaml file can contain multiple independent documents, separated by ---. This is common in Kubernetes manifests.

📄 YAML — Multiple documents
---   # Document 1 starts
name: Alice
role: admin
...   # Optional explicit end marker

---   # Document 2 starts
name: Bob
role: viewer

---   # Document 3 starts
name: Carol
role: editor
📄 YAML — Kubernetes multi-resource manifest
---
apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: my-app
  ports:
    - port: 80

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-deployment
spec:
  replicas: 3
MarkerMeaning
---Start of a new document. Also separates YAML directives from document content.
...Explicit end of a document. Optional — omitting it is valid.

Real-World Examples

YAML is the standard format for Docker Compose, GitHub Actions, Kubernetes, and Ansible. Here are practical patterns you'll encounter every day.

🐳 Docker Compose
services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro
    environment:
      - NGINX_HOST=example.com
    restart: unless-stopped
    depends_on:
      - api

  api:
    build:
      context: ./api
      dockerfile: Dockerfile
    env_file:
      - .env
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: "1.0"

volumes:
  data:
    driver: local

networks:
  frontend:
    driver: bridge
⚙️ GitHub Actions CI/CD
name: CI

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

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Multi-line run
        run: |
          echo "Building..."
          npm run build
          echo "Done!"
☸️ Kubernetes Deployment
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: app
          image: myrepo/my-app:latest
          ports:
            - containerPort: 8080
          env:
            - name: ENV
              value: production
          resources:
            limits:
              memory: 256Mi
              cpu: "500m"
📄 YAML — Shared config with anchors & merge (real pattern)
# Shared base config
base: &base
  adapter: postgres
  pool: 5
  timeout: 5000

development:
  <<: *base
  database: app_development
  host: localhost

test:
  <<: *base
  database: app_test
  host: localhost

production:
  <<: *base
  database: app_production
  host: db.example.com
  pool: 25              # overrides merged pool: 5
  timeout: 10000         # overrides merged timeout

Best Practices — Always use a YAML linter (yamllint). Quote strings that start with special characters. Register custom tags in your parser. Avoid deeply nested structures — flatten where possible. Use anchors for shared blocks, not for single scalar values.