05: Anchor, alias, merge key e chiavi

← 04 - Tipi, risoluzione implicita e trappole · Indice · successivo → 06 - YAML nella pratica

5.1 Anchor e alias: non ripetersi

Un anchor (&nome) etichetta un nodo; un alias (*nome) lo richiama.

default: &impostazioni
  restart: unless-stopped
  logging:
    driver: json-file
 
servizio_a:
  <<: *impostazioni
  image: nginx
 
servizio_b: *impostazioni

Regole dalla specifica:

  • L’alias si riferisce all’anchor precedente più vicino. Un anchor può essere ridefinito più avanti nel file e da quel punto gli alias puntano al nuovo valore.
  • Un alias non può avere proprietà o contenuto propri: *base restituisce esattamente quel nodo. Non esiste un “alias con override”, per quello serve la merge key.
  • Gli anchor valgono per documento: non attraversano i --- e, soprattutto, non attraversano i file. Non esiste un “anchors.yaml” incluso e riusato altrove.
  • Il nome dell’anchor non può contenere , [ ] { }.

Dettaglio che conta: gli alias condividono l’oggetto, non lo copiano. ✅ verificato in Python: due elementi creati da alias sono lo stesso oggetto in memoria (is → True). Se il programma che legge muta uno, mutano tutti. È anche la base dell’attacco “billion laughs” (07 - Strumenti, sicurezza, debug e reference).

5.2 Merge key <<

base: &base
  a: 1
  b: 2
 
derivato:
  <<: *base
  b: sovrascritto
  c: 3

✅ verificato → {'a': 1, 'b': 'sovrascritto', 'c': 3}

Due regole di precedenza da tenere distinte:

  1. Le chiavi scritte esplicitamente vincono sempre sul merge.
  2. Con più sorgenti, <<: [*A, *B], vince la prima. ✅ verificato: con base = {a:1, b:2} e big = {b:99, c:3}, <<: [*base, *big] dà b: 2, mentre <<: [*big, *base] dà b: 99.

Il punto 2 è l’opposto di quello che fanno Object.assign in JS e dict.update in Python, dove vince l’ultimo. È una fonte di bug garantita se non lo sai.

<< non è nella specifica YAML 1.2

È stato rimosso nel 2009 insieme al resto della libreria di tipi 1.1. Sopravvive come estensione dei parser, e ad agosto 2026 la situazione è questa:

Parser<< attivo di default
PyYAMLsì
ruamel.yaml (round-trip)sì
js-yaml 5no
yaml (npm) 2.xno

✅ verificato: con uno schema 1.2 core, <<: *base non fa il merge: diventa una chiave letterale '<<' con dentro la mappa. Nessun errore, dati sbagliati. Un file che usa << e passa da un tool Python a uno JavaScript si rompe in silenzio.

Aggiungi che nel 2025-2026 il merge di js-yaml ha generato due avvisi di sicurezza (prototype pollution e complessità quadratica), ed è ragionevole concludere: << è comodo, ma usalo solo dove sei certo del parser.

5.3 Quando gli anchor sono la scelta giusta

Sono un meccanismo di riuso testuale, non un sistema di ereditarietà. Funzionano bene per:

  • valori ripetuti nello stesso file (una versione, un percorso, un’immagine Docker);
  • blocchi di configurazione identici (logging, restart policy, healthcheck).

Funzionano male quando:

  • la struttura da riusare sta in un altro file (non si può);
  • ti serve override profondo (il merge è superficiale: sostituisce l’intera chiave di primo livello, non fa merge ricorsivo);
  • vuoi merge di liste: non è previsto, il merge vale solo per i mapping.

Quando raggiungi quei limiti, la risposta non è forzare YAML: è generare il file con uno strumento (script, Helm, Kustomize, un template) o usare i meccanismi di composizione dell’applicazione (!include in Home Assistant, extends in Docker Compose, i reusable workflow di GitHub Actions).

5.4 Un esempio realistico

x-comune: &comune
  restart: unless-stopped
  environment: &env-comune
    TZ: Europe/Rome
    PUID: '1000'
  logging:
    driver: json-file
    options:
      max-size: 10m
 
services:
  app:
    <<: *comune
    image: 'ghcr.io/esempio/app:1.10'
    ports:
      - '8080:80'
 
  worker:
    <<: *comune
    image: 'ghcr.io/esempio/app:1.10'
    command: ['worker']

Nota due cose: il prefisso x- per le chiavi “di servizio” (Docker Compose le ignora ufficialmente) e le virgolette su '1.10', '1000' e '8080:80': tutti valori che senza apici cambierebbero tipo (04 - Tipi, risoluzione implicita e trappole).

5.5 Chiavi: i casi limite

Duplicate. La specifica: errore. PyYAML: silenzio, vince l’ultima. Già visto in 02 - Struttura - documenti, mapping, indentazione, ma vale la ripetizione perché è il bug più frequente in assoluto. Attiva key-duplicates in yamllint.

Non stringa. ✅ verificato: 1: uno e true: booleano nello stesso mapping, letti da Python, danno {1: 'booleano'}, perché in Python True == 1. YAML le considera chiavi distinte, Python no. Il dato sparisce senza avviso.

Molto lunghe. Una chiave implicita non può superare i 1024 caratteri né stare su più righe. Se ti serve di più, esiste la sintassi esplicita:

? |
  una chiave
  su più righe
: il suo valore

Se ti trovi ad averne bisogno, quasi sempre il modello dei dati è sbagliato.

Ordine. YAML non garantisce che l’ordine delle chiavi sia significativo. Molti parser lo preservano (Python dalla 3.7 mantiene l’ordine di inserimento), ma non contarci come parte del contratto.

Esercizi

  1. Scrivi un docker-compose.yml con tre servizi che condividono logging e restart policy via anchor. Poi verifica con docker compose config che l’espansione sia quella attesa.
  2. Prova <<: [*A, *B] e poi <<: [*B, *A] con una chiave in comune: constata di persona che vince la prima.
  3. Leggi lo stesso file con PyYAML e con js-yaml e confronta il risultato del merge.
  4. Prova a usare un anchor definito in un file dentro un altro file incluso: guarda l’errore, così non ci riproverai fra sei mesi.