DebuggingGENERALSCALE-SPECIFICORG-SPECIFIC

Production Anti-Patterns

The practices that reliably produce incidents — each with why it is tempting, because a list that only says "do not" teaches nothing.

The question, the obvious approach, and why it breaks

Every lesson starts where the work starts: an operational problem, a first attempt that is entirely reasonable, and the way production disagrees with it.

The production question

Which habits produce most production incidents, and why do sensible teams adopt them anyway?

The problem

Every one of these is adopted by competent people for a real reason, usually under time pressure. They are not ignorance; they are local optimisations whose cost lands later, elsewhere, and on someone else.

What teams do first

Publish the list of things not to do and expect the list to change behaviour. Everyone nods, and then ships a hotfix over SSH on Friday because the pipeline takes forty minutes.

How it breaks

A prohibition without an alternative loses to the pressure that produced the behaviour. If the safe path is slower than the unsafe one at 2am, the unsafe one wins.

How it breaks in production
  • A prohibition without an alternative loses to the pressure that produced the behaviour. If the safe path is slower than the unsafe one at 2am, the unsafe one wins.
  • People who do not know why a practice is tempting cannot recognise themselves doing it — they think the anti-pattern is something other, less careful teams do.
  • Each of these has a legitimate emergency form. Banning it outright means it happens anyway, undocumented, which is strictly worse than a sanctioned break-glass path (Break-Glass Access).
  • Lists get adopted as compliance checklists, so teams optimise the appearance rather than the property — access exists but is unreviewed, runbooks exist but are stale.
CodeBuildTestArtifactReleaseDeployRunObserveOperateIncidentRecoverLearnImprove

What is actually happening

Underneath the tooling, which is the part that survives a change of tool.

  • Nearly all of these share a shape: they trade a known, deferred cost for an immediate saving, and the deferred cost is paid during an incident by whoever is on call.
  • The second shared shape is destroying evidence or reversibility. Manual patches, per-environment rebuilds and mutable tags all leave you unable to answer "what is running and where did it come from" at the moment you most need to.
  • The third is removing a signal: alerting on everything removes the meaning of an alert, and ignoring failed backups removes the only evidence that recovery is possible.
  • The fix is almost never willpower. It is making the safe path the fast path, so the tempting shortcut stops being the shortest route (Golden Paths).

Change and access

This group shares one property: the state of production stops being derivable from anything you can read. Once that is true, every subsequent incident starts with archaeology.

Anti-patternWhy it is temptingWhat it actually costsInstead
SSH in and manually patch productionIt is the fastest path to a fix at 2am, and the pipeline takes forty minutesThe running state now matches no artifact. It vanishes at the next deploy or restart, and the next person cannot reproduce itMake the emergency path a fast, audited deploy; treat the pipeline's speed as the actual bug (Manual Production Changes)
Unlimited standing production accessRestricting it slows people down, and everyone here is trustedOne compromised laptop or one mistyped command has unlimited blast radius, and nothing distinguishes a routine action from an unusual oneRead access broadly, write access scoped and time-bound, with break-glass for the rest (Least Privilege in Production)
Manual repetitive release stepsEach step is small, and automating them is a project nobody has time forSteps get skipped or reordered under pressure, and the release becomes dependent on who runs itAutomate the sequence; keep the go/no-go decision human (Toil)
Configuration known only by one personIt works, they know it, and writing it down never becomes urgentThat person becomes a single point of failure for every incident, including the ones during their holidayConfiguration in version control, reviewed and applied through a pipeline (A Config Change Is a Production Change)
Huge, infrequent releasesReleasing is painful, so it is done rarely — which is a locally rational response to painEvery release contains dozens of changes, so correlation is useless and rollback reverts everything, including the parts that workedSmaller, more frequent releases; the pain is the signal to fix the path (Change Size: Why Small Changes Are Safer, and When They Are Not)

Build, deploy and rollback

GENERALThese hold on any platform that has artifacts and releases. On a platform where deployment is a git push and the provider builds for you, "build once" is partly the provider's responsibility — the question becomes whether it rebuilds on promotion, which many do.

