A cura del team DevOps di Codebaker
Con Kubernetes 1.37 “Garhwal”, rilasciato il 26 agosto 2026, KYAML è diventato Stable. È una delle novità più citate della release e anche una delle più fraintese: la si sente descrivere come “il nuovo formato che sostituisce YAML”, e non lo è.
Abbiamo diviso l'articolo in due parti.
- Parte 1, Primi passi. Per chi si sta avvicinando a Kubernetes: cos'è un manifest, perché YAML crea problemi e come KYAML li evita.
- Parte 2, Approfondimento. Per chi gestisce cluster da anni: regole di rendering, pipeline di serializzazione, commenti, stringhe multilinea, Helm, Kustomize, GitOps, CI e strategia di adozione.
Le due parti sono indipendenti: chi ha già esperienza può saltare direttamente alla seconda.
Parte 1: Primi passi con KYAML
Cos'è un manifest Kubernetes
In Kubernetes non si dice al cluster cosa fare passo per passo. Si descrive lo stato desiderato: “voglio tre copie di questa applicazione, raggiungibili su questa porta, con questa quantità di memoria”. Questa descrizione è un manifest, un file di testo che si invia al cluster con un comando come:
kubectl apply -f mia-applicazione.yamlDa quel momento Kubernetes lavora di continuo per far coincidere la realtà con quanto scritto nel file.
Il formato storicamente usato per questi file è YAML, un linguaggio pensato per essere leggibile da una persona. Ecco un manifest minimo che crea un Pod, cioè l'unità più piccola che Kubernetes esegue:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
labels:
app: demo
spec:
containers:
- name: nginx
image: nginx:1.20Le regole di base sono tre:
chiave: valoredefinisce una proprietà;- l'indentazione (gli spazi a inizio riga) indica cosa sta dentro cosa:
nameelabelsstanno dentrometadata; - il trattino
-indica un elemento di una lista:containersè una lista che contiene un solo container.
Dove YAML ti tradisce
YAML è comodo, ma lascia molta libertà. Chi inizia con Kubernetes finisce quasi sempre per inciampare negli stessi tre problemi.
Problema 1: due spazi di troppo cambiano il significato
Nel YAML tradizionale la struttura dipende solo dagli spazi. Guarda questo esempio:
spec:
containers:
- name: app
image: registry.example.com/app:1.4
resources:
limits:
memory: 512MiVolevamo limitare la memoria del container, ma resources è indentato allo stesso livello di containers e quindi finisce dentro spec, non dentro il container. Il file è sintatticamente corretto: nessun errore di “formato sbagliato”. Il limite di memoria, semplicemente, non viene applicato dove pensavi. In questo caso fortunato kubectl segnalerà un campo sconosciuto; in altri casi l'errore passa del tutto inosservato.
Problema 2: il testo che diventa altro
In YAML le virgolette attorno ai testi sono facoltative. Il parser prova a indovinare il tipo del valore, e a volte sbaglia rispetto alle tue intenzioni:
country: NO # letto come "falso", non come il codice della Norvegia
feature: on # letto come "vero"
version: 1.20 # letto come il numero 1.2: lo zero finale sparisceIl primo caso è talmente famoso da avere un nome: il “Norway problem”.
Problema 3: JSON non è la via di fuga
Kubernetes accetta anche JSON, che non ha nessuno di questi problemi. Però JSON non ammette commenti, pretende le virgolette su ogni chiave e va in errore se lasci una virgola dopo l'ultimo elemento. Per file che devi scrivere e leggere a mano è scomodo.
KYAML: YAML con regole più severe
KYAML (Kubernetes YAML) non è un nuovo linguaggio. È YAML scritto seguendo sempre le stesse regole:
- gli oggetti stanno sempre tra parentesi graffe
{ }; - le liste stanno sempre tra parentesi quadre
[ ]; - i valori di testo stanno sempre tra virgolette doppie
" "; - ogni elemento termina con una virgola, anche l'ultimo;
- il file inizia con
---, un marcatore standard di YAML che indica l'inizio di un documento.
Lo stesso Pod di prima, in KYAML:
---
{
apiVersion: "v1",
kind: "Pod",
metadata: {
name: "my-pod",
labels: {
app: "demo",
},
},
spec: {
containers: [{
name: "nginx",
image: "nginx:1.20",
}],
},
}Cosa cambia in pratica:
- gli spazi non contano più per la struttura. Sono le parentesi a dire cosa sta dentro cosa. L'indentazione resta per leggibilità, ma se sbagli uno spazio il significato non cambia;
- il testo resta testo.
"NO"tra virgolette è la stringa NO, punto; - puoi ancora commentare, a differenza di JSON.
Se ti sembra JSON, hai ragione: è molto simile. Del resto, tecnicamente, un file JSON è già YAML valido. KYAML sta a metà strada: più rigoroso del YAML classico, più comodo di JSON.
Le domande che ci fanno più spesso
Devo rinominare i file in .kyaml?
No. I file restano .yaml. Non esiste un'estensione dedicata.
Devo convertire i miei manifest esistenti?
No. KYAML è YAML, e il YAML tradizionale continua a funzionare esattamente come prima. Il progetto Kubernetes non ha piani per renderlo il formato predefinito.
Il cluster deve essere aggiornato alla 1.37 per leggere KYAML?
No. KYAML riguarda solo il lato client. Qualsiasi versione di kubectl e qualsiasi cluster leggono un file KYAML, perché è YAML valido. Serve una versione recente di kubectl solo per generare KYAML.
Allora cosa è cambiato con la 1.37?
Il comando kubectl ... -o kyaml è ora stabile e sempre disponibile, senza impostazioni particolari.
I primi comandi da provare
Vedere una risorsa esistente in KYAML:
kubectl get deployment my-app -o kyamlGenerare lo scheletro di un nuovo Deployment senza toccare il cluster, direttamente in KYAML, e salvarlo su file:
kubectl create deployment web --image=nginx:1.27 --replicas=2 \
--dry-run=client -o kyaml > web.yamlApplicarlo:
kubectl apply -f web.yamlSe preferisci vedere sempre KYAML quando usi kubectl get, puoi impostarlo come preferenza personale:
kubectl kuberc set --section defaults --command get --option output=kyamlLa preferenza vale solo per il tuo computer: non tocca il cluster né i colleghi.
In sintesi per chi inizia
| YAML tradizionale | KYAML | |
|---|---|---|
| Cosa definisce la struttura | Gli spazi | Le parentesi {} e [] |
| Virgolette sui testi | Facoltative | Obbligatorie |
| Rischio che NO diventi “falso” | Sì | No |
| Commenti | Sì | Sì |
| Estensione del file | .yaml | .yaml |
| Funziona con qualsiasi kubectl | Sì | Sì |
Il nostro consiglio: impara a leggere entrambi. Quasi tutta la documentazione e gli esempi online sono in YAML tradizionale, ma usare -o kyaml quando esplori un cluster ti abitua a vedere la struttura in modo esplicito ed evita una classe intera di errori.
Parte 2: Approfondimento per chi è in produzione
Questa parte dà per scontata la familiarità con kubectl, Helm, Kustomize e flussi GitOps. L'obiettivo è capire come KYAML è costruito, dove aiuta davvero e dove può creare attrito.
Inquadramento: cosa è e cosa non è
KYAML è definito dalla KEP-5295 (SIG CLI, autori Tim Hockin e Benjamin Elder), con milestone Alpha in 1.34, Beta in 1.35 e Stable in 1.37. Alcuni punti della KEP vanno tenuti presenti:
- è esclusivamente una questione client-side. Nessuna modifica all'API server, nessun supporto server-side diverso da quello già esistente per YAML;
- è una specifica di output.
kubectlaccetta come input qualunque YAML valido, KYAML incluso, ma non impone che l'input sia KYAML stretto; - tra i non-obiettivi dichiarati ci sono il cambio della libreria YAML di Kubernetes e l'obbligo di migrare;
- qualsiasi componente che usa
k8s.io/cli-runtime/pkg/printersegenericclioptionseredita il supporto a KYAML, quindi anche plugin e tool costruiti su cli-runtime.
Evoluzione del gate
| Versione | Stato | Comportamento |
|---|---|---|
| 1.34 | Alpha | -o kyaml disponibile solo con KUBECTL_KYAML=true |
| 1.35 | Beta | Abilitato di default, disattivabile con KUBECTL_KYAML=false |
| 1.36 | Beta | kubectl kuberc esce dal prefisso alpha |
| 1.37 | Stable | Variabile d'ambiente rimossa, -o kyaml sempre disponibile |
Se avete script che esportano KUBECTL_KYAML, con client 1.37 la variabile è semplicemente ignorata e potete eliminarla.
Perché YAML è ambiguo proprio nell'ecosistema Kubernetes
La libreria usata da Kubernetes è sigs.k8s.io/yaml, che dalla 1.33 include fork di go-yaml v2 e v3. Il percorso di decodifica di un manifest è, semplificando:
YAML ──(sigs.k8s.io/yaml)──▶ JSON ──(decoder strict)──▶ oggetto Go tipizzatoLa risoluzione implicita dei tipi segue le regole storiche di YAML 1.1. La KEP elenca esplicitamente i casi più insidiosi:
| Valore non quotato | Interpretato come |
|---|---|
NO, no, N, YES, yes, Y, On, Off | booleano |
_42, _4_2_ | numero |
11:00 | numero in base 60 |
1.20 | float 1.2 |
0755 | intero ottale |
Quando l'errore è rumoroso e quando è silenzioso
Sui campi tipizzati delle API core, la coercizione di solito fallisce in modo esplicito, perché il decoder strict rifiuta un booleano dove si aspetta una stringa:
env:
- name: PORT
value: 8080Questo produce un errore del tipo cannot unmarshal number into Go struct field EnvVar...value of type string. Fastidioso, ma visibile. Lo stesso accade con label e annotation, che sono map[string]string.
Il problema diventa silenzioso in tre situazioni tipiche:
- values di Helm.
country: NOinvalues.yamldiventafalseprima ancora che il template venga renderizzato. Se il template fa{{ .Values.country | quote }}il manifest finale conterrà"false", perfettamente valido e perfettamente sbagliato; - CRD con schema permissivo. Campi con
x-kubernetes-preserve-unknown-fields: trueo tipiobjectgenerici accettano qualsiasi tipo senza protestare; - payload annidati in stringhe. Configurazioni applicative incluse in una ConfigMap e poi parsate dall'applicazione con un parser YAML diverso, con regole diverse.
Il caso IntOrString
KYAML quota le stringhe, non tutto. I campi IntOrString come targetPort restano semanticamente diversi a seconda del tipo:
ports: [{
port: 80,
targetPort: 8080, # porta numerica
}, {
port: 443,
targetPort: "https", # porta nominata del container
}],Scrivere targetPort: "8080" non è equivalente a targetPort: 8080: la stringa viene trattata come nome di porta e fallisce la validazione. KYAML rende il tipo esplicito, ma non vi dispensa dal conoscerlo.
Le Quantity
Le resource.Quantity si serializzano sempre come stringa. Anche se scrivete cpu: 1, l'output KYAML di un oggetto letto dal cluster sarà cpu: "1". Non è una differenza semantica e non va “corretta”.
La pipeline di rendering
Per garantire la massima fedeltà a JSON, KYAML non serializza direttamente gli oggetti Go:
oggetto Go ──▶ JSON ──▶ AST (go-yaml v3) ──▶ KYAMLIl passaggio intermedio da JSON ha conseguenze precise.
I tag json governano tutto. omitempty, omitzero, rinomine dei campi: tutto è rispettato perché passa dal marshaller JSON. I tag yaml sono ignorati, come già avviene con -o yaml. I tipi che implementano json.Marshaler vengono resi tramite quello; MarshalYAML() non viene usato.
Le chiavi delle mappe devono essere stringhe. YAML ammette chiavi composte (liste, mappe) o non stringa; JSON no. Un YAML con chiavi di questo tipo non è convertibile in KYAML e la conversione fallisce. In altre parole: ogni KYAML è YAML, ma non ogni YAML può diventare KYAML.
Le chiavi delle mappe sono ordinate, e l'ordine dei campi delle struct non è garantito. kubectl converte tutto in unstructured internamente e perde l'ordine di dichiarazione. Chi confronta output testuali deve tenerne conto.
Anchor, alias e tag vengono appiattiti. Anchor e alias vengono materializzati, i tag espliciti (!!str, !!int) e globali (%TAG) possono essere semplificati, a condizione che l'oggetto risultante sia identico. Se usate anchor per deduplicare blocchi in file scritti a mano, la conversione li espande.
Puntatori e interfacce sono resi come null o come il valore effettivo. Tipi non gestiti producono un errore esplicito invece di un output ambiguo.
Regole di formattazione nel dettaglio
Scalari
- interi e float nella loro rappresentazione numerica naturale;
- booleani come
true/false; - stringhe sempre tra doppi apici, con escape.
Chiavi
Le chiavi non sono quotate, salvo quando non sono “ovviamente stringhe” o coincidono con parole ambigue come no. Le chiavi di label con prefisso (app.kubernetes.io/name) rientrano nei casi gestiti. La KEP non garantisce uniformità: nello stesso oggetto possono convivere chiavi quotate e non quotate.
labels: {
app: "hostnames",
"kubernetes.io/service-name": "hostnames",
pod-template-hash: "77b655d8d",
},Virgole e parentesi
- ogni lista, mappa e struct ha sempre la virgola finale dopo l'ultimo elemento, tranne quando la parentesi di chiusura è “accoppiata”;
- le parentesi adiacenti sono accoppiate (cuddled) per risparmiare spazio verticale:
[{ ... }, { ... }]; - l'accoppiamento si interrompe quando un elemento ha un commento di testa o il precedente ha un commento di coda;
- liste e mappe vuote sono
[]e{}; - nessun tentativo di mettere su una riga sola strutture brevi, nessun allineamento dei valori;
- indentazione a due spazi.
L'intestazione ---
Serve a distinguere KYAML da un JSON malformato, dato che entrambi iniziano con {. Secondo la KEP, i client dalla 1.33 in poi gestiscono KYAML anche senza intestazione; le versioni precedenti ne hanno bisogno. Se avete ambienti con kubectl datati in pipeline, non togliete l'intestazione.
Documenti multipli
Un multi-documento KYAML è un normale multi-documento YAML: più blocchi { ... } separati da ---.
Stringhe multilinea
Il flow style di YAML non ammette i blocchi letterali | e >. KYAML usa quindi stringhe tra doppi apici con flow folding (backslash a fine riga, che elimina il ritorno a capo e gli spazi iniziali della riga successiva), \n espliciti per i veri a capo e \ per preservare gli spazi iniziali.
Una ConfigMap in YAML tradizionale:
data:
nginx.conf: |
server {
listen 80;
location / {
return 200 'ok';
}
}Lo stesso contenuto nella resa KYAML:
data: {
"nginx.conf": "\
server {\n\
\ listen 80;\n\
\ location / {\n\
\ return 200 'ok';\n\
\ }\n\
}\n\
",
},Qualche osservazione:
- lo spazio in più sulle righe senza indentazione serve solo all'allineamento visivo; YAML scarta comunque gli spazi iniziali dopo un folding;
- il contenuto è identico byte per byte a quello del blocco letterale;
- la leggibilità peggiora sensibilmente. Per ConfigMap con file di configurazione lunghi, script o certificati, il block style resta più pratico. È uno dei casi in cui ha senso tenere il YAML tradizionale.
Commenti: il punto debole
KYAML ammette commenti, ma la loro conservazione durante la conversione dipende da go-yaml, che ha limiti noti nella gestione dei commenti. La KEP lo dichiara apertamente:
- alcuni commenti possono finire in posizioni diverse o andare persi;
- il commento di riga di una mappa, di una lista o di una struct viene spostato dopo la parentesi di chiusura, perché è l'unica posizione in cui go-yaml lo ritrova in rilettura;
- senza righe vuote, un commento “di coda” di un elemento è indistinguibile dal commento “di testa” del successivo.
Conseguenza pratica: prima di convertire un repository, isolate i file con commenti significativi (spiegazioni di tuning, workaround, riferimenti a ticket) e verificateli a mano dopo la conversione.
Stabilità dell'output
La KEP garantisce idempotenza a parità di versione: l'output del formatter, ripassato allo stesso formatter, non cambia. Non garantisce stabilità byte per byte tra versioni diverse: la resa potrà essere affinata in futuro.
Implicazioni:
- non scrivete test che confrontano byte per byte l'output di
-o kyamlcon un golden file, a meno di fissare la versione del client; - nei confronti usate il parsing, non il testo (vedi sotto);
- fissate la versione del formatter in CI, come fate già per linter e compilatori.
KYAML e Helm
Qui KYAML dà il vantaggio più concreto, ma con un'avvertenza importante.
Il vantaggio: niente più nindent
In block style, iniettare un blocco richiede di gestire l'indentazione dal template:
spec:
template:
spec:
containers:
- name: app
resources:
{{- toYaml .Values.resources | nindent 12 }}Poiché JSON è YAML flow valido, in un template KYAML si può iniettare direttamente l'output di toJson, senza preoccuparsi degli spazi:
spec: {
template: {
spec: {
containers: [{
name: "app",
image: {{ printf "%s:%s" .Values.image.repository .Values.image.tag | toJson }},
resources: {{ toJson .Values.resources }},
}],
},
},
},Due dettagli:
toJsonsu una stringa produce un valore tra doppi apici con escape JSON, compatibile con le stringhe YAML double-quoted. È più robusto diquotequando i valori possono contenere caratteri speciali;- le virgole finali ammesse semplificano i cicli
range: non serve più gestire l'ultimo elemento a parte.
env: [
{{- range $k, $v := .Values.env }}
{ name: {{ $k | toJson }}, value: {{ $v | toJson }} },
{{- end }}
],L'avvertenza: non mescolate gli stili
La KEP lo dice esplicitamente: patchare testualmente KYAML con frammenti block style, come succede in un chart che include helper esistenti, non funziona in modo affidabile. Un chart va scritto interamente in uno stile o nell'altro. Includere helper di terze parti che emettono block style dentro un template KYAML è la ricetta per errori di parsing difficili da leggere.
E ricordate che KYAML non protegge i values: il Norway problem colpisce values.yaml prima del rendering. Se il chart accetta stringhe ambigue, quotatele nei values o validatele con values.schema.json.
KYAML e Kustomize
kustomize build e kubectl kustomize producono block style. Kustomize legge senza problemi base e patch in KYAML, ma l'output resta tradizionale. Se volete l'output finale in KYAML, ad esempio per archiviare i manifest renderizzati, serve un passaggio di formattazione successivo in pipeline.
Attenzione anche alle patch strategic merge o JSON 6902 scritte a mano: sono YAML e funzionano in qualsiasi stile, ma è bene mantenere uno stile coerente con la base per non confondere chi le rivede.
KYAML e GitOps
Argo CD e Flux confrontano oggetti, non testo: lo stato desiderato viene parsato prima del confronto con lo stato live. Convertire un manifest da block style a KYAML non genera drift né sincronizzazioni, purché l'oggetto risultante sia identico.
Genera però un diff Git enorme. Buone pratiche:
- fate la conversione in un commit dedicato, senza modifiche funzionali;
- aggiungete l'hash di quel commit a
.git-blame-ignore-revs, cosìgit blamecontinua a puntare alle modifiche reali; - verificate l'equivalenza prima del merge.
Verificare l'equivalenza
Offline, confrontando la forma parsata con yq (v4), con chiavi ordinate:
diff \
<(git show HEAD~1:deploy/app.yaml | yq -o=json 'sort_keys(..)') \
<(yq -o=json 'sort_keys(..)' deploy/app.yaml)Online, contro il cluster, con kubectl diff, che deve restituire output vuoto:
kubectl diff -f deploy/Il secondo controllo è più forte perché tiene conto anche di defaulting e validazione server-side.
Esportare risorse dal cluster
kubectl get -o kyaml restituisce l'oggetto live, con status, uid, resourceVersion, creationTimestamp e i campi popolati dai default. I managedFields sono nascosti per impostazione predefinita. Prima di versionare un oggetto esportato, rimuovete i campi runtime, a mano o con un plugin come kubectl-neat, e poi riformattate.
Per creare scheletri puliti, il percorso migliore resta --dry-run=client:
kubectl create service clusterip api --tcp=80:8080 \
--dry-run=client -o kyamlTooling di conversione
yamlfmt di sigs.k8s.io/yaml
È il tool del progetto Kubernetes, usato anche nel verifier CI del repository kubernetes/kubernetes:
go install sigs.k8s.io/yaml/yamlfmt@latestAccetta un file o una directory e scrive su stdout, quindi l'output va rediretto. La KEP prevede flag per scegliere tra stile “convenzionale” e KYAML, così che ogni file possa essere verificato rispetto allo stile previsto; consultate --help della versione installata per la sintassi esatta.
google/yamlfmt
Dalla versione 0.21.0 include un formatter dedicato a KYAML. È la scelta più comoda per pre-commit hook e controlli in CI, perché supporta già la modalità di verifica senza scrittura e la configurazione per percorso.
yq
Legge KYAML senza problemi, essendo YAML. Utile per tornare al block style quando serve (yq -P), per interrogare i file e per i confronti di equivalenza visti sopra.
Editor e schema
Il language server YAML degli editor più diffusi lavora sul documento parsato, quindi validazione tramite JSON Schema e autocompletamento continuano a funzionare anche sui file KYAML.
Compatibilità e version skew
| Scenario | Esito |
|---|---|
File KYAML applicato con qualsiasi kubectl | Funziona (è YAML) |
File KYAML senza --- con kubectl precedente alla 1.33 | Rischio di errata identificazione come JSON |
-o kyaml con client 1.34 senza KUBECTL_KYAML=true | Formato non disponibile |
-o kyaml con client 1.35 o 1.36 | Disponibile, disattivabile via variabile |
-o kyaml con client 1.37 | Sempre disponibile |
| Output KYAML confrontato byte per byte tra versioni diverse | Non garantito |
La KEP raccomanda di non usare -o kyaml nelle automazioni finché tutti i client coinvolti non sono aggiornati. Con la 1.37 GA il vincolo si riduce a verificare la versione del kubectl presente nelle immagini dei runner CI.
Limiti da mettere in conto
- Leggibilità delle stringhe multilinea, come visto sopra.
- Densità visiva. Su oggetti molto profondi, le parentesi di chiusura accumulate non aiutano la lettura quanto l'indentazione pura.
- Conoscenza diluita. Un ulteriore stile da insegnare al team, in un ecosistema dove il 99% degli esempi è in block style.
- Falso senso di sicurezza. KYAML elimina errori sintattici e di coercizione, non errori semantici: selector che non corrispondono, porte sbagliate, risorse sottodimensionate restano tutti possibili. Validazione con
kubeconformokubectl apply --dry-run=servere policy (Kyverno, ValidatingAdmissionPolicy) restano necessarie.
Strategia di adozione consigliata
Il nostro consiglio, per team che gestiscono più cluster e più repository:
- 1
Output prima di input. Adottate subito
-o kyamlin script e automazioni che leggono l'output dikubectl: è il caso d'uso a rischio zero e beneficio immediato. - 2
Nuovi repository e nuovi chart in KYAML, con formatter in modalità verifica nei pre-commit hook e in CI, versione del formatter fissata.
- 3
Repository esistenti: solo se c'è un motivo. Template Helm fragili sull'indentazione o storico di incidenti da coercizione giustificano la conversione. Altrimenti il costo in diff e revisione supera il beneficio.
- 4
Eccezioni esplicite. ConfigMap con file lunghi, manifest di terze parti vendorizzati e chart upstream restano in block style. Documentatelo nel README del repository, così nessuno li “corregge”.
- 5
Validazione indipendente dal formato, sempre: schema, dry-run server-side e policy.
KYAML non rende YAML obsoleto. Toglie di mezzo una categoria di errori che con Kubernetes non avremmo mai dovuto avere, senza chiedere di buttare via nulla di quello che già funziona. Adottarlo con giudizio, partendo da dove rende di più, è la scelta più sensata.
Luca Vitali
Riferimenti
- Kubernetes Blog, Kubernetes v1.37: Garhwal, 26 agosto 2026
- Kubernetes Blog, How to Pretty-Print Your Kubernetes YAML as KYAML and Why You'd Want To, 11 agosto 2026
- KEP-5295, Introducing KYAML, a safer, less ambiguous YAML subset / encoding, kubernetes/enhancements
- Documentazione Kubernetes: KYAML Reference, kuberc
sigs.k8s.io/yamlegoogle/yamlfmtsu GitHub
Vuoi un cluster Kubernetes gestito come si deve?
Progettiamo e gestiamo infrastrutture Kubernetes su cloud europeo e on-premise, con manifest puliti, GitOps, osservabilità e backup. Se vuoi parlarne con chi lo fa ogni giorno, scrivici.
Scopri Kubernetes & Docker