Anti-Patterns & Pitfalls
A platform doesn’t fail all at once — it fails one tempting shortcut at a time. Gizmo the Gremlin has a friendly-sounding pitch for every corner you could cut, and each one quietly drags the team back toward Ticket Swamp. This lesson is the field guide to the twelve traps that turn a would-be golden path into brand-new toil. For each one you get the whole story: Gizmo’s whisper, why it’s so tempting, why it hurts, and Timmy’s fix — so you can spot the trap coming and pave straight around it.
Imagine you’re building a smooth water slide so kids don’t have to climb the muddy hill anymore. A sneaky gremlin keeps whispering easy-sounding shortcuts: “skip the safety rails!”, “don’t build a little slide now — build one giant slide over two whole years!”, “just let everyone climb wherever they want!” Every shortcut sounds faster in the moment, but each one turns your shiny slide back into the muddy hill. This lesson is a list of the gremlin’s twelve favourite tricks — and how the slow, careful turtle beats each one.
Why anti-patterns matter more here
☺ Like you’re 10: Building a platform is meant to remove busywork. These mistakes secretly put the busywork back — sometimes with a prettier logo on top.
Every other lesson teaches what to do; this one teaches what not to do — and it earns its own page because platform anti-patterns are unusually seductive. They rarely look like mistakes. They look like being pragmatic, being in control, being fast, being like the famous companies. A platform’s whole job is to move an org from “file a ticket and wait” toward self-service flow, and each trap below quietly reverses that journey — slowly and in disguise, so a team can burn a year before noticing it has rebuilt Ticket Swamp with nicer paint.
When you suspect a trap, run these. The self-service test: can a developer’s request complete with zero humans in the loop? If a person has to action it, it’s ticket-ops, however pretty the front end. The pull test: are teams choosing the platform because it’s genuinely the easiest path — or only because someone decreed it? A golden path is chosen, not mandated. Almost every anti-pattern below fails one of these two tests.
Family one · Building a platform nobody wanted
☺ Like you’re 10: These are the mistakes where you build a fancy toy without ever asking the kid what she actually wanted to play with.
The first family of traps happens before you write a line of code — in how you scope, fund, and justify the platform. Get this wrong and it doesn’t matter how good your Kubernetes is; you’ll have built something no one uses. Treating the platform as a product, with real customers, is the antidote to all four.
1 · The platform nobody asked for
Gizmo’s whisper: “We’re engineers — we already know what developers need. Let’s go heads-down for a year, build the perfect platform in secret, and unveil it with a big bow.” Why it tempts: building is fun and fully under your control; talking to users is slow, awkward, and full of inconvenient answers. Your own taste feels like enough. Why it hurts: you solve imagined problems and ship features no team requested — a shiny catalog for a workflow nobody has, an abstraction over a pain that doesn’t exist. Adoption lands near zero and the platform becomes expensive shelfware. Timmy’s fix: do discovery first. Talk to Dot the Duck and the other ducklings, watch where they actually bleed time, and ship the thinnest viable platform — the one paved path that removes the most pain — then measure whether anyone walks it. Build with your customers, not at them.
“Someone spent a year building a gorgeous internal platform. It provisions message queues I’ll never use and has a beautiful dashboard for a deploy flow my team doesn’t follow. What I actually needed — a Postgres database and a staging URL without a three-day ticket — it doesn’t do. So I route around it: I open the same old Jira ticket and wait. Nobody asked me. If they had, the answer would’ve taken ten minutes.”
2 · The big-bang platform rebuild
Gizmo’s whisper: “The current setup is a mess. Let’s freeze everything and build the One True Platform over the next two years. Do it properly this time.” Why it tempts: greenfield feels clean; a grand moonshot is heroic and avoids the messy compromises of touching production. Why it hurts: two years pass with zero value shipped, requirements drift out from under you, the launch date slips, morale sags, and the business quietly loses faith. It’s the classic second-system effect — and when the moonshot finally lands, it’s often a platform nobody asked for (see trap 1). Timmy’s fix: incremental delivery. Pave one golden path, get three teams onto it in a few weeks, learn, and iterate. Ship value early and continuously; a platform that helps five teams next month beats a perfect one that helps nobody for two years. The best-practices lesson calls this “start with a thin slice and grow.”
Value cadence beats grand vision. If your platform can’t point to a real team it made faster this month, the roadmap is too big. Slice until each increment is shippable in weeks, and let real usage steer the next slice.
3 · Platform as a project, not a product
Gizmo’s whisper: “Great news — the platform is done! Ship it, disband the team, reassign everyone to the next project. We finished!” Why it tempts: projects have a satisfying end date; “done” frees up headcount and looks great to leadership. Why it hurts: a platform is never done. Kubernetes minor versions age out, dependencies are deprecated, security patches land, and new teams arrive with new needs. With no owning team, the golden path cracks, bugs go unfixed, and within a year the platform rots into a fresh legacy snowflake that everyone routes around. Timmy’s fix: fund it as a long-lived product team with a roadmap, a backlog, on-call, and developer-experience metrics — not a project with a finish line. The best-practices page frames this as “you are running a product, forever.”
4 · Cargo-culting the hyperscalers
☺ Like you’re 10: Copying a giant’s homework without checking that their homework was even the same assignment as yours.
Gizmo’s whisper: “Spotify built Backstage! Google has Borg! Netflix has Spinnaker! We have to build exactly what they built or we’re hopelessly behind.” Why it tempts: copying the famous feels safe and looks impressive in a slide deck — conference-driven, résumé-driven design. Why it hurts: their scale, their org chart, and their constraints are not yours. You bolt a fifty-engineer platform onto a five-team company, stand up multi-cluster fleet management when a single cluster with namespaces would do, and drown in complexity you can’t staff. Timmy’s fix: fit your org’s actual size and needs. Steal ideas, not architectures. For a ten-person startup, the golden path might honestly be a good Helm chart, a template repo, and a README — and that’s a success, not a shortfall. Right-size everything.
“Spotify does it” is not a requirement. Before adopting any tool from a giant’s stack, ask: what problem does this solve, do we actually have that problem yet, and can we operate it with the people we have? A platform you can’t staff is just a second system to maintain.
Family two · Toil wearing a platform costume
☺ Like you’re 10: These traps look like a real self-service kitchen from the outside — but open the cupboard and it’s the same old “fill in a form and wait for a grown-up.”
The second family is the sneakiest: the platform looks real, but toil has leaked back in behind the pretty front end. These are the traps that fail the self-service test — a human is still in every loop, or there’s no paved road at all.
5 · Ticket-ops in disguise
Gizmo’s whisper: “Real self-service is hard. Let’s just put a slick Backstage form on top — the developer fills it in, it opens a Jira ticket, and the platform team actions it. Same thing, but modern!” Why it tempts: a form is quick to build, keeping a human in the loop feels safe, and it demos beautifully. Why it hurts: it’s still a queue. Lead time is still measured in days, Dot still waits, and you’ve added a UI to toil instead of removing the toil. There’s a portal on top but no API underneath. Timmy’s fix: put a real self-service API behind the button. The form should create an API object — a custom resource backed by a CRD, or a Crossplane composite resource — that a controller reconciles into real infrastructure in minutes, with nobody in the loop. Apply the self-service test relentlessly: if a human has to touch it, you haven’t escaped Ticket Swamp — you’ve just repainted it.
A capability is only self-service if a developer’s request completes end-to-end with no one touching it. “A human approves it in minutes” still fails the bar — an approval is just a fast ticket. The fix is to encode the policy into the platform (admission control, quotas, guardrails) so “safe” no longer requires “manual.”
6 · The platform team as the bottleneck
Gizmo’s whisper: “Everything should go through you — you’re the experts! Every namespace, every database, every rollout, route it to the platform team for approval.” Why it tempts: control feels responsible, gatekeeping feels like quality, and “we know best” is flattering. Why it hurts: you become the new Ops team — the new Ticket Swamp. You’re paged for everything, you can’t scale past a handful of teams, and you turn into the single constraint on every other team’s delivery. In Team Topologies terms, you’re stuck in permanent “collaboration” mode and never reach “X-as-a-service.” Timmy’s fix: self-service by default. The platform team builds capabilities, not tickets — you pave the road, teams drive it themselves. Judge success by how little developers need you, not how much.
If your platform team’s week is mostly reactive — provisioning things by hand, approving requests, unblocking people one-by-one — you’ve become the bottleneck, no matter what the org chart calls you. Each manual request is a missing self-service feature. Track “requests we actioned by hand” and drive it to zero.
7 · No golden path
☺ Like you’re 10: Handing someone a pile of tools and shouting “freedom!” isn’t a paved road — it’s just a pile of tools.
Gizmo’s whisper: “Flexibility for everyone! Offer Argo and Flux, Helm and Kustomize and raw YAML, three different ways to get a database. Never tell a team no.” Why it tempts: it avoids hard opinions, feels empowering, and nobody’s favourite tool gets rejected. Why it hurts: a pile of tools is not a paved road. Every team reinvents the basics, cognitive load explodes, nothing is supported deeply, and your team spreads thin across a dozen stacks with zero economies of scale. The paradox of choice makes developers slower, not freer. Timmy’s fix: an opinionated, supported golden path — one blessed, secure-by-default way to do the common thing, documented and paved. Keep escape hatches for the rare team that genuinely needs something else, but make the default so good that almost nobody leaves it. See the glossary entry on golden paths.
Opinions are a feature. A golden path means the platform team has made the boring, correct choices once so every developer doesn’t have to re-make them badly. “One well-lit road” beats “infinite dark alleys” every single time.
8 · Mandating the platform
Gizmo’s whisper: “Adoption is low? Easy — make it mandatory. Get a VP to decree that everyone migrates by Q3 or else. Instant hundred-percent adoption!” Why it tempts: it manufactures adoption numbers overnight and lets you skip the hard work of actually being good. Why it hurts: forced adoption breeds resentment and malicious compliance. Teams do the bare minimum, quietly route around you, and file complaints — and because they were coerced, you never learn why the platform wasn’t chosen on its merits. A mandated bad platform is still a bad platform; now it just has a captive, unhappy audience. Adoption is not the same as value. Timmy’s fix: win the pull test. Golden paths are chosen because they’re the easiest path, not because they’re decreed. Make the paved road so obviously faster and safer that opting out feels silly. Pull, never push. This is the heart of platform as a product: your developers are customers you have to earn, not conscripts.
9 · Over-abstraction & the leaky abstraction
☺ Like you’re 10: If you hide the engine and weld the hood shut, the driver can’t fix a thing when the car coughs — and cars always cough eventually.
Gizmo’s whisper: “Hide everything! Developers shouldn’t see YAML, pods, or logs — give them one magic field. kind: App, an image name, and we handle the rest forever.” Why it tempts: simplicity is seductive, “developers shouldn’t need to know Kubernetes” sounds kind, and a one-line manifest demos like magic. Why it hurts: the abstraction leaks the instant something breaks. Dot hits a CrashLoopBackOff buried under six layers of your custom resource and can’t see in, can’t debug, can’t fix — so she files a ticket, straight back to toil. And every real need (a sidecar, a custom probe, a memory bump) becomes a change request to your team: the dreaded platform tax. You picked the wrong altitude — too high. Timmy’s fix: the right altitude plus escape hatches. Abstract the common 80%, but let power users drop down a level — see the generated YAML, override fields, read real logs and traces. Build a golden path, not a golden cage.
# 👺 Gizmo’s “one magic button” — lovely until Dot needs anything real. apiVersion: platform.acme.io/v1 kind: App metadata: name: checkout spec: image: acme/checkout:1.4.3 # ...and that is the entire API. There is no way to set resource # limits, add a volume, tweak a health probe, or even SEE the # Deployment this generates. Every one of those is now a ticket to # the platform team. That is the platform tax.
# 🐢 Timmy’s fix — expose the common knobs, and keep an escape hatch
# down to raw Kubernetes for the 10% who genuinely need it.
apiVersion: platform.acme.io/v2
kind: App
metadata:
name: checkout
spec:
image: acme/checkout:1.4.3
resources: # common knobs, first-class
cpu: "500m"
memory: "512Mi"
podTemplateOverride: # the escape hatch: drop to real k8s
spec:
containers:
- name: app
livenessProbe:
httpGet: { path: /healthz, port: 8080 }
# Dot can also run: kubectl get deploy checkout -o yaml
# to see exactly what the platform rendered. No cage.Family three · The road that eats you at 2am
☺ Like you’re 10: These are the traps that seem fine on a sunny afternoon and then wreck you in the middle of the night — hand-tuned servers, doors left wide open, and no lights on the road.
The last family is operational: shortcuts in how the platform is run and secured. They’re invisible when things are calm and catastrophic when they’re not — the un-rebuildable cluster, the leaked super-token, the outage you hear about from an angry customer instead of a dashboard. This is where GitOps, policy-as-code, and observability earn their keep.
10 · Snowflake clusters & config drift
Gizmo’s whisper: “Just kubectl edit it on the prod cluster real quick — the incident is now, who has time for a pull request? We’ll write it down later, promise.” Why it tempts: hand-tuning is fast and feels like craftsmanship, and the fire is burning right now. Why it hurts: every undocumented hand-edit makes a cluster a little more unique — a snowflake that can’t be reproduced. Clusters drift apart (“works on cluster A, not cluster B”), disaster recovery becomes guesswork, there’s no audit trail, and “later” never comes. Timmy’s fix: GitOps and everything-as-code. Git is the single source of truth; Recon the Robot reconciles reality to match it and self-heals hand-edits away; rebuilding a lost cluster becomes one sync. Change the poster, never the room by hand.
# ❌ Snowflake: fix it live, forget to write it down. Un-reproducible, # and cluster-b never got the change. Disaster recovery? Good luck. kubectl scale deploy/checkout --replicas=8 -n checkout kubectl edit configmap/checkout -n checkout # hand-tweak in prod # ✅ Golden path: change DESIRED STATE in Git; the reconciler rolls it # out identically to every cluster, with a full audit trail. git commit -am "checkout: 8 replicas + timeout bump for Black Friday" git push # Argo CD / Flux applies it everywhere, the same way
The most dangerous snowflakes are made during incidents, when everyone agrees “we’ll GitOps it properly afterward.” Afterward rarely happens. Enforce it with tooling, not willpower: turn on automated drift correction — in Argo CD that is spec.syncPolicy.automated.selfHeal on the Application — so out-of-band edits get reverted automatically, and keep a break-glass procedure that still lands in Git within the hour.
11 · Cluster-admin for everyone
Gizmo’s whisper: “RBAC is fiddly and slows everyone down. Just give every developer — and the CI bot — cluster-admin. Then nobody ever files an access ticket again!” Why it tempts: it kills access tickets instantly, nothing is ever blocked, and it’s dead simple to set up. Why it hurts: your blast radius becomes everything. One leaked token, one bad script, one compromised CI job, and the entire fleet is gone. There’s no tenant isolation — a mistake in one namespace can nuke another — no meaningful audit of who can do what, and compliance auditors will faint. This is exactly the 2am breach Timmy is always warning about. Timmy’s fix: least privilege and policy-as-code. Scope access with namespace-scoped RoleBindings — pointing at a custom Role, or at a built-in ClusterRole such as edit, which the binding confines to that one namespace — give workloads their own ServiceAccounts, use namespaces as tenant boundaries, and let admission controllers (Kyverno or Gatekeeper) enforce guardrails so self-service stays safe by default.
# 👺 Gizmo’s shortcut: everyone is a god. One leaked token = whole fleet.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata: { name: devs-are-admins }
roleRef:
kind: ClusterRole
name: cluster-admin # the keys to the entire kingdom
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: Group
name: developers
apiGroup: rbac.authorization.k8s.io
---
# 🐢 Timmy’s guardrail: least privilege, scoped to ONE namespace.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: checkout-devs, namespace: checkout }
roleRef:
# A RoleBinding can reference a ClusterRole: the permissions then
# apply ONLY inside this RoleBinding's namespace. Here that means
# the built-in "edit" role, scoped to the checkout namespace alone.
kind: ClusterRole
name: edit
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: Group
name: checkout-team
apiGroup: rbac.authorization.k8s.ioCounter-intuitively, tight RBAC is what lets you safely say “yes, self-serve.” When each team’s blast radius is bounded to its own namespace, a mistake or a leaked token stays in one lane instead of taking down the whole road — so you can hand out access freely within those lanes. Guardrails aren’t the opposite of speed; they’re the precondition for it.
12 · Ignoring Day-2 & shipping with no observability
☺ Like you’re 10: Getting it running is Day-1. Keeping it alive, day after day, is Day-2 — and that’s where platforms actually live or die.
Gizmo’s whisper: “Ship it! Monitoring, dashboards, alerts, runbooks — that’s all a ‘later’ problem. It works on my machine. Deploy and pray.” Why it tempts: getting something running (Day-1) feels like the finish line, observability is unglamorous plumbing, and nothing’s broken yet. Why it hurts: you’re flying blind. The first you hear of an outage is Dot — or worse, her customers — complaining. You can’t tell latency from errors from saturation, MTTR balloons because you’re debugging in the dark, and with no SLOs or error budgets, every incident is a fresh archaeology dig. Timmy’s fix: build the watchtower from day one. The four golden signals, SLIs and SLOs, dashboards, alerting, and tracing are platform features, not add-ons. If a team ships onto your golden path, it should get observability for free — Day-2 readiness baked into the paved road, not bolted on after the first outage.
“It works” is a Day-1 statement. The exam — and reality — care about Day-2: upgrades, drift, incidents, capacity, cost, and the un-fun grind of keeping a platform healthy for years. A golden path that isn’t observable and operable is a trap with a launch party.
The whole rogues’ gallery, at a glance
☺ Like you’re 10: One big cheat-sheet — the sneaky move, the smell that gives it away, and the fix — for all twelve traps at once.
Every trap has a tell — a symptom you can smell before the platform fully rots. Keep this table close; it’s the fastest audit you can run on any platform, real or imagined.
| # | Anti-pattern | The smell (what you’d notice) | The fix |
|---|---|---|---|
| 1 | The platform nobody asked for | Adoption near zero; you built features no team ever requested. | Discovery first; ship the thinnest viable platform and measure use. |
| 2 | Big-bang rebuild | A two-year moonshot with no value shipped; requirements already stale. | Incremental slices; real value in weeks, steered by usage. |
| 3 | Project, not product | Team disbanded at “launch”; the platform rots into new legacy. | Fund an ongoing product team with roadmap, on-call & DevEx metrics. |
| 4 | Cargo-culting hyperscalers | Running Spotify’s stack at a five-team company; complexity you can’t staff. | Fit your org’s real size & needs; steal ideas, not architectures. |
| 5 | Ticket-ops in disguise | A slick portal that just opens a ticket; lead time still measured in days. | A real self-service API behind the button; zero humans in the loop. |
| 6 | Team as the bottleneck | Every request routes through the platform team; you’re the new Ops queue. | Self-service by default; build capabilities, not tickets. |
| 7 | No golden path | Six ways to do everything; every team reinvents; nothing supported well. | One opinionated, supported, secure-by-default road (with escape hatches). |
| 8 | Mandating the platform | Adoption exists only because a VP decreed it; teams comply resentfully. | Win the pull test — make the paved path the genuinely easiest path. |
| 9 | Over-/leaky abstraction | Devs can’t debug their own app; every small need is a “platform tax” ticket. | Right altitude + escape hatches; expose the generated YAML and real logs. |
| 10 | Snowflakes & drift | kubectl edit in prod; clusters that can’t be rebuilt; “works on A, not B.” | GitOps; everything-as-code; self-heal reverts hand-edits. |
| 11 | Cluster-admin for all | Every dev and bot holds cluster-admin; one leaked token = total blast radius. | Least privilege; scoped RBAC; policy-as-code guardrails. |
| 12 | Ignoring Day-2 / no observability | No dashboards or alerts; you learn about outages from angry users. | Build the watchtower from day one; golden signals, SLOs, tracing. |
A second look at drift — why snowflakes are so costly
☺ Like you’re 10: When every server is hand-tuned differently, you can never make two the same again — so rebuilding after a disaster is a nightmare.
Traps 10 and 11 travel together: hand-editing (a snowflake) and over-broad access both wreck your ability to answer the two questions that matter most in an incident — what is supposed to be running here? and who could have changed it? GitOps answers the first, scoped RBAC and an audit trail the second. Below, the same three clusters drift into unreproducible snowflakes on the left, versus one reconciled source of truth on the right.
Audit a platform against this list — your real one, or one you imagine for a mid-size company. Go trap by trap and score each red / yellow / green. For every request type a developer can make (new service, database, environment, deploy, rollback), answer the self-service test: does it complete with zero humans in the loop? Then answer the pull test: would teams choose this path if it weren’t mandated? Finally, pick the one reddest trap and write a single incremental slice that moves it toward green in under two weeks. That last step — one small, shippable fix — is the whole philosophy of this course in miniature.
Foxy: We keep saying “build a platform.” What actually goes wrong when a team tries?
Gizmo: Nothing, if you take my shortcuts! Step one: give every dev cluster-admin. No more access tickets. You’re welcome. 🤑
Timmy: …and one leaked token deletes the whole fleet. Least privilege, scoped RBAC. Next.
Gizmo: Fine, fine. Then hide all the YAML behind one magic kind: App button. Developers should never see Kubernetes!
Timmy: Until Dot hits a CrashLoopBackOff she can’t debug and files a ticket — straight back to the swamp. Right altitude, plus an escape hatch.
Gizmo: Ugh. Okay — nobody’s using the platform anyway, so just mandate it. VP edict. Boom, 100% adoption!
Timmy: Coerced adoption isn’t value, it’s resentment. Make the paved road the easy road and they’ll choose it. And no, we are not spending two years building it in secret first.
Dot: Honestly? If your platform is faster and safer than my old tickets, I’ll move tomorrow — nobody has to force me. If it’s slower, I’ll route around it no matter what the VP says.
Notice the shape of every fix: it drags one slice of the platform back toward self-service flow and treats developers as customers to be earned. That’s not a coincidence — it’s the same north star the whole course points at. When you’re ready to see all the fixes assembled into a positive playbook rather than a list of don’ts, head to best practices.
1. A team ships a Backstage portal, but every “new database” form opens a Jira ticket a human then fulfils. Which anti-pattern is this, and what single test exposes it? 2. Why is mandating platform adoption a red flag even when the adoption numbers look great? 3. What is the “platform tax,” and which trap causes it? 4. Give the two-part fix for “cluster-admin for everyone.” 5. Name one reason a big-bang two-year rebuild tends to fail — and the alternative in three words.
Check your answers
- Ticket-ops in disguise. The self-service test exposes it: the request can’t complete with zero humans in the loop, so it’s still a queue with a nicer front end.
- Adoption is not the same as value. Forced adoption breeds malicious compliance and route-arounds, and it hides why the platform wasn’t chosen on merit. A real golden path passes the pull test — teams pick it because it’s the easiest path.
- The platform tax is when every real developer need (a sidecar, a probe, a resource tweak) requires a change from the platform team because the abstraction hid too much. It’s caused by over-abstraction / leaky abstraction; the fix is the right altitude plus escape hatches.
- Least privilege (scoped
Role/RoleBindingper namespace, per-workload ServiceAccounts, namespaces as tenant boundaries) plus policy-as-code (admission controllers like Kyverno or Gatekeeper enforcing guardrails). - Any of: no value ships for years, requirements go stale, morale and business faith collapse, and it often lands as a platform nobody asked for. The alternative: incremental thin slices.