Twelve-Factor App: the Heroku method that survived containers
In January 2018, I come across the Twelve-Factor method (and I immediately file it among the practices I already did without naming it): three readings that complete each other (the reference site by Adam Wiggins , the adaptation to microservices by Ben Horowitz on the nginx blog and the report Kevin Hoffman draws from it at O’Reilly ). Suddenly a word for what I had been doing for years. Before, I already knew (and more or less applied) KISS, YAGNI and DRY, then SOLID; the V-model and AGILE were part of the landscape, each with its flaws that I could enumerate without needing anyone to explain them to me. Twelve-Factor, for its part, is not for the general public: it is an engineer’s checklist, written for Heroku, that says in black and white what an application must guarantee to survive production and its own redeployments.
Where it comes from
The method is written by Adam Wiggins, Heroku’s cofounder, around 2011, for the platform he is building (the document distills the observation of hundreds of deployed applications, in a format explicitly inspired by Martin Fowler’s books). It targets software delivered as a service, what people then call a SaaS. Its site has barely moved, its last displayed update is 2017, which says as much about maturity as about a beginning of ageing I come back to below. The Reactive Manifesto of 2013 says much the same on another axis: a reactive system is resilient, elastic and message-driven, which joins the concurrency and disposability factors. 12factor.net
The foundation: code, dependencies, config
The first four factors hold the application by its foundation: a single codebase kept in version control, deployed many times, one repository per application and not one repository per environment; dependencies declared explicitly and isolated, never assumed present on the target machine; configuration stored in the environment, separate from the code (it is the factor that ages worst, I come back to it below); and external services, database, message queue or cache, treated as attached resources one can replace without touching the code.
The cycle: build, release, run
The fifth factor imposes a strict separation between building, publishing and running, with an immutable artefact between each step. Processes are stateless and keep nothing between two requests, which allows the horizontal scale-up of the eighth factor. The application exposes its service by listening on a port, it does not rely on an external web server to exist. And disposability closes the loop: fast startup and clean shutdown, so an orchestrator can kill an instance without ceremony.
Production: parity, logs, admin
The last three factors concern life in production. Dev/prod parity wants the gap between development, staging and production to be minimal, same language, same support services, continuous deployment rather than big batch. Logs are a stream of events the application writes to its standard output, without caring about their destination, which the runtime then routes. And administration tasks, migrations or one-off scripts, run as one-off processes in the same environment as the application.
Where it rubs
The config factor takes a hit: writing secrets into the process environment exposes them to any process inspection, to memory dumps and to poorly filtered logs, which secret managers like Vault fix by keeping the secret out of the environment and injecting it on demand. The logs factor has also evolved: writing to standard output remains the right rule, but the destination is no longer a file on disk, it is a collector, Fluentd or journald, that routes it. The stateless-process dogma, finally, runs into the reality of databases and queues, where state is precisely what one wants to keep (the method did not ignore it, it stored these states in attached resources, which everybody ends up understanding).
Translated into containers and Kubernetes
The good surprise is that each factor translates almost term for term: the image materialises the immutable build and release, the running container, the run; the process environment becomes an environment variable injected from a ConfigMap for the non-sensitive part and a Secret for the rest; port binding becomes the port exposed by the container behind a Service; disposability becomes a liveness and readiness probe (it is that very mechanism that makes scale-to-zero possible, an instance can only be put to sleep if it can be recognised as ready); concurrency becomes the replica count and autoscaling; logs as a stream end up on the container’s standard output, which the runtime collects; admin tasks become Job or CronJob; and dev/prod parity is the very argument of containers and Kubernetes, the same environment everywhere, from the developer’s machine to production. It is also the clean positive of the R.A.C.H.E method : where the satire names the anti-practices, these twelve factors name the contracts. I re-read them once a year. I do not often find a reason to depart from them.