Skip to content

12-Factor App

Let's look at 12 principles that will help create software with proper engineering approaches ⬇️

First Factor

📄 Codebase. One codebase tracked in a version control system for each application. Multiple deploys of the same codebase should be possible.

In practice, your application will be deployed in many environments. And this should be possible from the same branch. Containers allow running one application multiple times, meaning all environments will have the same codebase.

Second Factor

📄 Dependencies. Explicitly declare and isolate dependencies.

This principle says that we should explicitly specify our dependencies. In our case, we explicitly list all dependencies in the requirements.txt file. Also, Docker itself allows you to "pack" all dependencies into a single container and make the container itself the deployment unit of the software product.

Third Factor

📄 Configs. Store config in the environment.

All application configurations should be extracted from the application itself to the level of, for example, a Docker container, so they can be easily changed. Let's look at the example of the docker-compose file and a Python application. We will create a new environment variable that we will read and assign its connection string in the SQLALCHEMY configuration:

connectionString = os.environ.get("DB_CONNECTION","") app.config['SQLALCHEMY_DATABASE_URI'] = connectionString

We add a new environment variable to the Dockerfile:

ENV DB_CONNECTION="default"

And in docker-compose:

 pythonapp:
   image: app:1.2
   build:
     context: .
     dockerfile: Dockerfile.app
   ports:
     - "8080:8080"
   environment:
     - APP_ENV=Development
     - DB_CONNECTION=mysql+mysqlconnector://app_user:1234@my-sql/app_db

This way, the same application can be run in different environments.

Forth Factor

📄 Backing services. Treat backing services as attached resources.

Backing services refer to auxiliary services that the application consumes over the network, such as databases. For example, you can replace one MySQL database with another without making any changes to the application code itself. In critical situations, this can speed up problem resolution in a production environment. Docker helps with this — we can simply start a container from an existing Image with an updated connection string passed through an environment variable inside the container.

Fifth Factor

📄 Build, release, run. Strictly separate build and run stages.

Your code or your team's code is essentially built and packaged into a Docker container — this is the build stage. The Image is our release candidate of the code that can be simultaneously deployed to different environments. Each release should have a unique name, and Image tags can serve this purpose. Semantic versioning is commonly used:

1.2.2
MAJOR.MINOR.PATCH
  • MAJOR is changed when we make changes to functionality or API that are no longer compatible with previous versions of the product.
  • MINOR is changed when we add new functionality, but the old functionality still works as before and is compatible with the previous version of the product.
  • PATCH is changed when we make minor changes or bug fixes.

Sixth Factor

📄 Processes. Execute the app as one or more stateless processes.

The application should not store anything locally. Any data that needs to be shared with other services should be written to backing services, for example, a database. Docker helps with this too: the container already encapsulates the application as a single stateless service, unless we explicitly violate this principle. Reading files from the local file system does not violate this principle, but writing new files for a long period does.

Seventh Factor

📄 Port binding. Export services via port binding.

The application should be self-sufficient and should not depend on other applications. For example, the application should not depend on a third-party web server such as Apache HTTPD or Tomcat. Both of these applications are servers that simply run other applications.

The application should be able to expose its own port on which it will start listening for requests on the local machine. Docker also helps with this using the command:

docker run --name some-server -d -p 8080:8080 some_image:1.0.0

Eighth Factor

📄 Concurrency.

To scale an application so it can handle more tasks, you should launch new processes rather than threads within a single process. In practice, this means that if you have a high load, start more containers and distribute that load!

Ninth Factor

📄 Disposability.

Containers should start quickly and stop or be removed safely. For safe termination, use the docker stop command, and for less safe termination use docker kill.

Tenth Factor

📄 Dev/Prod Parity. Keep development, staging, and production as similar as possible.

Environments should be identical and have the same configurations. Changes from developers should be deployed to the shared environment as frequently as possible — ideally as soon as they are merged into the master branch. Also, the set of applications you use across different environments should be the same. That is, you should not use MySQL in one environment and MSSQL in another.

In practice, Docker handles this perfectly: the same Image across all environments!

Eleventh Factor

📄 Logs. Treat logs as event streams.

Treat logs as a stream of events and sort them in chronological order. Logs should add transparency to the application's operation. We can, for example, write logs when the application starts, performs an action, or even when an error occurs, to make it easier to fix later.

Docker allows viewing all logs that go to srdout, sdterr and centrally storing them.

Twelfth Factor

📄 Admin processes. Run admin/management tasks as one-off processes.

If you need to change the database schema (schema migrations) or add initial data to the database at application startup (database seeding), use a separate Docker container for this.