Cloud development has evolved since this manifesto came out, but the principles still apply to cloud native application development.
One codebase tracked in revision control, many deploys
Having a single codebase per application is recommended. You can deploy the applications into multiple environments (development, test, production, staging).
For cloud native applications, this means that each service or function would have its own code repository and CI/CD flow. You work on the code locally, you push commits to the code repository, and CI/CD builds and deploys your application.
Explicitly declare and isolate dependencies
Your applications need to explicitly declare and isolate dependencies. If you’re using a third-party library, package it with your application.
For example, in Node.js, you would use
package.json to declare all dependencies. Similarly, in Python, you can use
requirements.txt and then use package or dependency managers like npm, pip, or Maven to install the dependencies.
Isolating the dependencies is also essential. Never depend on the host machine to have any dependencies installed. You can rely only on your code to have dependencies packaged.
The use of containers has decreased dependency-based issues. With Docker, you can package everything together in an image. In a
Dockerfile, you declare and install everything that’s needed: tools that your app needs and any other libraries. Packaging your application in such a way makes it portable, and you can run it anywhere in the same way.
Store configuration in the environment
Don’t include configuration with your application. For example, if you connect to a database, don’t hardcode the connection string inside the Docker image.
Instead, define the connection string as part of the environment. You can use the same Docker image and deploy it into production, and your container reads the environment’s configuration settings. Making the configuration part of the environment lets you re-use the same application image.
Treat backing services as attached resources
All applications have backing services: a database, a cache, or another service. The guidance here is to treat all backing services as third-party services and never connect to them directly.
If you combine this practice with the configuration one, you access the service through configuration settings from the environment. This practice also helps make your applications loosely coupled.
Strictly separate build and run stages
After you package your application and have the configuration, you need to build, release, and run your services.
Ideally, this process is fully automated and doesn’t require any human intervention. The builds and releases should be reproducible and have a unique identifier (usually the application version and a timestamp).
After you create a build or a release, you can’t change it. If you need to update a build or release, you need to restart the stages.
Execute the app in one or more stateless processes
Your application shouldn’t rely on any state that is stored locally, either on a disk or in memory. Use the shared-nothing architecture, in which you don’t share the memory or storage.
In a cloud native environment, the infrastructure that you run your applications on can be replaced at any time. Therefore, you can’t know which hosts your application will be run on. Instead, store all application states externally, in a database, cache, or something similar.
Following this approach allows for elasticity, and you never have to worry about whether the state is on the host.
Export services via port binding
This practice means that you should be able to address applications by specific port numbers. Doing so lets you use names when referring to them and correctly load balance between different instances and port numbers.
Scale out via the process model
Design your applications so that they can scale independently and horizontally to achieve better resource usage. You should also be able to run multiple instances of your application at the same time. You can accomplish this by share-nothing architecture and data isolation.
Maximize robustness with fast startup and graceful shutdown
Applications can be started and stopped at any time. Strive for fast startup times, responsiveness, and graceful shutdowns of your applications.
With fast startup time, you can scale up faster when needed or quickly replace any down or failing instances. For graceful shutdowns, you can listen for a SIGTERM signal in the application. You can then close any open connections, flush the logs, or do anything needed to terminate the application gracefully.
Keep development, staging, and production as similar as possible
If you follow the previous practices, you’re already packaging dependencies with your applications and using environment configuration. Doing all that also lets you quickly deploy the packages to different environments and keep them synchronized.
Treat logs as event streams
Keep logs and log management separate from the applications. Your applications should write logs to standard output, and a logging agent such as Fluentd should capture the logs and aggregate them in a central place. That way, even if your application crashes, the logging agent can capture the logs. Storing logs in a central place lets you to go back and investigate any application issues.
The logging strategy is crucial in a cloud native environment because there are many moving parts. Each log should give you visibility into the behavior of the application.
Run admin and management tasks as one-off processes
Run administrative or management tasks as short-lived processes. Examples of such tasks include migrating databases, repairing broken data, creating reports, and creating one-time scripts to fix something.
These processes should run in identical environments as the application, using the same release, codebase, and configuration. Ship the administrative code together with the application code so that you can avoid any synchronization issues.
In this post, you learned about the Twelve-Factor App manifesto and the best practices from it that you should follow when developing cloud native applications.
Every use case is different. The only way to know if Oracle Cloud Infrastructure is right for you is to try it. You can select either the Oracle Cloud Free Tier or a 30-day free trial, which includes US$300 in credit to get you started with a range of services, including compute, storage, and networking.