Type coercion: the biggest silent bomb
YAML infers scalar types automatically, and many values that "look like strings" get silently converted: `no`/`na`/`n` become the boolean false (the famous Norway problem), `on`/`off`/`yes` become true/false, `0733` becomes octal, and `1.10` becomes the number 1.1.
The result is config that "looks right but runs wrong": version numbers lose precision, country codes turn into booleans. Unless you explicitly want a number/boolean/null, quote every scalar — the single most stress-reducing YAML rule.
country: no # 实际是 false,不是挪威!
country: "no" # 这才是字符串
version: 1.10 # 变成数字 1.1
version: "1.10" # 字符串,安全When quoting is mandatory
Three classes of values must be quoted: ① anything that could be coerced to a number/boolean/null (`no`, `on`, `1.10`, plus `2026-01-01`, which parses as a date object); ② values starting with special characters (`*` `&` `!` `%` `@`); ③ values containing "colon + space" or ` #`.
The date trap deserves emphasis: `date: 2026-01-01` is a date object in most parsers, not a string, and it surfaces in a totally different shape during log debugging. Write `date: "2026-01-01"` for a string.
date: 2026-01-01 # 日期对象,不是字符串
date: "2026-01-01" # 字符串
value: a: b # 语法错误(冒号+空格)
value: "a: b" # 正确Indentation: one level off, data lands elsewhere
YAML accepts spaces only, never tabs (a tab is a hard syntax error); sibling keys must align; content after a list marker `- ` belongs to a child level. One level off and data silently lands under the wrong node — most parsers never complain.
Two frequent details: a space must follow the colon (`key:value` is invalid), and a comment `#` needs a preceding space or it becomes part of the value. A YAML language server (e.g. Red Hat YAML for VSCode) flags these live.
server:
host: 0.0.0.0 # 缩进 2 空格,属于 server
ports:
- 80 # 列表项再缩进一级
- 443
# key:value 错 → key: value 对Multiline strings: | vs >
The pipe `|` keeps newlines (literal block); the fold `>` collapses newlines into spaces (folded block); adding `-` strips the trailing newline (`|-`, `>-`). Use `|` for scripts, certificates and SQL; use `>` for long prose.
The indentation baseline is set by the first line: every line in the block must be indented deeper than the parent key and consistently — this is exactly why "it looks aligned but still errors": you compared against the wrong level.
script: |
#!/usr/bin/env bash
set -euo pipefail
echo "ok"
desc: >
这是一段很长的说明文字,
换行会被折叠成空格。
sql: |-
SELECT *
FROM users;Anchors, aliases and merge keys
An anchor `&name` marks a node, an alias `*name` references it, and `<<:` merges a mapping into the current key — this is how docker-compose reuses shared config.
Two limits to remember: lists cannot be merged with `<<`, only referenced whole; an alias is a reference, not a copy, so changing one place changes all. Bonus trap: unquoted `80:80` parses as a sexagesimal (base-60) number under YAML 1.1 — always quote port mappings as `"80:80"` in compose files.
common: &common
image: nginx:1.27
restart: unless-stopped
web:
<<: *common
ports:
- "80:80"Debugging toolchain and checklist
Validate before shipping: yamllint for style and structure, the consuming tool itself (`docker compose config`, `kubectl --dry-run`), or a one-liner `yaml.safe_load` in Python to confirm what the parser actually sees.
Debugging principle: the reported line usually marks where the problem surfaced, while the real mistake sits a few lines above in indentation or quoting. Checklist: quote scalars, spaces only, space after the colon, pick the right `|`/`>` block, quote port mappings, run a parser before committing.
yamllint config.yaml
docker compose config # 解析并规范化输出
kubectl apply --dry-run=client -f manifest.yaml
python -c "import yaml,sys; yaml.safe_load(open(sys.argv[1]))" config.yaml