Sign in
← Kubernetes: basics
4 min · intro · Lesson 1 of 11

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.

Form Name John Smith Languages • English • Spanish Address Country: Canada City: Toronto YAML name: John Smith languages: ··- English ··- Spanish address: ··country: Canada ··city: Toronto

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.

  1. kubectl parses the YAML on your machine. If the syntax itself is broken, the response starts with error converting YAML to JSON and includes a line number. That number is sometimes one or two lines below the real spot.
  2. 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 example spec.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

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

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:

Done reading?

Mark the lesson as read to move your track progress forward.

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.

Get access for $49

Tell the agent: use MCP izi, give me lesson k8s-basics-01-yaml