06: YAML nella pratica

← 05 - Anchor, alias, merge key e chiavi · Indice · successivo → 07 - Strumenti, sicurezza, debug e reference

Ogni applicazione che usa YAML in realtà usa un parser concreto, con la sua semantica e le sue estensioni. Questa è la mappa dei quattro contesti che incontrerai più spesso.

ContestoParserSemantica
GitHub Actionsproprietario (non pubblicato)1.2 core
Home AssistantPyYAML1.1
Docker Composego-yaml v41.2 con concessioni 1.1
Kubernetesgo-yaml v2 → conversione a JSON1.1
Astro (frontmatter)js-yaml 41.2 + date + merge
Obsidian (properties)non documentato-

6.1 Home Assistant

È YAML 1.1 puro. La documentazione lo dice esplicitamente: “YAML tratta Y, true, Yes, ON tutti come true… se vuoi impostare lo stato di un’entità a on devi virgolettarlo come 'on'”.

# ✗ sbagliato: 'on' diventa il booleano true
- condition: state
  entity_id: light.salotto
  state: on
 
# ✓ corretto
- condition: state
  entity_id: light.salotto
  state: 'on'

I tag custom. Home Assistant registra i suoi: !secret, !include, !include_dir_list, !include_dir_merge_list, !include_dir_named, !include_dir_merge_named, !env_var, !input.

# configuration.yaml
mqtt: !include mqtt.yaml
automation: !include_dir_merge_list automations/
 
recorder:
  db_url: !secret db_url

Cose da sapere, documentate:

  • gli !include_dir_* leggono solo i file .yaml (i .yml vengono ignorati);
  • !secret cerca secrets.yaml nella cartella del file e risalendo;
  • !secret non va usato in automations.yaml o scripts.yaml: se lo fai, l’editor grafico non riesce più a mostrare o modificare nessuna automazione;
  • gli anchor funzionano solo dentro lo stesso file: ogni file incluso viene parsato a parte, quindi un alias definito altrove dà found undefined alias.