This group destroys either the identity of what is running or the ability to get back. Both are cheap to preserve at build time and impossible to reconstruct during an incident.

The migration item is the highest-severity entry on the whole list, because it is the one where "roll back" stops being available at all.

Anti-patternWhy it is temptingWhat it actually costsInstead
Rebuild separately for each environmentIt feels natural — each environment gets a fresh build with its own settingsWhat you tested is not what you shipped. Any difference in inputs between builds is invisible and untestableBuild once, promote the same artifact through environments, vary only config (Build Once, Deploy Many)
Deploy a mutable tag such as latestIt is convenient, always current, and requires no version bookkeepingYou cannot say what is running, cannot reproduce it, and a rollback may retrieve different bits than it did yesterdayDeploy by immutable digest; keep tags as human labels only (Tags Versus Digests)
No rollback planForward-only feels confident, and rollback is assumed to be automaticThe first time you need it is during an incident, which is where you discover it was never testedDecide and test the reversal before the change ships (Rollback: Only Useful If It Is Actually Safe)
Destructive DB migration in the same release as the codeOne deploy, one change, one thing to coordinate — simpler on paperThe code can roll back and the dropped column cannot. The release becomes one-way at the moment you most want it reversibleExpand, migrate, contract as separate releases (Expand, Migrate, Contract)
Secrets committed to gitIt is expedient during setup and everything works immediatelyThey live in history forever, on every clone and every fork, and rotation is now a separate projectSecret manager or workload identity from the start; scan history and rotate what is there (What Counts as a Secret, and Where It Must Not Be)
CI runners with broad standing production credentialsIt makes every pipeline "just work" without per-job permission wranglingAny dependency in any build now has production credentials — the widest supply-chain surface most teams have (Securing the Pipeline Itself)Short-lived, job-scoped credentials issued per deployment (Secrets in CI)

Signals, recovery and scale

SCALE-SPECIFICThe orchestration and autoscaling entries are about fit, not about the tools. Kubernetes is the correct answer for many organisations; the anti-pattern is adopting it as a definition of production rather than as an answer to placement, rollout and bin-packing problems you can name (The Problems Kubernetes Answers).

This group removes your ability to know something is wrong, to recover when it is, or to understand the system you have chosen. They are the quietest items on the list and the most common contributing factors in severe incidents.

Anti-patternWhy it is temptingWhat it actually costsInstead
Alert on everythingEvery alert was added after a real incident, by someone being responsibleVolume destroys meaning. Real pages are missed inside noise, and on-call burns out (Alert Fatigue)Page on user-visible symptoms; everything else is a dashboard or a ticket (Alert on Symptoms, Not on Causes)
No runbooksThe people who know the system are on call, so writing it down feels redundantThe knowledge is unavailable exactly when the person holding it is asleep, on leave, or has leftA runbook per alert, written for someone who was not there (Runbooks)
Ignore failed backup jobsThe failure is usually transient, the job is noisy, and it has always been fineBackups are only evidence if they succeeded. A silently failing job means the last good copy is older than anyone believesAlert on backup failure and on backup age; treat both as user-facing (Backup Operations)
No restore drillsBackups exist and are green, so restoring is assumed to workYou discover the restore path is broken, incomplete or far slower than your recovery objective during the disasterScheduled restore drills with timing, into a real environment (Restore Drills)
Kubernetes because everyone uses itIt is the default answer, it is well-documented, and it is good for hiringA large operational surface with its own failure modes, adopted before the problems it solves exist (Do You Need Kubernetes?)Choose the simplest platform that solves the problems you actually have, and revisit as they change
Autoscaling without understanding capacityIt appears to make capacity someone else's problemScaling on the wrong signal, or into a downstream bottleneck, converts a slow service into a saturated database (How Autoscaling Fails)Model capacity first — what saturates, at what level, with what lag — then automate within it (Building a Capacity Model)

How to do it properly

