The Unreasonable Effectiveness
of 12-Factor Apps
This issue is brought to you by:
Twingate: It's time to ditch your VPN.
Keep private resources and internet traffic protected with Zero Trust security tools built for the modern world of work.
|
|
The title of this newsletter isn’t just fluff; it points to a fundamental truth in software development. Often, the most expensive problems stem from ignoring a few simple, foundational rules.
The “unreasonable effectiveness” of the 12-Factor App methodology lies in its power to prevent million-dollar failures with a straightforward set of guidelines for building modern applications.
The biggest headaches in tech aren’t always caused by buggy code.
The most expensive issues are caused by perfectly good code running in a terrible environment.
Research shows a staggering 73% of companies feel held back by technical debt, with issues from bad architecture costing millions. The classic “but it works on my machine!” is an enormous red flag leading to a deployment nightmare, where an application that runs perfectly on a developer’s laptop falls apart in the real world.
Here’s how you can put these learnings into action and start building better.
Instead of creating fragile, tightly coupled systems, The Twelve-Factor App rules set a few simple concepts to build great environments, and has been a blueprint for robust applications since 2011.
Built in Heroku, the 12Factor App has been improving and updated since.
And although last updated in 2017, the GitHub repo is updated regularly and a new version is expected pretty soon.
And while 12 factors sound a lot, you’re probably already implementing 6 of them without thinking about it.
So today we’re focusing on the essentials, and the one I believe are critical to focus on.
Here’s how you can put these learnings into action and start building better:
1. Separate Configuration from Code
Stop hardcoding values like database addresses or API keys directly into your application.
Non configurable code creates production nightmares - think of a devops engineer not having to dive into the application code to fix an API endpoint or any env var to configure its behavior.
Your code should be the same everywhere, but its configuration (like the database it connects to) will change between your laptop, staging, and production.
Store these values in environment variables.
This way, you can deploy the exact same application artifact anywhere without changing a single line of code, making your deployments predictable and safe.
Or as the 12 Factor team puts it:
“Config varies … code does not”
- https://12factor.net/config
2. Treat Backing Services as Attached Resources
Think of your database, cache, or message queue as a plug-and-play resource, like a USB drive; you can plug and disconnect, you can replace / switch / whatever, computers don’t care.
Whatever’s plugged - that’s what we’re working with.
Your application shouldn’t care if the database is running on the same machine or is a managed service in the cloud.
This loose coupling means you can easily swap out a local Postgres database for a cloud-based one (assuming API parity), or scale your Redis cache independently, all without touching your application’s code.
3. Strictly Separate Build, Release, and Run
These three stages must be distinct.
Build is compiling your code and assets into a package.
Release is combining that package with a specific configuration.
Run is launching the application in an environment.
This separation ensures you can’t make changes to code at runtime (no more SSHing into a server to apply a quick fix…).
It forces a disciplined process where every change goes through a proper build and release pipeline, making your system stable and auditable.
Also, consider the fact you “build once - run everywhere” - by combining separate config (see point 1), a software build runs once it’s ready and can be deployed on any environment, and the one thing that changes is the surrounding config!
4. Run as Stateless Processes
Your application should run as one or more processes that share nothing and save nothing locally. Any data that needs to persist must be stored in a stateful backing service (like a database).
This means you should never rely on sticky sessions or data cached in memory, this may hurt some ears, and I do get it can even be ‘controversial’ to an extent (as with anything on the internet 🤣) but it will make your production engineering life’s easier.
The benefit?
You can scale your application horizontally by simply adding more copies of the process, making it incredibly easy to handle more traffic.
5. Aim for Disposability (Cattle not pets!)
Your application’s processes should be disposable, meaning they can be started or stopped at a moment’s notice.
This requires fast startup times and graceful shutdowns.
When a process is told to stop, it should finish its current task, clean up its resources (like closing database connections), and then exit.
This makes your system resilient to scaling events, crashes, and updates.
There’s a great example for listening to incoming signals and responding accordingly in a 12 Factor demo by Kelsey Hightower.
6. Keep Development and Production in Parity
The gap between your development environment and production should be as small as possible.
If you use Redis and Postgres in production, you should use them in development too. This principle helps you catch bugs and configuration mistakes early, long before they can impact your users. The more similar the environments, the more reliable (and frictionless) your deployments will be
7. Treat Logs as Event Streams
Never write logs to a file.
This used to be standard practice, in modern environments, especially ones that rely on containers, avoid this at all costs. File based logs == No logs.
Instead, write all log output to standard output (/dev/stdout).
In modern cloud environments, the platform is responsible for collecting, routing, and storing these log streams.
This turns logging from a complex file management problem into a simple, centralized stream of events that tells you exactly what your application is doing at any given time.
With that in mind - use logs for this purpose: “speak” to whoever’s debugging your app through logs, say what it’s doing.
There’s more to that short list and it’s all here: 12factor.net, or more importantly here - on their github repository.
Thank you for reading.
Feel free to reply directly with any question or feedback.
Have a great weekend!
Whenever you’re ready, here’s how I can help you:
|
|