YAML for Kubernetes
The minimum syntax you need to read manifests.
Everything you hand to the cluster is written in YAML: Pod, ConfigMap,
Service, and the rest of the objects. A manifest is a YAML file describing a
Kubernetes object. Almost every "weird" kubectl apply failure in your
first weeks turns out not to be a kubectl bug, but YAML that parsed
differently than you expected. This lesson covers the minimum syntax you
need to read manifests, and five mistakes almost everyone makes.
A form for a machine
Picture a paper form: name at the top, then a list of languages, then an "address" sub-form with its own fields. YAML works the same way. It's a form, except a parser reads it instead of a person.
A field on the left, a value on the right, a colon and a space between
them. The familiar "Name: John" becomes name: John. Almost the whole Pod
manifest is made of pairs like this.
Maps, lists, nesting
The line name: John is called a map, or dictionary: a key, a colon, a
space, a value. The whole manifest is built from pairs like this; one line
equals one form field.
A list of languages works differently. On the form those are checkboxes; in YAML, instead of checkboxes, there's a dash and a space, one item per line:
languages:
- English
- Spanish
This is a list. The "address" sub-form is nested inside its parent through indentation:
address:
country: Canada
city: Toronto
Two spaces of indentation tell the parser these fields belong to the address. YAML doesn't fix the number of spaces: two or four both work. What matters is that the indentation is the same within one block. In Kubernetes the convention is two.
A list item can itself be a map. That's how containers are described in a Pod:
containers:
- name: nginx
image: nginx:1.27.3-alpine
The dash opens a new item, and name and image are fields of the same
item, so they line up directly under each other.
Comments and multiple objects in one file
A comment starts with # and runs to the end of the line. The parser
skips it:
# the whole line is a comment
name: web # a trailing comment works too
In Kubernetes, several objects often live in one file. They're separated
by a line of three dashes, ---, and each piece is called a document:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
---
apiVersion: v1
kind: Service
metadata:
name: app-svc
kubectl apply -f all.yaml sends both objects to the cluster in one run.
But it sends them one at a time. If the server rejects one document, the
rest still get applied, and the cluster ends up with half the file. That's
why you read the whole output of kubectl apply, not just the last line.
How kubectl reads the file
Errors in a manifest get caught at two stages, and the text of the message tells you which one.
- kubectl parses the YAML on your machine. If the syntax itself is broken,
the response starts with
error converting YAML to JSONand includes a line number. That number is sometimes one or two lines below the real spot. - The Kubernetes server (the API server) maps the values onto the
object's fields. If a field has the wrong type or doesn't exist, the
response starts with
Error from server (BadRequest)and names the field by its path, for examplespec.containers[0].containerPort.
kubectl does some type checks itself, before it even sends the request.
Then the message starts with error: unable to decode, with the same
cannot unmarshal in the text as the server uses.
Five mistakes
1. Tabs instead of spaces
# wrong: a tab before containers (drawn here as an arrow)
spec:
→containers:
# right: two spaces
spec:
containers:
YAML forbids tabs in indentation, so kubectl fails right at parsing:
error: error parsing pod.yaml: error converting YAML to JSON: yaml: line 6: found character that cannot start any token
A tab often sneaks in from a browser copy-paste, or from an editor that
inserts a tab character on the Tab key. You can't tell it apart from
spaces by eye. The command cat -et pod.yaml will show it: a tab looks
like ^I.
2. no, yes, numbers without quotes
Kubernetes parses YAML by the old YAML 1.1 rules. There, yes, no, on,
off, y (and NO in caps too) mean booleans true and false, and 8080
and 1.10 are numbers. Fields like a ConfigMap's data expect strings,
and the server won't accept a file like this. A ConfigMap stores app
settings as key-string pairs; the course covers it in detail later:
data:
LOG_JSON: yes
Error from server (BadRequest): error when creating "cm.yaml": ConfigMap in version "v1" cannot be handled as a ConfigMap: json: cannot unmarshal bool into Go struct field ConfigMap.data of type string
Fix: quote the value, LOG_JSON: "yes". In Kubernetes this kind of mistake
is at least loud. Tools that parse YAML themselves and pass the values
along fail silently. For example, PyYAML (Python's YAML library) turns
no into False, and 1.10 into 1.1. So the habit of quoting anything
that's meant to be a string saves you everywhere: versions, image tags,
yes/no, ConfigMap values.
3. Mixed indentation
# wrong: ports is at the level of spec, not inside the container
spec:
containers:
- name: web
image: nginx:1.27.3-alpine
ports:
- containerPort: 80
# right: ports is shifted together with the container's fields
spec:
containers:
- name: web
image: nginx:1.27.3-alpine
ports:
- containerPort: 80
The parser only looks at the column a line starts in. Shift a field by a couple of spaces and it ends up in a different object. The file is syntactically valid, so this time it's the server that complains:
Error from server (BadRequest): error when creating "pod.yaml": Pod in version "v1" cannot be handled as a Pod: strict decoding error: unknown field "spec.ports"
If you see unknown field, compare the path in the message with where the
field should be, and fix the indentation.
4. Leading zero
YAML reads an integer with a leading zero as octal: 0644 equals decimal
420 to the parser. For file permissions that's exactly what you want, so
in manifests a file mode is written as a number without quotes:
defaultMode: 0644
Quotes break this completely. The defaultMode field is declared as an
integer in the API, and the API server won't accept a string:
Error from server (BadRequest): error when creating "pod.yaml": Pod in version "v1" cannot be handled as a Pod: json: cannot unmarshal string into Go struct field ConfigMapVolumeSource.spec.volumes.VolumeSource.configMap.defaultMode of type int32
It's more dangerous to forget the zero itself. defaultMode: 400 goes
through with no errors, but that's decimal 400, which is octal 0620. The
file gets permissions rw--w---- instead of the intended r--------.
5. No space after the colon
# wrong: the key and value are stuck together
metadata:
name:web
# right: the space is required
metadata:
name: web
Without the space, name:web is no longer a pair to YAML, just one string
of eight characters. What happens next depends on the neighboring lines. If
more fields follow below, parsing fails with
mapping values are not allowed in this context or
could not find expected ':', and the line number points below name, not
at it. If it's the only line in the block,
YAML parses it fine, but the parent gets a string instead of a map, and
kubectl answers with cannot unmarshal string into Go struct field. Either
message is a reason to check the spaces after your colons.
Minimal Pod manifest
A Pod is the smallest unit Kubernetes runs: one or more containers sharing a network.
apiVersion: v1
kind: Pod
metadata:
name: web
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27.3-alpine
apiVersion: the API version kubectl sends the object under, "which form template this is." For a Pod it'sv1.kind: the object type (Pod,ConfigMap,Service), case matters.metadata: bookkeeping fields,nameis required,labelsis optional.spec: the actual request, what you're asking the cluster for.
Check yourself: without looking back at the manifest, what's the indent of
containers relative to spec, and why does image sit directly under
name, not under the dash? Answer: two spaces; image and name are
fields of the same list item.
Common mistakes
- Trying to fix everything at once. kubectl stops at the first error it finds, so fix them one at a time: read, find the line, fix it, apply again.
- Looking for the error on the exact line from the message. YAML parsing often points below the real spot.
- Seeing
createdin theapplyoutput and assuming the whole file went through. With multiple documents, one error doesn't cancel the rest. - Quoting a numeric field like
defaultMode"just in case": the server will reject a string where it expects a number.
What you'll need for the tasks
Two labs on one local kind cluster: fix a Pod manifest that kubectl won't accept, and find out why a Pod is waiting on a ConfigMap that's sitting in the same file. Commands:
kubectl apply -f <file>: send the manifest to the cluster;kubectl get pod <name>andkubectl get pod <name> --show-labels;kubectl describe pod <name>: theEventssection at the end;kubectl logs <name>: what the container printed;kubectl get configmap <name> -o yaml;kubectl delete pod <name>;cat -et <file>: show tabs (^I) and line endings ($).
Done reading?
Mark the lesson as read to move your track progress forward.
Lesson marked as done.
Go through it with an AI agent
The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.
Progress saved in this browser
Clear your history or open this on your phone, and the marks will be gone. Leave your email and they'll move to your account. No password needed.
Done. moved to your account. You can see your progress on any device. My progress