Skip to content
OpsAtlas

Why Kubernetes Rejects value: yes (YAML 1.1 vs 1.2)

By OpsAtlas · · 3 min read

Table of contents

You add a feature flag to a Deployment, run kubectl apply, and get this:

json: cannot unmarshal bool into Go struct field EnvVar.spec.template.spec.containers.env.value of type string

The manifest looks harmless:

env:
  - name: FEATURE_SEARCH
    value: yes

Nothing is wrong with the indentation, and the key is spelled correctly. The problem is that yes stopped being a string before Kubernetes ever saw it.

Two YAMLs in one ecosystem

YAML has two versions in common use, and they disagree about plain (unquoted) scalars:

  • YAML 1.1 (2005) treats yes, no, on, off, y, n and their capitalised forms as booleans. It reads 0755 as octal and 22:22 as a base-60 number.
  • YAML 1.2 (2009) keeps only true and false as booleans. yes is a string, 0755 is the decimal 755, and 22:22 is a string.

Kubernetes converts YAML to JSON before decoding it, and that conversion has historically followed 1.1 rules for booleans. So value: yes arrives as "value": true. EnvVar.value must be a string, so the API server rejects the request. The fix is to quote it:

    value: "yes"

The same rule applies to every field that must be a string: env values, annotations, labels and ConfigMap data. A ConfigMap entry like ENABLED: on fails the same way.

Three more files that bite

GitHub Actions: the on: key

on:
  push:
    branches: [main]

GitHub’s own parser reads this correctly. But a Python script that loads the workflow with PyYAML, a YAML 1.1 parser, gets a dictionary whose key is the boolean True, not the string "on":

>>> yaml.safe_load(open(".github/workflows/ci.yml"))
{True: {'push': {'branches': ['main']}}, ...}

Linters, workflow generators and audit scripts written in Python run into this. They then report “no triggers defined” or crash with a KeyError: 'on'.

Docker Compose: ports below 60

The Compose documentation has long recommended writing port mappings as strings, and this is why:

ports:
  - 22:22      # a 1.1 parser reads this as 1342 (22 × 60 + 22)
  - "22:22"    # always a string

Current Compose versions are stricter about this, but the file may also be read by other tools: linters, IDE plugins, scripts. Quoting costs nothing.

Ansible and anything Python-based

Ansible uses PyYAML. mode: 0644 without quotes is read as the octal number 420, which happens to be what you want. mode: 644 is the decimal number 644, which is a very different permission. Quote file modes as strings (mode: "0644") and the meaning cannot drift.

See what the parser saw

The fastest way to debug any of these is to stop looking at the YAML and look at the data it produces. Converting to JSON shows every type explicitly: a quoted "yes" and a bare true look different, and a key of true is obvious.

Paste the file into the YAML ⇄ JSON converter. It parses with YAML 1.2 rules, and it also lists every value that a 1.1 parser would read differently, with its line number. That warning list doubles as a checklist of what to quote. Since it runs in the browser, you can paste internal manifests without uploading them anywhere.

To see the YAML 1.1 behaviour on the command line, use PyYAML directly:

python3 -c 'import sys, yaml, json; print(json.dumps(yaml.safe_load(sys.stdin), default=str, indent=2))' < deploy.yaml

If that output differs from the converter’s output, you have found a value that depends on which parser reads the file.

Rules that prevent all of this

  1. Quote every string that could look like something else. That covers yes/no/on/off, version numbers ("1.10"), anything with a colon, IDs with leading zeros and file modes.
  2. Never rely on a bare boolean where the schema wants a string. Kubernetes env values, labels and annotations are always strings.
  3. Lint in CI. yamllint with the truthy rule enabled flags yes, on and the rest before they merge.
  4. Validate the whole manifest, not just the syntax. Once the YAML parses, run it through the Kubernetes YAML validator to catch the problems that come next: selectors that match nothing, missing Secrets, and insecure defaults.

YAML is a convenient format to write, but parsers fill in the types and they don’t all agree. Quote anything that could be read two ways.

Try it yourself

  • YAML ⇄ JSON converter

    Convert YAML to JSON and back with exact error positions, anchor and merge-key expansion, and warnings for YAML 1.1 gotchas like the Norway problem.

    Kubernetes
  • Kubernetes YAML validator and visualizer

    Paste Kubernetes manifests to get a resource diagram and a security and best-practice review: privileged pods, root, probes, limits, deprecated APIs.

    KubernetesBeta