A Default Is a Decision Nobody Made
You did configure it. You just didn’t choose.
There’s no such thing as an unconfigured system. If you didn’t set worker_processes, nginx set it. If you didn’t declare resources, Kubernetes decided what that means. If you didn’t declare a browser floor, your bundler picked one. The value exists either way; the only question is whether a human in your organization ever looked at it.
Most defaults are harmless. The dangerous ones share a trait: they read their environment and guess, and in a container the environment they read is not the one you’re paying for.
nginx sized itself to the node
worker_processes auto means “one worker per CPU core.” Inside a container, nginx reads the core count of the node — not the cgroup limit, not the pod’s request. Our pods requested 50–100 millicores of CPU and were running 80 to 97 nginx workers, one per physical core on whichever node they landed on.
The memory cost was real, but the more interesting finding was what it did to measurement. The same Deployment, with the same code, used about 393 MiB on an 80-core node and about 472 MiB on a 96-core one. A ~20% spread with zero code difference means every percentile you take across pods is measuring node placement mixed in with behavior — and we were about to use those percentiles to right-size the whole cluster.
The fix is one line. The lesson is that it took a cluster-wide sizing audit to notice it.
Thirteen months of BestEffort
Almost every production Deployment in one monorepo had resources: {} from the day it was created. Empty braces look like “no opinion.” To Kubernetes they mean QoS class BestEffort — first to be evicted when a node gets memory pressure.
Thirteen production services ran like that, the oldest for thirteen months, while every lint, test and review gate stayed green. Nothing was wrong with the YAML. It parsed, it applied, it ran. The default simply encoded a priority — “this workload is expendable” — that nobody would have agreed to if asked.
What fixed it wasn’t documentation. It was a gate that requires a resource declaration to exist on every container, including init containers. The gate doesn’t pick the numbers (those belong to whoever owns capacity); it just refuses to let “nobody decided” ship.
The same shape, everywhere
Once I started listing them, the pattern kept turning up:
- A model-inference runtime sizes its thread pool off the node’s core count and ignores the cgroup CPU limit. Put a CPU limit on it and it doesn’t use fewer threads — it gets throttled. We documented the absence of a CPU limit in the manifest so nobody “fixes” it.
vitestwith no arguments runs in watch mode. Ourtest:unitscript was justvitest.npm testappended--run, so CI was fine; anyone runningtest:unitdirectly got a process that never exited. One was found orphaned for 13 days, reparented to init, holding a headless browser pool.- A default error code of
0. An exception class defaulted$code = 0, and0was also the enum value for valid. An unset code on a failure read, downstream, as success. - Two tools, two browser floors. The bundler’s default build target and the CSS framework’s default target disagreed. The effective minimum browser was whichever was higher — a support policy nobody chose, which would move silently on the next upgrade of either package. Declaring the floor explicitly (and gating new code against APIs that floor doesn’t have) turned up fullscreen silently doing nothing on iPhone.
How to find yours
Look for defaults that read the host. Core counts, memory size, hostname, locale, timezone, isatty. In a container, almost all of these describe the wrong machine. auto is a word worth grepping for.
Look for “empty means something.” {}, null, 0, "". If an empty value is a valid input, find out what it means — it’s often the most permissive or the least protected option, because that’s the one that never errors.
Pin what you measure against. If a tool’s behavior depends on a version, a target or a platform, declare it. Our static analyzer gave 0 errors analyzing as PHP 8.4 and 9 errors as 8.5 — same code, same rules — because it was inferring the target from whatever PHP happened to be on the machine running it.
Write the declaration even when it matches the default. worker_processes 2; and test:watch as an explicit opt-in aren’t about the value. They’re a record that someone looked.
The principle
Every default is a decision made by someone who has never seen your system. You don’t need to override all of them. You need to know which ones are making choices about your hardware, your priority and your users — and make those choices yourself, on purpose, where the next person can see them.