mirror of
https://github.com/kennethreitz/12factor.git
synced 2026-06-05 23:10:17 +00:00
a final once-over
This commit is contained in:
@@ -5,19 +5,15 @@ A [codebase](/codebase) is transformed into a (non-development) deploy through t
|
||||
|
||||
* The *build stage* is a transform which converts a code repo into an executable bundle known as a *build*. Using a version of the code at a commit specified by the deployment process, the build stage fetches and vendors [dependencies](/dependencies) and compiles binaries and assets.
|
||||
* The *release stage* takes the build produced by the build stage and combines it with the deploy's current [config](/config). The resulting *release* contains both the build and the config and is ready for immediate execution in the execution environment.
|
||||
* The *run stage* (also sometimes referenced as "at runtime") runs the app in the execution environment, by launching some set of the app's [processes](/processes) against a selected release.
|
||||
|
||||
**The twelve-factor app uses strict separation between the build, release, and run stages.** It is impossible to make changes to the code at runtime, since there is no way to propagate those changes back to the build stage. Config is accessible at runtime but not at build time, since config can change without triggering a build.
|
||||
* The *run stage* (also known as "runtime") runs the app in the execution environment, by launching some set of the app's [processes](/processes) against a selected release.
|
||||
|
||||

|
||||
|
||||
A *release* is a combination of a build (the executable bundle generated in the build stage) and a [config](/config) (the set of environment variables which determine runtime behavior).
|
||||
**The twelve-factor app uses strict separation between the build, release, and run stages.** For example, it is impossible to make changes to the code at runtime, since there is no way to propagate those changes back to the build stage.
|
||||
|
||||
New builds always trigger new releases (since the build has been updated). Config changes also trigger a new release (since the config has been updated). A release requires restarting all running processes in order to bring all processes onto the new release.
|
||||
|
||||
Deployment tools typically offer release management tools, most notably the ability to roll back to a previous release. For example, the [Capistrano](https://github.com/capistrano/capistrano/wiki) deployment tool stores releases in a subdirectory named `releases`, where the current release is a symlink to the current release directory; and offers a `rollback` command.
|
||||
Deployment tools typically offer release management tools, most notably the ability to roll back to a previous release. For example, the [Capistrano](https://github.com/capistrano/capistrano/wiki) deployment tool stores releases in a subdirectory named `releases`, where the current release is a symlink to the current release directory. Its `rollback` command makes it easily to quickly roll back to a previous release.
|
||||
|
||||
Every release should always have a unique release ID, such as a timestamp of the release (such as `2011-04-06-20:32:17`) or an incrementing number (such as `v100`). Releases are an append-only ledger and a release cannot be mutated once it is created. Any changes must create a new release.
|
||||
|
||||
Builds must be initiated by a developer, as an essential part of the process of deploying new code. Runtime execution, by contrast, can happen automatically in cases such as a server reboot, or a crashed process being restarted by the process manager. Therefore, the run stage should be kept to as few moving parts as possible, since problems that prevent an app from running can cause it to break in the middle of the night when no developers are on hand. The build stage can be more complex, since errors are always in the foreground for a developer who is driving the deploy. A failed build must abort the release process and avoid any disruption of the running app.
|
||||
Builds are initiated by the app's developers whenever new code is deployed. Runtime execution, by contrast, can happen automatically in cases such as a server reboot, or a crashed process being restarted by the process manager. Therefore, the run stage should be kept to as few moving parts as possible, since problems that prevent an app from running can cause it to break in the middle of the night when no developers are on hand. The build stage can be more complex, since errors are always in the foreground for a developer who is driving the deploy. A failed build must abort the release process and avoid any disruption of the running app.
|
||||
|
||||
|
||||
+4
-2
@@ -7,7 +7,9 @@ An app's *config* is everything that is likely to vary between [deploys](/codeba
|
||||
* Credentials to external services such as Amazon S3 or Twitter
|
||||
* Per-deploy values such as the canonical hostname for the deploy
|
||||
|
||||
Apps sometimes store config as constants in the code. This is a violation of twelve-factor, which requires **strict separation of config from code**. Config varies substantially across deploys, code does not. A litmus test for whether an app has all config correctly factored out of the code is whether the codebase could be open sourced as-is without compromising any credentials.
|
||||
Apps sometimes store config as constants in the code. This is a violation of twelve-factor, which requires **strict separatation of config from code**. Config varies substantially across deploys, code does not.
|
||||
|
||||
A litmus test for whether an app has all config correctly factored out of the code is whether the codebase could be made open source at any moment, without compromising any credentials.
|
||||
|
||||
Another approach to config is the use of config files which are not checked into revision control, such as `config/database.yml` in Rails. This is a huge improvement over using constants which are checked into the code repo, but still has weaknesses: it's easy to mistakenly check in a config file to the repo; there is a tendency for config files to be scattered about in different places and different formats, making it hard to see and manage all the config in one place. Further, these formats tend to be language- or framework-specific.
|
||||
|
||||
@@ -15,5 +17,5 @@ Another approach to config is the use of config files which are not checked into
|
||||
|
||||
Another aspect of config management is grouping. Sometimes apps batch config into named groups (often called "environments") named after specific deploys, such as the `development`, `test`, and `production` environments in Rails. This method does not scale cleanly: as more deploys of the app are created, new environment names are necessary, such as `staging` or `qa`. As the project grows further, developers may add their own special environments like `joes-staging`, resulting in a combinatorial explosion of config which makes managing deploys of the app very brittle.
|
||||
|
||||
In a twelve-factor app, env vars are granular controls, each fully orthogonal to other env vars. They are never grouped together as "environments," but instead are independently controllable for each deploy. This is a model that scales up smoothly as the app naturally expands into more deploys over its lifetime.
|
||||
In a twelve-factor app, env vars are granular controls, each fully orthogonal to other env vars. They are never grouped together as "environments," but instead are independently managed for each deploy. This is a model that scales up smoothly as the app naturally expands into more deploys over its lifetime.
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ Most programming languages offer a packaging system for distributing support lib
|
||||
|
||||
**A twelve-factor app never relies on implicit existence of system-wide packages.** It declares all dependencies, completely and exactly, via a *dependency declaration* manifest. Furthermore, it uses a *dependency isolation* tool during execution to ensure that no implicit dependencies "leak in" from the surrounding system. The full and explicit dependency specification is applied uniformly to both production and development
|
||||
|
||||
For example, [Gem Bundler](http://gembundler.com/) for Ruby offers the `Gemfile` manifest format for dependency declaration and `bundle exec` for dependency isolation. In, Python there are two separate tools for these steps -- [Pip](http://www.pip-installer.org/en/latest/) is used for declaration and [Virtualenv](http://www.virtualenv.org/en/latest/) for isolation. Java has a variety of build tools, such as [Maven](http://maven.apache.org/), which handle dependency declaration, and a limited `CLASSPATH` provides dependency isolation. Even C has Autoconf for dependency declaration, and static linking can provide dependency isolation. No matter what the toolchain, dependency declaration and isolation must always be used together -- only one or the other is not sufficient to satisfy twelve-factor.
|
||||
For example, [Gem Bundler](http://gembundler.com/) for Ruby offers the `Gemfile` manifest format for dependency declaration and `bundle exec` for dependency isolation. In, Python there are two separate tools for these steps -- [Pip](http://www.pip-installer.org/en/latest/) is used for declaration and [Virtualenv](http://www.virtualenv.org/en/latest/) for isolation. Even C has [Autoconf](http://www.gnu.org/s/autoconf/) for dependency declaration, and static linking can provide dependency isolation. No matter what the toolchain, dependency declaration and isolation must always be used together -- only one or the other is not sufficient to satisfy twelve-factor.
|
||||
|
||||
One benefit of explicit dependency declaration is that it simplifies setup for developers new to the app. The new developer can check out the app's codebase onto their development machine, requiring only the language runtime and dependency manager installed as prerequisites. They will be able to set up everything needed to run the app's code with a deterministic *build command*. For example, the build command for Ruby/Bundler is `bundle install`, while for Clojure/Leiningen it is `lein deps`.
|
||||
One benefit of explicit dependency declaration is that it simplifies setup for developers new to the app. The new developer can check out the app's codebase onto their development machine, requiring only the language runtime and dependency manager installed as prerequisites. They will be able to set up everything needed to run the app's code with a deterministic *build command*. For example, the build command for Ruby/Bundler is `bundle install`, while for Clojure/[Leiningen](https://github.com/technomancy/leiningen#readme) it is `lein deps`.
|
||||
|
||||
Twelve-factor apps also do not rely on the implicit existence of any system tools. Examples include shelling out to ImageMagick or `curl`. While these tools may exist on many or even most systems, there is no guarantee that they will exist on all systems where the app may run in the future, or whether the version found on a future system will be compatible with the app. If the app needs to shell out to a system tool, that tool should be vendored into the app.
|
||||
|
||||
@@ -11,7 +11,7 @@ Historically, there is a substantial gap between development (a developer making
|
||||
|
||||
* The time gap is small: a developer may write code and have it deployed hours or even just minutes later.
|
||||
* The personnel gap is small: developers who wrote code are closely involved in deploying it and watching its behavior in production.
|
||||
* Thus, the tools gap between development and production environments should also be made small.
|
||||
* Thus, it follows that the tools gap between development and production environments should also be made small.
|
||||
|
||||
Summarizing the above into a table:
|
||||
|
||||
@@ -38,7 +38,7 @@ Summarizing the above into a table:
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
[Backing services](/backing-services), such as the app's database, queueing system, or cache, is one area where dev/prod parity is important. Many languages offer libraries which simplify access to the backing service, including adapters to different types of services. Some examples are in the table below.
|
||||
[Backing services](/backing-services), such as the app's database, queueing system, or cache, is one area where dev/prod parity is important. Many languages offer libraries which simplify access to the backing service, including *adapters* to different types of services. Some examples are in the table below.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
|
||||
@@ -9,6 +9,6 @@ Processes shut down gracefully when they receive a [SIGTERM](http://en.wikipedia
|
||||
|
||||
For a worker process, graceful shutdown is achieved by returning the current job to the work queue. For example, on [RabbitMQ](http://www.rabbitmq.com/) the worker can send a [`NACK`](http://www.rabbitmq.com/amqp-0-9-1-quickref.html#basic.nack); on [Beanstalkd](http://kr.github.com/beanstalkd/), the job is returned to the queue automatically whenever a worker disconnects. Lock-based systems such as [Delayed Job](https://github.com/collectiveidea/delayed_job#readme) need to be sure to release their lock on the job record. Implicit in this model is that all jobs are [reentrant](http://en.wikipedia.org/wiki/Reentrant_%28subroutine%29), which typically is achieved by wrapping the results in a transaction, or making the operation [idempotent](http://en.wikipedia.org/wiki/Idempotence).
|
||||
|
||||
Processes should also be robust against sudden death, in the case of a failure in the underlying hardware. While this is a much less common occurrence than a graceful shutdown with `SIGTERM`, it can still happen. A recommended approach is use of a robust queueing backend, such as Beanstalkd, that returns jobs to the queue when clients disconnect or time out. Either way, a twelve-factor app is architected to handle unexpected, non-graceful terminations. [Crash-only design](http://lwn.net/Articles/191059/) takes this concept to its [logical extreme](http://couchdb.apache.org/docs/overview.html).
|
||||
Processes should also be robust against sudden death, in the case of a failure in the underlying hardware. While this is a much less common occurrence than a graceful shutdown with `SIGTERM`, it can still happen. A recommended approach is use of a robust queueing backend, such as Beanstalkd, that returns jobs to the queue when clients disconnect or time out. Either way, a twelve-factor app is architected to handle unexpected, non-graceful terminations. [Crash-only design](http://lwn.net/Articles/191059/) takes this concept to its [logical conclusion](http://couchdb.apache.org/docs/overview.html).
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ Logs are the [stream](http://adam.heroku.com/past/2011/4/1/logs_are_streams_not_
|
||||
|
||||
**A twelve-factor app never concerns itself with routing or storage of its output stream.** It should not attempt to write to or manage logfiles. Instead, each running process writes its event stream, unbuffered, to `stdout`. During local development, the developer will view this stream in the foreground of their terminal to observe the app's behavior.
|
||||
|
||||
In staging or production deploys, each process' stream will be captured by the execution environment, collated together with all other streams from the app, and routed to one or more final destinations for viewing and long-term archival. These archival destinations are not visible to or configurable by the app, and instead are completely managed by the execution environment.
|
||||
In staging or production deploys, each process' stream will be captured by the execution environment, collated together with all other streams from the app, and routed to one or more final destinations for viewing and long-term archival. These archival destinations are not visible to or configurable by the app, and instead are completely managed by the execution environment. Open-source log routers [Logplex](https://github.com/heroku/logplex) and [Fluent](https://github.com/fluent/fluentd).
|
||||
|
||||
The event stream for an app can be routed to a file, or watched via realtime tail in a terminal. Most significantly, the stream can be sent to a log indexing and analysis system such as [Splunk](http://www.splunk.com/), or a general-purpose data warehousing system such as [Hadoop/Hive](http://hive.apache.org/). These systems allow for great power and flexibility for introspecting an app's behavior over time, including:
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
## VI. Processes
|
||||
### Stateless processes handle application logic
|
||||
|
||||
The business logic of an app is codified in its [codebase](/codebase). The code is executed in the execution environment as one or more *processes*.
|
||||
The app is executed in the execution environment as one or more *processes*.
|
||||
|
||||
In the simplest case, the code is a stand-alone script, the execution environment is a developer's local laptop with an installed language runtime, and the process is launched via the command line (for example, `python my_script.py`). On the other end of the spectrum, a production deploy of a sophisticated app may use many [process types, instantiated into zero or more running processes](/concurrency).
|
||||
|
||||
**Twelve-factor processes are stateless and [share-nothing](http://en.wikipedia.org/wiki/Shared_nothing_architecture).** The filesystem sitting beneath the process is either read-only (attempting a write will produce an error), or completely ephemeral (when the process terminates, all state written to disk will be discarded). Any data that needs to persist must be stored in a stateful [backing service](/backing-services), typically a database.
|
||||
**Twelve-factor processes are stateless and [share-nothing](http://en.wikipedia.org/wiki/Shared_nothing_architecture).** Any data that needs to persist must be stored in a stateful [backing service](/backing-services), typically a database.
|
||||
|
||||
Process memory space or an ephemeral filesystem can potentially be used as a brief, single-transaction cache. For example, downloading a large file, operating on it, and storing the results of the operation in the database. The twelve-factor app never assumes that anything cached in memory or on disk will be available on a future request or job -- with many processes of each type running, chances are high that a future request will be served by a different process. Even when running only one process, a restart (triggered by code deploy, config change, or the execution environment relocating the process to a different physical location) will wipe all filesystem and in-memory state.
|
||||
The memory space or filesystem of the process can be used as a brief, single-transaction cache. For example, downloading a large file, operating on it, and storing the results of the operation in the database. The twelve-factor app never assumes that anything cached in memory or on disk will be available on a future request or job -- with many processes of each type running, chances are high that that a future request will be served by a different process. Even when running only one process, a restart (triggered by code deploy, config change, or the execution environment relocating the process to a different physical location) will usually wipe out all local (e.g., memory and filesystem) state.
|
||||
|
||||
Asset packagers (such as [Jammit](http://documentcloud.github.com/jammit/) or [django-assetpackager](http://code.google.com/p/django-assetpackager/)) use the filesystem as a cache for compiled assets. A twelve-factor app prefers to do this compiling during the [build stage](/build-release-run), such as the [Rails asset pipeline](http://ryanbigg.com/guides/asset_pipeline.html), rather than at runtime.
|
||||
|
||||
Some web systems rely on "sticky sessions" -- that is, caching user session data in memory of the app's process and expecting future requests from the same visitor to be routed to the same process. Sticky sessions are a violation of twelve-factor and should never be used or relied upon. Session state is a good candidate for a datastore that offers time-expiration, such as [Memcached](http://memcached.org/) or [Redis](http://redis.io/).
|
||||
Some web systems rely on ["sticky sessions"](http://en.wikipedia.org/wiki/Load_balancing_%28computing%29#Persistence) -- that is, caching user session data in memory of the app's process and expecting future requests from the same visitor to be routed to the same process. Sticky sessions are a violation of twelve-factor and should never be used or relied upon. Session state data is a good candidate for a datastore that offers time-expiration, such as [Memcached](http://memcached.org/) or [Redis](http://redis.io/).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user