The 12-Factor App - 15 Years later. Does it Still Hold Up in 2026?
15 points by zanlib
15 points by zanlib
An exemption to Betteridge's Law but (just like the article's conclusion) my opinion is firmly in the "absolutely, yes" camp
I agree with all of those backward and forward compatibility points, too, and will add "database schema upgrades should be out of band, or have a distributed lock" since if the app itself owns its own schema migration then a hypothetical kubectl rollout deploy --image=new-thing will cause all instances to try and upgrade simultaneously unless the mechanism is guarding against that outcome
I wrote a schema management tool (https://github.com/pgpkg/pgpkg) which does exactly this, using PostgreSQL advisory locks. It also performs all migrations in a single transaction, which significantly reduces the scope for errors (one of the many reasons I love Postgres!)
But if - like me - you make heavy use of stored procedures and views, you do have to take care that they remain backwards compatible. It’s really easy to forget that a function or view definition is a contract with the past.
This can be mitigated somewhat with database unit tests, which I run as part of the upgrade transaction. So not only do you need locks and backward compatibility, but also schema tests, and rollback if something goes wrong at any point.
to help others, this link is the thing under discussion https://github.com/pgpkg/pgpkg/blob/v0.1.16/docs/pages/phases.md#:~:text=only%20one%20instance%20of%20pgpkg%20can%20run%20at%20a%20time and is seemingly implemented by https://github.com/pgpkg/pgpkg/blob/v0.1.16/package.go#L207-L208 (aka pg_advisory_xact_lock)
The twelve-factor app stores config in environment variables...
The article touched on this a bit as well, but to expand a bit..
I always found the original 12 factor app guidelines had a subtlety with regards to its recommendation about config in env vars. Specifically regarding secrets in ENV. The process env may be logged on startup, may be copied into subprocesses, and the initial env is usually readable in the filesystem (/proc/<pid>/environ), even if it has been deleted from the live process env after reading.
Using a file path as an env var, pointing to a secret stored in a file¹ (populated by k8s/vault/etc), might to be preferable. Even better might be fetching a credential via direct api call against a secret store during process startup (with the location of the secret in an env var or file).
¹: could even be an ephemeral/temporary file that is removed after reading and launching the process
yeah I always thought the reason they recommended that was that when they wrote it they were trying to drum up Heroku usage and they wanted to make config files look uncool because they didn't have a good solution for mounting a config file, so they sold the idea that the thing that was easy on their platform was "best practice".
It actually has more to do with the easy of use around environment variables rather than files. Files typically require a deploy, envvars can be managed directly (especially with Heroku's cli, but even in other deployments you can build up the environment and then restart the app trivially).
Secrets though were definitely something being worked on, I don't know that it was ever finished but there was some work on possibly encrypting them. At the time though, I think we generally relied on TLS for transport around api calls to fill in the details and then used capabilities around cgroups. It's been a while since I thought about this setup though, so I don't remember all the details.
The process env may be logged on startup, may be copied into subprocesses, and the initial env is usually readable in the filesystem (/proc/<pid>/environ), even if it has been deleted from the live process env after reading.
I feel like the concerns about someone else reading your env vars are the sort of "other side of this airtight hatchway" situation that isn't worth spending too much time worrying about. If isolation mechanisms on the physical machine have been broken to such a degree that an interloper can get into it and use these approaches to read your process' env vars, then
For example, one of your links recommends using AWS secret manager and only putting the ID of the secret in the env. But if I have enough access to your process to read its env, it's highly likely and bordering on certain that I can also bootstrap my way to accessing the AWS secret manager the same way your process is accessing it.
Or as Raymond Chen put it:
The real vulnerability is the component that has a write-what-where vulnerability. That’s the guy that let you into the airtight hatchway. But once you get there, you’ve already won.
It may not be 100 % effective, but I would argue that if the application image is immutable and known to be safe, then you can avoid a lot of trouble by having secrets be read-once.
This can be done be either reading the secrets file on startup and then deleting it, or having root start your application as a unprivilleged user and pass the secrets as stdin (cat /root/secrets.json | sudo -u app_user myapp).
Example: you have a webapp that allows user uploads, but it has an exploit if a special crafted file is uploaded. When the app starts, there is no exploit running, but after a while there is. If the application can't reread the secrets, they might be safe.
This article is written as if containerization had not been a hot topic during the original heyday of the 12-factor app, but the 12 factors are intimately related to containerization. Previously, on Lobsters, we discussed how the 12 factors relate to containerization and cloud-native offerings, and previously, on Lobsters, we also discussed the history of containers in Linux.
12-factor was released before "containers" as they are known today. However, the underlying technology was "cgroups" which are what docker and the likes are built around. Phrases like "containerization", "cloud-native", and "docker" didn't exist when Heroku was in it's hey-day and the 12-factor methodology was released.
You might get a kick out of the history in my first link. LXC, also called "Linux containers", was first published by an IBM-led coalition in 2008. dotCloud, who would become Docker, Inc., was started in Europe in 2008 two years prior to their incorporation in the USA. The first 12-factor talks were given sometime around 2011 and the 12factor.net Website has no scrapes in the Internet Archive prior to November 2011. There's a gap of over two years between the introduction of containers to the Linux ecosystem by IBM, Google, and others; and the publication of the first versions of the 12-factor methodology.
I'll readily admit that cloud-native computing appears to have been introduced as part of the understanding around k8s in 2015.