I template Jinja2 vanno virgolettati quando iniziano con {{:

value_template: "{{ states('sensor.temperatura') | float(0) > 21 }}"
 
# per template lunghi, il blocco è più leggibile e non richiede escaping
value_template: >-
  {% if is_state('binary_sensor.porta', 'on') %}
    aperta
  {% else %}
    chiusa
  {% endif %}

>- è la scelta idiomatica per i value_template: unisce le righe e non lascia l’a capo finale. Per i messaggi con a capo veri, usa |.

Chiavi duplicate: la documentazione avverte che vince l’ultima, in silenzio. In un configuration.yaml cresciuto negli anni è un errore realistico.

Validazione: hass --script check_config (o ha core check in supervised).

6.2 GitHub Actions

Il caso on:. La chiave che apre ogni workflow è letteralmente on. GitHub la legge correttamente come stringa (usa uno schema 1.2), ma ogni strumento basato su PyYAML la legge come il booleano True. ✅ verificato: yaml.safe_load() su un workflow restituisce {True: 'push', 'jobs': {...}}.

Non serve virgolettarla nel workflow: serve dire al linter di non lamentarsi (truthy: {check-keys: false} in .yamllint).

Il quoting obbligatorio. La documentazione lo dice: quando un’espressione inizia con !, devi usare la sintassi ${{ }} o virgolettare, perché ! in YAML avvia un tag.

if: ${{ !cancelled() }}      # ✓
if: '!cancelled()'           # ✓
if: !cancelled()             # ✗ errore di sintassi YAML

Invece run: echo ${{ github.sha }} non ha bisogno di virgolette: inizia con $, che non è un carattere speciale.

Anchor e alias: supportati da settembre 2025. La merge key << risulta invece non supportata: quindi puoi riusare un blocco identico, ma non “estendilo e sovrascrivi”. Per quello ci sono i reusable workflow (workflow_call) e le composite action. (Non ho potuto eseguire un workflow reale per riverificarlo ad agosto 2026: se ti serve, provalo su un branch prima di fidarti.)

run: va sempre con |:

- name: Build
  run: |
    npm ci
    npm run build
    npm test

Con > i tre comandi diventerebbero una riga sola.

La regola di sicurezza da non violare: mai interpolare ${{ }} dentro run: con dati che arrivano dall’esterno (titoli di PR, nomi di branch, commenti). La documentazione raccomanda di passarli da una variabile d’ambiente:

- env:
    TITLE: ${{ github.event.pull_request.title }}
  run: echo "$TITLE"

Il motivo: dentro un blocco | non esiste escaping, quindi un titolo malevolo diventa codice eseguito.

Booleani negli input: inputs preserva i booleani, github.event.inputs li converte a stringa. Da cui il classico if: github.event.inputs.debug == 'true'.

6.3 Docker Compose

Anchor, alias e merge sono documentati e supportati. Con due limiti dichiarati: il merge vale solo per i mapping (quindi environment in forma mappa sì, in forma lista no), e le ancore si risolvono prima dell’interpolazione delle variabili, quindi non puoi costruire un anchor con dentro ${VAR}.

Tag custom di Compose: !reset (azzera un attributo) e !override (sostituisce invece di fondere), utili quando componi più file.

Interpolazione: ${VAR}, ${VAR:-default}, ${VAR:?errore}, ${VAR:+valore}. Per un $ letterale si raddoppia: $$. Ordine mentale corretto: prima YAML parsa, poi Compose interpola sulle stringhe risultanti.

command: echo $HOME     # HOME dell'host, sostituito da Compose
command: echo $$HOME    # HOME del container, Compose non lo tocca

Le porte vanno virgolettate: '8080:80'. Il parser attuale di Compose non ha più il problema sessagesimale, ma con i range e gli zeri iniziali le virgolette restano la scelta sicura, e rendono il file leggibile da qualunque tool.

Nota: la chiave version: in cima è obsoleta e produce un warning.

Validazione: docker compose config espande interpolazione, anchor e merge e ti mostra il file finale. È il modo migliore per capire cosa hai davvero scritto.

6.4 Kubernetes

Due caratteristiche cambiano tutto rispetto agli altri contesti:

1. Il manifest viene convertito in JSON. Quindi: niente tag custom, niente !!binary, le chiavi sono sempre stringhe.

2. Il parser è 1.1, quindi vale il Norway problem. Un campo che lo schema vuole stringa e che riceve un booleano produce l’errore classico cannot unmarshal bool into Go value of type string. → In Kubernetes ogni valore stringa ambiguo va virgolettato: value: "true", tag: "1.0", - "NO".

3. La validazione è stretta. L’API server rifiuta campi sconosciuti e chiavi duplicate (a differenza di Home Assistant, che le ingoia). kubectl apply --dry-run=server è il tuo amico.

Novità 2026: KYAML. Kubernetes ha introdotto (alpha in 1.34, beta e default in 1.35) un sottoinsieme di YAML deliberatamente non ambiguo: flow style, tutte le stringhe fra doppi apici, virgole finali ammesse. È YAML valido a tutti gli effetti, ma senza tipizzazione implicita. Lo ottieni con kubectl ... -o kyaml. Vale la pena guardarlo anche solo come segnale: anche chi ha scelto YAML sta standardizzando un modo di scriverlo che non sorprenda.

6.5 Frontmatter: Astro e Obsidian

Astro usa js-yaml 4, quindi: schema 1.2 più i timestamp e la merge key. In pratica:

  • pubDate: 2026-08-21 diventa un oggetto Date → in Zod usa z.coerce.date();
  • yes / no / on restano stringhe (a differenza di Python);
  • le chiavi duplicate sono un errore fatale (js-yaml protesta);
  • sha: 0e1234 diventa il numero 0: un hash di commit corto va sempre virgolettato;
  • il frontmatter deve essere il primissimo contenuto del file.

Obsidian: il parser non è documentato, ma le regole dell’interfaccia Properties sì: niente proprietà annidate, niente Markdown nei valori, i wikilink vanno fra virgolette, e l’editor riscrive il file normalizzando quoting e stile delle liste (e mettendo a rischio i commenti).

La conseguenza pratica se le stesse note vivono in Obsidian e in Astro: tieni il frontmatter piatto, usa gli stessi nomi di campo, e non metterci commenti YAML.

6.6 Riepilogo: le regole per contesto

ContestoLa regola che non puoi ignorare
Home Assistant'on' e 'off' sempre fra apici; template Jinja virgolettati; anchor solo intra-file
GitHub Actionsrun: con |; mai ${{ }} dentro run: con input esterni; niente merge key
Docker Composeporte fra apici; $$ per il $ letterale; docker compose config per verificare
Kubernetesvirgoletta ogni stringa ambigua; --dry-run=server prima di applicare
Frontmattervalori ambigui fra apici; date con z.coerce.date(); schema piatto

Esercizi

  1. Prendi una tua automazione Home Assistant e cerca on/off non virgolettati. Poi fai girare hass --script check_config.
  2. Leggi un tuo workflow GitHub con python3 -c "import yaml;print(yaml.safe_load(open('.github/workflows/ci.yml')))" e osserva la chiave True.
  3. Scrivi un docker-compose.yml con anchor e ${VAR}, poi confronta il sorgente con l’output di docker compose config.
  4. Nel frontmatter di una nota metti versione: 1.10 e codice: 0e1234, e leggi il risultato. Poi correggili.