the slow (then fast) death of the 12-factor app

Adam Wiggins published the Twelve-Factor App methodology in 2011. At the time, it was a genuinely useful set of principles for developers that were really just starting to handle non-functional aspects of software development for the first time. This was a long time ago, so let’s paint the picture - you might have just gotten your first smartphone (iPhone 4 had just launched, the first with that awesome, square side), you were probably sick of hearing “Somebody that I Used to Know” on the radio and the news was covering the dramatic events of the Arab spring, the royal wedding (and not Meghan and Harry’s) and the death of Steve Jobs. Yes, back then, developers were just getting used to owning more than just writing code and tests. Apps were still pretty monolithic, the cloud was not yet ubiquitous and Heroku was making deployment simpler for a new generation of builders.

The 12 factor app was a sensible checklist, or set of design principles for this world, fixing oversights that made apps less performant, reliable, maintainable, durable, observable, etc etc etc.

That world is gone.

Developers, (at least those with some grey hair like me) were heavily influenced by these principles and these biases still affect us today. I’ll argue below that some of those “factors” (principles) are actually ill-fit for software development today.


What still holds

A few factors remain solid and don’t need to be relitigated: a single tracked codebase, explicit dependency declarations, stateless processes, logs (the framework was written before the ubiquity of the term “observability”), disposability (fancy word for fast startups and graceful shutdowns). These aren’t insights anymore, they’re defaults and your framework of choice enforces them.


What doesn’t

III. Config - store config in the environment

The intent was good: don’t hardcode values, separate config (and most importantly, secrets) from code. In practice, “store it in the environment” became “dump everything into .env files”, which then invariably leak (git, slack) because the workflow around them is error-prone by nature.

It’s also deeply ironic that while factor ten calls for dev/prod parity, in reality, config/secrets are managed drastically differently between production and development. Poor DevEx is really to blame for all of the secret leakage of the last 15 years. And even if nobody accidentally leaked a secret, storing credentials in plaintext on your development machine is a bad idea. The software supply chain is a murky and dangerous mess, and the malware within it is increasingly designed to target development environments (e.g. Shai Hulud 1 & 2, Nx-S1ngularity) to exfiltrate or abuse your credentials for fun and profit.

If we are really engineers, we should act like our colleagues in other disciplines (civil, aerospace) and designing more fault tolerant approaches.

Twelve-factor’s env framing actively delayed that shift by making plaintext files feel like a sound design decision.

My approach today is simple - avoiding static secrets, but for those that cannot be avoided, use a centralised secrets management solution (like AWS parameter store / secrets manager) and nice devtooling tooling (like fnox) to plumb profiles of secrets into the environment.

As a dear and talented colleague once described it “the best way to avoid radiation poisoning is to avoid handling the radioactive material in the first place”.


X. Dev/prod parity - keep environments as similar as possible

This principle is where the methodology is most out of sync with modern realities.

The stated goal is to eliminate bugs that appear in production but not locally. Admirable in 2011-13, when the gap was “Postgres in prod, SQLite in dev.” Unworkable now under the weight of our (usually) overcomplicated architecture.

A modern microservices deployment might involve dozens of services, event queues, caches, CDN config, identity providers, service mesh policies, and environment-specific IAM roles. Getting that to run on a laptop and manage all config and data state and schemas is too complex. And while local/remote proxies like Tilt or Mirrord are nice approaches and yield results, they don’t address the structural problem: managing complex and fragmented applications are already a full-time job in production, and therefore shouldn’t be an extra full-time job in local development for our developers.

The worse consequence is what this assumption encodes about who writes the code. Dev/prod parity is a laptop-centric idea. We focus on getting everything running locally first. Why? Because of “fast feedback loops”, allegedly. It assumes a single human developer needs a hermetic local environment to work. That was already questionable with ubiquity of the cloud and simple, declarative instantiation of arbitrary environments. It’s plainly wrong now.

Agentic development breaks this assumption entirely. The number of agents we seek to leverage compounds the problem locally. Attempting to run everything on our machine, or in a coding agent’s sandbox is plainly not feasible, nor is it the best form of feedback for agents to work quickly through tasks.

It can hit a real staging API, run against an actual database seed, and get feedback from a real observability stack. The goal shouldn’t be shrinking production down to fit on a developer’s machine — it should be about designing a means of safe contribution that can run in a sandbox.

That’s a different problem, with better solutions: better software architecture, clearer contracts, better e2e testing, ephemeral environments, preview deployments, branch-scoped databases, feature flags and synthetic/smoke tests in production. And by the way, ignoring agentic development, it’s a better experience for developers than messing around with docker-compose configs in the first place.

Local development as the primary feedback loop is a bad habit and one we should drop, in my humble opinion.


Better principles for today

With respect to devops, we have almost come full-circle. We went from just writing code and tests, to managing everything about the application (functional and non-functional), to platform engineering (designing common primitives to offload the majority of undifferentiated needs across our complex apps and services) to the age of agentic engineering, where thanks to the hard work on our internal platforms, we focus on shipping features, but we don’t even really write code and tests.

I’d argue that today we should continue to emulate principles from platform engineering, but with different stakeholders in mind. Like I argue above for two of the worst-managed “factors” of the 12-factor app, I believe the right approach is for teams to focus on enablement - not for the developer, but for the coding agent. We need to build tools, guidelines (and sometimes even guardrails) for more productive and less error-prone workflows.