YAML is data, not code

YAML is a human-friendly format for representing structured data. You will see it in Home Assistant, Docker Compose, GitHub Actions, Kubernetes, and many tool configurations. It does not “run” by itself; the application reading it gives it meaning.

The central rule: indentation is structure. A single wrong space can change the meaning or make the file invalid.

The three shapes

# Mapping: key to value
name: lounge

# List: ordered items
rooms:
  - lounge
  - office

# Nested mapping
climate:
  target: 21
  mode: heat

Use spaces, never tabs. Two spaces per nesting level is a common, readable convention.

Scalars: strings, numbers, booleans, null

port: 8080
enabled: true
name: "Lounge heater"
notes: null

Quote strings when they contain characters with special meaning, leading zeroes, dates you mean to keep as text, or values that could be mistaken for booleans. In YAML ecosystems, yes, on, off, and date-looking values can be interpreted unexpectedly depending on parser/version.

# Safer intent
pin: "01"
label: "on"
message: "Restart at 06:00"

Lists of objects

automations:
  - alias: Notify when WAN is down
    trigger:
      - platform: state
        entity_id: binary_sensor.8_8_8_8
        to: "off"
    action:
      - service: notify.mobile_app_phone
        data:
          message: "WAN is unavailable"

The dash begins each list item. The nested keys underneath belong to that item.

Home Assistant: readability and correctness

alias: Lounge AC scheduled start
mode: single
trigger:
  - platform: time
    at: input_datetime.living_room_ac_scheduled_time
action:
  - service: climate.set_temperature
    target:
      entity_id: climate.lounge
    data:
      temperature: 26
      hvac_mode: heat

Guidelines:

  • Prefer the HA UI for helpers and common automations when it offers the same capability.
  • Use meaningful alias, id, and entity names.
  • Make automation mode explicit: single, restart, queued, or parallel changes behaviour under repeated triggers.
  • Use the Developer Tools YAML/config validation before reloads.
  • Build one trigger/action path first; add conditions and branches only once the baseline works.

Docker Compose YAML

services:
  app:
    image: example/app:1.2.3
    restart: unless-stopped
    environment:
      TZ: Australia/Melbourne
    ports:
      - "8080:8080"
    volumes:
      - app_data:/var/lib/app

volumes:
  app_data:

For services that matter, pin major/minor versions or digest deliberately; latest means a later pull can change behaviour. Keep credentials in an ignored .env file or secret manager—not in committed Compose YAML.

Validate before changes:

docker compose config       # expand and validate config
docker compose up -d        # apply
docker compose logs -f app  # observe

Anchors: reuse only when it improves clarity

x-common: &common
  restart: unless-stopped

services:
  app:
    <<: *common
    image: example/app:1.2.3

Anchors reduce repetition, but overusing them makes a file harder to read. Prefer straightforward duplication for small, distinct services.

GitHub Actions workflow shape

name: Build site
on:
  push:
    branches: [main]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run build

Workflow YAML often has its own expression syntax (${{ ... }}) and schema. A valid YAML file can still be an invalid workflow—validate against the platform documentation and inspect action logs.

Merge vs replace: a common conceptual trap

YAML itself simply represents data. Whether a list is merged, replaced, appended, or overridden is decided by the application. For example, Compose overrides and Home Assistant includes have their own rules. Never assume “it will merge”; read the consuming tool’s documentation.

Debugging YAML quickly

  1. Read the error’s line and column, then look above it—the structural mistake is often earlier.
  2. Check alignment of every sibling key.
  3. Verify every list item has a dash at the same indentation level.
  4. Quote suspicious scalar values.
  5. Reduce to the smallest valid example; add blocks back one at a time.
  6. Use the application’s validator, not only a generic YAML checker.

Common failures

FailureWhyFix
TabsYAML indentation requires spacesConfigure editor to insert spaces
Misaligned list dashChanges parent/child relationshipAlign - with sibling items
Colon inside unquoted textParsed as mapping syntaxQuote the complete string
Duplicate keyLater value may overwrite earlier oneUse linting; keep keys unique
Secret committed to GitYAML looks like ordinary configUse .env, GitHub/HA secrets, .gitignore
Valid YAML, broken appApp schema is wrongValidate with app-specific tool

A safe edit workflow

# Format/check where a tool supports it
docker compose config

# Review only your intended change
git diff -- compose.yaml

# Apply and observe
docker compose up -d
docker compose ps
docker compose logs --tail=100 app