YAML Is the New Black
Open any infrastructure repository from the last five years and count
the file extensions. I did this recently on a platform I inherited, and
the answer was bleak in a familiar way: a handful of Go files, some
Dockerfiles, a couple of shell scripts, and then a sprawling continent
of .yaml. Cluster manifests. Helm values. CI pipelines. Argo
Applications. Alerting rules. Service definitions. Policy. The actual
program was a rounding error. The system was YAML.
YAML is the new black. It is the thing everyone is wearing this season and the thing nobody will admit they chose on purpose. It became the default interface for modern operations the way black became the safe choice in a wardrobe — not because it is the best for every occasion, but because it goes with everything, never looks wrong in a review, and nobody gets fired for picking it. I want to be fair about this up front, because the topic attracts a lazy reading: YAML is not evil. YAML is genuinely useful. The problem is not that we use YAML. The problem is what we decided YAML should be.
Why it won, honestly
YAML won for good reasons, and pretending otherwise is just snobbery with a linter attached. It is portable — every language parses it, every tool emits it, it travels across stacks without a runtime. It is diffable, which sounds minor until you have tried to review a change to a binary config or a database row and realized you cannot. It is readable enough that a human can open a file they have never seen and roughly guess what it does. And it is boring in the right ways: no execution model to reason about, no build step, no surprises about when the file runs because it does not run at all. It just sits there describing something.
That last property is the one that mattered most, because it made YAML fit Git like a key fits a lock. The moment your desired state is plain text, Git becomes the natural home for it: history, blame, branches, pull requests, review. Git made YAML feel like the obvious interface for infrastructure change, and that feeling was not wrong. Reviewing a change as a diff against a text file is a real improvement over clicking through a console and hoping you remember what you touched. YAML rode into the center of operations on the back of version control, and it earned the seat.
Kubernetes made it the surface
Then Kubernetes happened, and YAML stopped being one option among many and became the operational surface area. You do not talk to a cluster. You write a manifest and the cluster reconciles toward it. Deployments, Services, ConfigMaps, CRDs, RBAC — all of it is YAML you apply and the control plane chases. This is a genuinely elegant idea, declarative state and a loop that makes reality match it, and I have defended it at length in the Argo CD post. But the side effect is that an entire generation of engineers learned that operating a system means editing YAML. The interface to the most complex distributed system most of us will ever run is a text file with significant whitespace.
And everyone can read it. That is the trap. YAML is legible enough that
anyone can open a manifest and feel like they understand it — and just
opaque enough that they are usually wrong about the part that matters.
The indentation that silently reparents a block. The string no that
becomes a boolean. The anchor three hundred lines up that changes what
this value resolves to. YAML became the new black precisely because
everyone can read it just enough to be dangerous, which is a very
different thing from understanding it.
CI/CD smuggled in a programming language
Watch where this goes once YAML is the accepted interface for
everything. CI/CD is the clearest case. A pipeline started as a list of
steps — build, test, deploy, done. Reasonable. Then someone needed a
conditional. Then a loop over a matrix. Then variable interpolation,
then expressions, then templated includes, then a way to call one
pipeline from another. Build logic quietly turned into a programming
language, except the language is YAML with a homegrown expression syntax
bolted on, no type system, no debugger, no local execution, and a
feedback loop that runs on a remote server and takes four minutes to
tell you that you mis-indented an if.
Nobody decided to write programs in YAML. It happened one reasonable feature at a time, because the file was already there and adding a key felt cheaper than admitting we needed a real interface. We did not choose this language. We accreted it.
When YAML isn’t enough, we add a layer
The most telling move our industry makes is what we do when YAML stops being expressive enough. We almost never step back and design a better interface. We add a layer that generates YAML. Helm is the canonical example, and I say this as someone who runs it and recently wrote about how Helm 4 finally won me back: Helm templates are Go templates that render text that happens to be YAML. We took a format whose entire appeal was that it was data, not code, and wrapped it in a templating language so that we could treat it as code again — except now a misplaced indent in a template produces invalid YAML that the cluster cheerfully rejects, and you are debugging whitespace through two layers of abstraction.
This is the pattern, and it is worth naming because we keep repeating
it. The interface is too rigid, so instead of fixing the interface we
build a machine that produces the interface. Kustomize patches it. ytt
treats it as data. Jsonnet compiles to it. Every one of these is a
reasonable response to a real limitation, and every one is also evidence
that YAML was asked to be something it is not. When you need a compiler
to write your config, the config was never really the interface. It was
always the artifact.
GitOps got the model right
Here is where I want to be generous, because GitOps takes everything
good about YAML and makes it genuinely powerful. When your desired state
lives as YAML in Git and a reconciler enforces it, you get properties
that are hard to overstate: the change is reviewable before it lands,
diffable against what is running, auditable forever, and reversible with
a git revert. At 3 a.m. that is not a fashion statement, it is the
difference between a four-minute recovery and a four-hour one. The YAML
is doing exactly what YAML is best at — being a flat, honest,
version-controlled description of what should be true.
But notice the sleight of hand we perform on top of that. “Just open a PR against the YAML” gets sold as self-service, and sometimes it is. Far more often it is product design outsourced to whoever needed the change. We hand an application developer a 200-line values file, a CODEOWNERS gate, and a mandatory review, and call it a platform. It is not a platform. It is a form with no validation, a UI with no affordances, and an error message that arrives at deploy time. We took the work of designing a good interface and pushed it onto the person least equipped to absorb a mistake, then congratulated ourselves on being declarative.
Make YAML the artifact, not the interface
So no, the answer is not to ban YAML, and anyone selling you a YAML-killer is selling you the next layer to debug. YAML is a good serialization format. The mistake was promoting a serialization format to a product interface and then acting surprised when humans struggle with it.
The fix is to put YAML back where it belongs: as the artifact, not always the primary thing a human touches. Generate it from something with a type system and a name for each field. Validate it against a schema before it ever reaches a cluster, so the error shows up in an editor and not in an incident. Preview the rendered result and the diff against live state before anyone approves it. Explain what a change will actually do in terms an on-call engineer can read at 3 a.m. Test it the way you test code, because by now it is code. And keep it versioned in Git, because that part we got exactly right.
YAML is the new black, and like everything that becomes the default, it stopped being a choice and started being the air. That is fine for a storage format. It is a quiet disaster for an interface. The mature move is not to throw out the wardrobe. It is to stop pretending the thing underneath everything is also the thing people should be wearing to work.