Most important first.

  • For each anti-pattern present in your system, identify the pressure that produces it and remove the pressure, not just the behaviour. A slow pipeline creates SSH patching; speeding the pipeline removes it (CI Is a Feedback System).
  • Provide a sanctioned, audited emergency path for the cases that genuinely need one, so the emergency does not have to route around the controls (Break-Glass Access).
  • Prioritise by blast radius rather than by list order. Unlimited production access, destructive migrations and untested restores are categorically worse than a stale runbook.
  • Convert conventions into mechanisms: immutability enforced by the registry beats a rule about tags, and least privilege enforced by policy beats a norm about care (Policy as Code).
  • Review the list against your own last five incidents, not in the abstract. The ones you have already been bitten by are the ones worth fixing first.

How much can this affect

Every production change has a blast radius. Stated as a scale so it is comparable between changes rather than adjectival — and paired with what actually contains it, because a wide scope with a real containment mechanism is a different situation from a wide scope with none.

Blast radius if this is wrongEveryone
One testEveryone
What contains it

These are the practices that specifically remove containment — rollback, audit, least privilege and recovery. That is why they cluster in postmortems.

What can go wrong

Failure modes, including of the mitigation
  • Fixing the behaviour while leaving the pressure, so it reappears in a new form — SSH access removed, and now people push emergency commits straight to the release branch.
  • Adopting all the controls at once on a small team, producing so much friction that people build a shadow path around them.
  • Treating the list as a maturity score, so effort goes to items that are cheap to demonstrate rather than items that are dangerous.
  • Guardrails with no exception path, which convert every genuine emergency into a policy violation performed in silence.
Misreads this invites
  • "These are things bad teams do." Every one of them is a rational local decision under pressure. The good teams differ in having removed the pressure.
  • "Never SSH into production." Investigating from inside a running container is sometimes exactly right (Debugging a Container in Production). The anti-pattern is *changing* production that way, leaving state no artifact reproduces.
  • "Adopt everything on this list." Several items are scale-dependent, and adopting them all on a small system produces ceremony without safety (What an Environment Is For).
  • "We have controls, so we are fine." Controls that are routinely bypassed are worse than absent ones, because they also produce false confidence.

Operating it

Evidence is the signal, not the intention. Rollback is sometimes 'you cannot, and that is the point'.

How you know it worked
  • You can name which of these are true of your system today, with an example of each, rather than asserting none apply.
  • The emergency path exists, has been used, and produced an audit record and a follow-up (Access Review).
  • The last incident postmortem did not name one of these as a contributing factor twice in a row.
How you get back
  • Removing an anti-pattern is itself a change with a blast radius: tightening production access can lock out the person who needs it at 3am. Stage it, and keep a break-glass route open while you do.
  • Where a control turns out to be too tight, loosen it deliberately and record why, rather than letting it be bypassed informally.
What to automate, and what stays human
  • Automate detection: unpinned tags, publicly writable buckets, secrets in git history, standing production credentials in CI, backup jobs that failed silently.
  • Automate the safe path so it is faster than the unsafe one — that is the only intervention that reliably works (Golden Paths).
  • Keep the exception human and reviewed. An automated emergency-access grant with nobody approving it is simply standing access with extra steps.
What this costs
  • Every control here costs friction, and friction is not free — it slows recovery as well as mistakes. The right amount depends on blast radius and team size.
  • Some items are genuinely scale-dependent: a two-person team with one service does not need environment promotion machinery, and adding it costs more than it returns.
  • Auditing and least privilege add real latency to legitimate emergency work. The answer is a fast sanctioned path, not the absence of controls.

Where this applies

This domain is unusually tool- and organisation-dependent. These labels say what each claim is specific to, and what a different platform, provider or organisation does instead.

  • GENERALThe failure mechanism of each holds broadly. What varies is severity: mutable tags on a hobby project cost an afternoon, and on a regulated system cost your ability to say what was running during an audited period.
  • SCALE-SPECIFICSeveral items only become anti-patterns above a threshold. One person deploying one service does not need environment promotion or formal access review; both become necessary as soon as more than one person can change production and nobody sees all the changes.
  • ORG-SPECIFICWhere the line sits between guardrail and gate is an organisational choice. A team with strong review culture may allow broader standing access than one where changes are made by contractors on rotation; neither is universally correct.

Where the depth lives

This domain teaches delivery and operation, and hands the mechanism off to the domain that owns it.