One of the defining characteristics of Habitat is the packaging technology. Packaging is in no way a new concept. Operating System vendors have solved this problem for sometime using tools like RPM, Debian Packages, Yum, Apt, etc. Virtually all programming languages have their own packaging formats as well, such as NPM, Yarn, pip, Cargo, bundler, etc. The problem with these packaging formats is that they are all exclusively designed to solve a single problem, dependency management. They don’t take into account the lifecycle of the application closer to deployment time. Those decisions are all made independent of the artifact that is packaged.
Build Artifacts vs Deployable Artifacts
To understand this better, it’s best to think of two different types of artifacts, build artifacts and deployable artifacts. Build artifacts are the artifacts produced that contain your application code (or the compiled code) itself. Your application build artifact will have dependencies on other build artifacts, and together you use those artifacts to run your application. Those artifacts may or may not have the required scripts and configuration files required to run the application.
Deployment artifacts are the collection of the build artifacts required, the scripts required to run your application, language runtimes, configuration files, etc. A current day example of a deployment artifact would be a container. The challenge with packaging formats and the build artifacts they create is that the resulting artifacts don’t inform the deployment artifacts of what is required to run the packaged application.
This is where Habitat’s concept of packaging is different. Habitat Plans allow you to not only define how the software is built, it also allows you to define aspects of what the software requires to run. The first aspect of this consists of the hooks Habitat allows you to define for your application. Habitat allows you to template out scripts to manage the run lifecycle of you application in these hooks, and the hooks will be interpreted at application launch time. This allows you to template these hooks, and they can be regenerated based on the environment the application is launching in.
The second aspect of defining runtime requirements is through the ability to explicitly declare services an application requires to start. This is done by including a pkg_binds variable in the plan.sh of your application. When an application is started, Habitat will check to see if the application’s dependent services are specified to bind to, and if not, Habitat will not start the application.
Lastly, Habitat allows you to define the services exposed by an application package. The pkg_exports and pkg_exposes options in a plan allow you to declare what configurations will be exposed by the application, and the ports an application will run its services on.
Creating Deployable Artifacts
So why is this important? When your software is built you don’t always know where or how it will be deployed. Habitat allows you to delay that decision as long as possible, and then export your application in the desired deployable artifact format. Because you’ve exposed information about how the software is ran in the build artifact, any of this information is available to the export process. You can see how this works by exporting a Habitat package as a Docker container.
If you think about the guiding principles of 12 Factor applications, you can see that these Habitat packaging concepts fit very cleanly into this model. In particular, Habitat packaging allows you to treat backing services as attached resources (factor 4), separate the build & run stages (factor 5), and export services via port binding (factor 7). Habitat packaging also allows you to decouple yourself from the underlying operating system. No longer are you dependent on packaging systems designed with assumptions of how we ran systems 20 years ago. Habitat provides you with these modern paradigms to run your applications in a modern way.