Still incredibly relevant. Even if you don’t apply it, there is so much to learn by reading this in 15 minutes.
The only grievance I have with this is Chapter 3: Config [1] “Store config in the environment”, “Credentials to external services such as Amazon S3 or Twitter”
Besides being bad advice, this had the second-order effect of leading devs to believe they could put all their local env secrets in ~/.bashrc files.
Stop doing this. Do the other 11.5 factors.
Ironically agents F'IN LOVE using dotenv for config but really struggle with sOps (as of Opus 4.6; maybe its better now).
I was a massive fan of dotenv but sOps is so much cleaner, easy enough to use and works well enough in k8s.
Even putting secrets aside, the environment is a crappy place for config data.
It's got a maximum size cap, is trivially introspectable by via any process that can read `/proc`, and sucks at representing hierarchical or structured data beyond k=v.
The proliferation of tools that come up with all sorts of contortions to encode e.g. JSON-ish structures into the environment is evidence that this ain't a great way to go. I hope we're moving towards a container-orchestrator-by-default future; mounting structured data into pseudo-files at runtime is a really nice alternative.
I wonder what alternative methods people use nowadays that are good enough but still simple and lightweight ? secret management services have its own place but not everyone have those available.
I’d speculate that this was a product of its time (early Heroku days), and that a goal at the time was to get secrets out of source control. Which was an antipattern way back then.
Times have changed since then, and there’s much better tooling available to help with this problem space and surface area these days.
> this had the second-order effect of leading devs to believe they could put all their local env secrets in ~/.bashrc files
Teach them to use dotenv.
We are moving away from configuration in config files because it is a pain to modify, especially if part of that configuration is secrets. You have to throw everything into your secrets vault of preference, and editing it requires extracting and reuploading the whole thing.
We are currently doing config in env by loading one or multiple secrets per kubernetes pod (mix and match).
What would be your suggestion?
Yeah, secrets should be fetched by application code from a secret store (aws secrets manager, vault, etc) using an identity. Put in a pull request.
100% this.
1. Keep secrets in a dedicated secrets store.
2. Read directly from the secrets store in application code. There is no environment, there are no environment variables. Yes, even on local.
Disagree, partially.
Buy-into storing credentials into environment variables.
Then, and this is important - MANAGE YOUR ENVIRONMENTS.
You shouldn't have prod level s3, or aws creds accessible openly in your environment. If someone can steal those values, they can steal the code, and pretty much everything else. This is very bad.
For prod (and possibly staging), use a lib that loads in values securely from an actual secrets service.
"The environment" is not "environment variables" and not ".env files"
For cloud services, it would typically be called a vault. But it could also be a hardware security module (HSM) with bring-your-own-key (BYOK, eg for certificates.)
Ansible also calls it a vault and encrypts it with a password — that file you can check into version control.
It's still completely correct. You don't have to use environment variables to store the environment. It can be stored in a secrets management system and loaded on-demand.
The point is that you must keep secrets, and anything environment-specific, out of the code. Follow the spirit of the law, not the letter.
Firmly agree. A lot of sibling comments are talking about environment mutation (which does have issues); I want to talk about environment read access.
The environment is a standard, locate-able, read-only at runtime k/v store in every process. That makes it an incredibly juicy target for exploits. There are tons of remote exploits well short of RCE which can access all or part of a server process's environment. If that environment contains secrets for everything that process might do, that's asking for trouble.
Consider a user-facing webserver with a rarely-used, admin-only route that talks to AWS APIs. Unless it's deployed on AWS and using IMDS, the 12-factor best practices say there should be AWS credentials in its environment.
Consider a service which, at startup, opens a connection to a telemetry/logging system, then drops privileges and handles requests. 12-factor best practices say there should be a secret for that telemetry system in its environment.
Additional examples abound. Most applications (even ones that aren't internet-facing web servers) use configured secrets infrequently--often only once, to open connections to external services--and not during the vast majority of requests they serve, but we put all secrets in the environment anyway.
Vaults don't automatically solve this problem either; many vaults provide secrets to applications by injecting them into process environment at start.
Good secret management at runtime should ideally be:
1. Mutable or at least delete-able. I really wish there were ways to remove environment variables after they're used (so I could say "once you have an authenticated, open socket or a refreshable auth token to $service, remove the initial login secret from memory entirely"), but absent highly complex multi-process/re-exec dances, that doesn't really exist. If, in Python, you 'del os.environ["foo"]', you haven't modified the environment segment of your program's memory.
2. Not in one common/uniform memory area or key-value API. Hell, it's slightly preferable to have secrets be stored piecemeal in regular variables in memory scattered around your code. Those are going to be slightly harder to find for malware that gets a foothold--security by obscurity, true, but the environment memory block/API is such a tempting and easy target that it buys you a bit more than a false sense of security here.
3. Ideally, stored or encrypted in memory (for secrets that have to stay in memory) such that an exploit which can read process memory doesn't get them for free. Some vaults have a host-local sidecar which provides secrets or a decryption key for them; that way, if an attacker gets memory-read without RCE they can't just exfil a memory image and figure out the decryption key later, but you don't have to be reliant on a remote networked service's uptime for all secret accesses. Even if you don't go that far, securing secrets in-memory at least gives you the option of doing zero-trust stuff based on request payloads, or even just making good-hygiene backend APIs that encode "you can only read the value for secret X if the request is for an admin route and authenticated" (which is a good idea for internet-exposed services with seldom-used risky secrets anyway, but doesn't help with parts 1 and 2 if that API is just wrapping env.get() or whatever).
> Besides being bad advice
What makes it bad advice?
> this had the second-order effect of leading devs to believe they could put all their local env secrets in ~/.bashrc files
You need some way to pass secrets to the app; doesn't every other way also suffer the same kind of issue?
The unwritten assumption in 12 Factor is: the environment is secure. For example, a production system should always have a secure means of setting environment variables. Said another way: If a random dev can change an environment variable in production either directly by logging in or indirectly by pushing code then there is something very very wrong.
If the dev is pushing code to production they should not simultaneously be pushing environment configs, this is doing two logically distinct things at once: Changing application behavior AND reconfiguring the server environment.
If the dev is adding secrets to their local config and they’re pushing that config to insecure places that means their deployment pipeline is broken and it should be fixed. .env is never committed to source for this reason, for example.