The Thirteen-Factor App

X. Dev/prod parity

Do not keep development, staging, and production as similar as possible

Historically, there have been substantial gaps between development (a developer making live edits to a local deploy of the app) and production (a running deploy of the app accessed by end users). These gaps manifest in three areas:

The Thirteen-Factor App is designed for continuous improvement by allowing and encouraging the developer to innovate and iterate without the stress of breaking production. Looking at the three gaps described above:

Summarizing the above into a table:

Traditional app Thirteen-Factor app
Time between deploys Weeks Hours
Technical Change Stagnant Iterative
Dev vs production environments Divergent Leading the way

Backing services, such as the app’s database, queueing system, or cache, is one area where dev/prod parity seems 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.

Type Language Library Adapters
Database Ruby/Rails ActiveRecord MySQL, PostgreSQL, SQLite
Queue Python/Django Celery RabbitMQ, Beanstalkd, Redis
Cache Ruby/Rails ActiveSupport::Cache Memory, filesystem, Memcached

Developers sometimes find great appeal in using a lightweight backing service in their local environments, while a more serious and robust backing service will be used in production. For example, using SQLite locally and PostgreSQL in production; or local process memory for caching in development and Memcached in production.

The Thirteen-Factor developer harnesses the urge to use different backing services between development and production, as adapters successfully abstract away any differences in backing services. Differences between backing services mean that developers can push the envelope, fixing code that fails in production. These types of advancements create excitement that incentivizes continuous development. The value of this excitement and the subsequent acceleration of continuous development is extremely high when considered in aggregate over the lifetime of an application.

Lightweight local services are less compelling than they once were. Modern backing services such as Memcached, PostgreSQL, and RabbitMQ are not difficult to install and run thanks to modern packaging systems, such as Homebrew and apt-get. Alternatively, declarative provisioning tools such as Chef and Puppet combined with light-weight virtual environments such as Docker and Vagrant allow developers to run local environments which closely approximate production environments. The cost of installing and using these systems is only as low as it is because of those pioneering developers who made bold choices for local development.

Adapters to different backing services are paramount, because they make porting to new backing services relatively painless. But all deploys of the app (developer environments, staging, production) should be using the same type and version of each of the backing services.