In this module, you'll build a basic web application that creates animated GIFs. Here's what you'll see:
You'll start by downloading starter code for the app. Then you'll write a Habitat plan that describes how to build and package the app. Then you'll build the Habitat package from the Studio and test it out. Finally, you'll export the Habitat package to a Docker container so you can access the app from your browser.
The web application is written in Ruby (specifically, using Sinatra). During the module, you'll discover how scaffolding helps you quickly build packages for Ruby and other common application types.
The web app uses ImageMagick to generate the animated GIF files and a Ruby gem called rmagick to connect the Ruby code to ImageMagick. You'll learn how to include these libraries in your plan so that your app has everything it needs to run.
For this module you're going to need Git and a text editor. You'll also need to:
Install Habitat and Docker
For this module, you'll need the Habitat command-line interface (CLI) and Docker on your workstation. You'll also need basic familiarity with how Habitat works.
The best way to get set up is to go through the Try Habitat module before you start this module.
Start the Try Habitat module
Upload your origin key to Builder
In the Try Habitat module, you uploaded two packages to Habitat Builder. The package upload process also uploads your origin key.
Shortly you'll build a new Habitat package and export it to Docker. The export process requires that your origin's public key exist in Builder. Let's make sure your public key is there so the export process goes smoothly.
Run the following
hab origin key upload command, replacing the file you see with your public key.
hab origin key upload --pubfile ~/.hab/cache/keys/learn-chef-20171002150046.pub» Uploading public origin key /Users/thomaspetchel/.hab/cache/keys/learn-chef-20171002150046.pub↑ Uploading /Users/thomaspetchel/.hab/cache/keys/learn-chef-20171002150046.pub 81 B / 81 B | [======================================================================================================] 100.00 % 418 B/s→ Using public key revision learn-chef-20171002150046 which already exists in the depot★ Upload of public origin key learn-chef-20171002150046 complete.
1. Get the sample app
From a terminal window (or PowerShell on Windows), start by moving to a working directory, for example, your home directory.
Next, clone the
habitat-building-with-scaffolding repo from GitHub.
git clone https://github.com/learn-chef/habitat-building-with-scaffolding.git
Next, move to the
2. Create the plan
Recall that a Habitat plan describes everything that's needed to build and package an app. The resulting package is often called an artifact.
A plan file is written in the core shell language of the target platform. When targeting Unix-based systems the plan file is a Bash script named
plan.sh. When targeting Windows, the plan file is a PowerShell script named
Every Habitat plan includes a plan file, but you'll often have additional files that describe your application's configuration or how the application should behave when it starts, stops, or restarts.
Run hab plan init to initialize a Habitat plan for your project. Then take a moment to look at the output.
hab plan init» Attempting autodiscovery No scaffolding type was provided. Let's see if we can figure out what kind of application you're planning to package. » We've detected a Ruby codebase→ Using Scaffolding package: 'core/scaffolding-ruby' » Constructing a cozy habitat for your app... Ω Creating file: habitat/plan.sh `plan.sh` is the foundation of your new habitat. It contains metadata, dependencies, and tasks. Ω Creating file: habitat/default.toml `default.toml` contains default values for `cfg` prefixed variables. Ω Creating directory: habitat/config/ `/config/` contains configuration files for your app. Ω Creating directory: habitat/hooks/ `/hooks/` contains automation hooks into your habitat. For more information on any of the files: https://www.habitat.sh/docs/reference/plan-syntax/ Ω Creating file: habitat/../.gitignore★ An abode for your code is initialized!
You see that
hab plan init created a few files and directories for your project, including a
plan.sh file. The Habitat plan exists in the
habitat directory. So not only does the automation travel with your application, but your Habitat plan also travels with your application's source code.
Also notice that
hab plan init detected that the application is written in Ruby. It was able to do so because the project conforms to a number of standard conventions that are common to Ruby web applications. For example, it has a
Gemfile, which provides Bundler a list of all the gems necessary for this project to run. It also has a Rackup configuration file,
config.ru, that provides a way to run the app.
hab plan init output tells us that it is using a scaffolding package based on the detected application. Think of scaffolding as a standard way to build a specific kind of application. You'll learn more about scaffolding in a bit. Besides Ruby, Habitat can detect other kinds of applications.
You can use the
--scaffolding argument to explicitly specify the kind of app you are building, for example,
hab plan init -s ruby.
Now's a good time to open the project in your text editor.
Take a look at your
plan.sh file. Here's an example. The value of
pkg_origin shows your origin.
The plan file defines a few details about your package, including its name, its origin (the company, team, or personal name who manages the package), its version, and the scaffolding that's used to perform common tasks.
A plan file includes everything your application needs to run. For a Ruby application, this includes the Ruby language, any gems your app needs, and the tools needed to build it. Although Habitat can help make your applications easier to deploy and manage, it often requires you to understand everything about your application. That's where scaffolding comes in. When your application follows common conventions, as in this example, scaffolding can provide many of these details for you.
3. Build the package
Before you build your package, let's take a closer look at what's happening in the Studio. You first saw the Studio in the Try Habitat module.
Start by running
hab studio enter to enter the Studio.
The first time you run this command (or when you upgrade your
hab version), Habitat downloads the Docker images needed to create it. This requires that Docker is running as a daemon/service in the background. When the download completes, Habitat runs the Docker container and logs you into the Studio.
You see that the Studio imports the origin keys that you created when you ran
hab setup. The Studio then runs a Supervisor in the background. Recall that the Supervisor is a process manager that runs Habitat artifacts.
Recall that the Studio is a special virtualized environment that's separate from your workstation, or host OS. We often call this environment a cleanroom. This cleanroom environment ensures that no libraries, build tools, or other dependencies are required from the external environment. It also ensures that nothing from the host is accidently pulled into your package.
You've probably heard the expression "It works on my machine!" when referring to an application that builds and runs fine in one environment, but fails in another. Building in a cleanroom environment helps ensure that packages are built consistently and completely, no matter where they're built.
The Studio is not completely empty. Run
ls to see the files in your current working directory.
Gemfile Gemfile.lock README.md assets config.ru habitat lib snorkelling-habicat.jpg snorkelling-habicat.png
Compare these file to the files in your text editor and you'll see they match. The Studio mounts the directory from where you ran
hab studio enter.
Mounting is useful because it enables you to change your project files and have those changes be seen in the Studio. This enables you to iteratively edit files on your workstation and then safely build in the Studio.
To see this in action, return to the plan file and change the package name to
habitatize. Here's an example (leave
pkg_origin with your origin name).
Return to the studio and show the contents of the plan file by running the command
build to build the package.
An error occurred while installing rmagick (2.16.0), and Bundler cannot continue.
Make sure that `gem install rmagick -v '2.16.0'` succeeds before bundling.
mv: can't rename '.bundle/config.prehab': No such file or directory
habitatize: Build time: 1m11s
habitatize: Exiting on error
For brevity, we omit most of the sample output here, but you see that the build failed due to the
rmagick Ruby gem.
We won't go into all the details of how a Ruby application is built, but an important step is to run
bundle install. Bundler reads the project's
Gemfile, determines which gems (or Ruby libraries) are needed, and then installs them.
However, the build fails because it is unable to find a number of things required to install the
4. Add a dependency
The build fails because you are still missing an important dependency, namely a dependency required by the
Let's take a look at the
rmagick gem's README file.
You see that the README specifies the OS and Ruby requirements. You also see that it requires a specific version of a library called ImageMagick.
Habitat knows how to build your project and its explicit dependencies, but it can't discover implicit dependencies. You need to provide these details in your plan.
In practice, you might search the web to learn how to install this library. If someone else hasn't built the package for you, then you may need to obtain the source code and build it yourself. With Habitat, the process is similar. Let's see if we can find a prebuilt package for ImageMagick.
Habitat Builder is a place for you to obtain core packages and packages provided by the Habitat community. You can also contribute your own packages back to the community.
From a browser, navigate to Habitat Builder. Then search for "imagemagick". Habitat package names are typically in all lowercase.
From the results you see there are a few published
imagemagick packages from different origins. Unless you know you want a package built by a specific vendor, you'll usually want to choose the one from the "core" origin. The "core" origin is the set of foundational packages that are managed and versioned by the core Habitat maintainers. This is the same origin that provided you with the Ruby scaffolding package.
core/imagemagick package to see more details. These details include how the package is built, its release history, and a few ways to run it or export it from the Studio.
To include this package in your app, you use the
pkg_deps setting in your plan file.
From your editor, add the line
pkg_deps=( core/imagemagick ) to your plan file. Here's a complete example.
pkg_deps=( core/imagemagick )
pkg_deps are only a few of the many settings you can use. Learn about other settings you can use in the documentation.
Save your plan file and return to the Studio. Then run
✓ Installed core/imagemagick/6.9.2-10/20170514150059
★ Install of core/imagemagick/6.9.2-10/20170514150059 complete with 2 new packages installed.
habitatize: I love it when a plan.sh comes together.
habitatize: Build time: 0m51s
Success! The output shows that
core/imagemagick was installed along with any packages that it depends on (that were not already installed). Environment settings such as
PKG_CONFIG_PATH are also updated to include the new dependency.
As you saw in the Try Habitat module, the package is built to the
results directory and is available from the Studio or your workstation. The file name is a combination of the origin name, package name, version, and timestamp. It ends with the
.hart extension, which stands for "Habitat artifact".
Some Ruby gems require native extensions to be built. A native extension is often a C or C++ library. Gems that require native extensions often surprise you during the deployment of a Ruby application. When you use a gem that requires native extensions you must ensure that the target has the appropriate build tools and libraries. Discovering this requirement during the build process is extremely valuable as it saves you from discovering the error during the deployment phase.
5. Run the package
Before you run the package, let's take a quick look at what's inside it.
You can think of a
.hart file as an archive. The build process leaves the unarchived package on disk. You can run the
ls command to see what's inside. Here's an example.
ls -l /hab/pkgs/learn-chef/habitatize/0.1.0/20171007152417
-rw-r--r-- 1 root root 1731 Oct 7 15:24 BUILDTIME_ENVIRONMENT
-rw-r--r-- 1 root root 392 Oct 7 15:24 BUILDTIME_ENVIRONMENT_PROVENANCE
-rw-r--r-- 1 root root 337 Oct 7 15:24 BUILD_DEPS
-rw-r--r-- 1 root root 1379 Oct 7 15:24 BUILD_TDEPS
-rw-r--r-- 1 root root 114 Oct 7 15:24 DEPS
-rw-r--r-- 1 root root 14 Oct 7 15:24 EXPORTS
-rw-r--r-- 1 root root 223024 Oct 7 15:24 FILES
-rw-r--r-- 1 root root 43 Oct 7 15:24 IDENT
-rw-r--r-- 1 root root 2301 Oct 7 15:24 MANIFEST
-rw-r--r-- 1 root root 123 Oct 7 15:24 PATH
-rw-r--r-- 1 root root 945 Oct 7 15:24 RUNTIME_ENVIRONMENT
-rw-r--r-- 1 root root 169 Oct 7 15:24 RUNTIME_ENVIRONMENT_PROVENANCE
-rw-r--r-- 1 root root 4 Oct 7 15:24 SVC_GROUP
-rw-r--r-- 1 root root 4 Oct 7 15:24 SVC_USER
-rw-r--r-- 1 root root 13 Oct 7 15:24 TARGET
-rw-r--r-- 1 root root 840 Oct 7 15:24 TDEPS
-rw-r--r-- 1 root root 11 Oct 7 15:24 TYPE
drwxr-xr-x 7 root root 4096 Oct 7 15:24 app
drwxr-xr-x 2 root root 4096 Oct 7 15:24 bin
drwxr-xr-x 2 root root 4096 Oct 7 15:24 config
-rw-r--r-- 1 root root 272 Oct 7 15:24 default.toml
drwxr-xr-x 2 root root 4096 Oct 7 15:24 hooks
drwxr-xr-x 2 root root 4096 Oct 7 15:24 libexec
-rw-r--r-- 1 root root 213 Oct 7 15:24 run
The artifact contains a number of directories. The app is located in the
app directory. You can explore the structure if you'd like. The documentation provides more information about the contents of a package.
The build process installs the package for you. You can now use
hab svc start to run the application as a service. The
hab svc start command takes the origin name and the package name as its argument, in the form
Run the folowing command to start the app. Recall that the
HAB_ORIGIN environment variable holds your origin name.
hab svc start $HAB_ORIGIN/habitatize
hab-sup(MN): Supervisor starting learn-chef/habitatize. See the Supervisor output for more details.
If you create a new Studio instance, your package will not be installed. To install it, run
hab pkg install PACKAGE_PATH, replacing
PACKAGE_PATH with the path to the your package in the
The Supervisor starts the application in the background. Run
sup-log to view the Supervisor log.
--> Tailing the Habitat Supervisor's output (use 'Ctrl+c' to stop)
habitatize.default(HK): init, compiled to /hab/svc/habitatize/hooks/init
habitatize.default(HK): Hooks compiled
habitatize.default(SR): Hooks recompiled
default(CF): Updated app_env.sh e662cfbd3c1ae9b64e51413150b51e40801d955f384b86da73ac8cb2a43d87c8
habitatize.default(SR): Configuration recompiled
habitatize.default(SV): Starting service as user=hab, group=hab
habitatize.default(O): [2017-10-07 15:53:34] INFO WEBrick 1.3.1
habitatize.default(O): [2017-10-07 15:53:34] INFO ruby 2.4.2 (2017-09-14) [x86_64-linux]
habitatize.default(O): [2017-10-07 15:53:34] INFO WEBrick::HTTPServer#start: pid=4524 port=8000
The Ruby web application is started and is listening on port 8000. Enter Ctrl+C to stop following the logs.
Now let's verify that the site is running. You can't visit the site from your workstation because the application is running in the Studio, which is isolated from your workstation. (You'll be able to see the site in your browser in the next step.)
However, you can use the
wget command to access the site from the Studio. Run
wget like this.
wget -qO- localhost:8000
<body style='background-color: #87B09A;'>
<h1>Meme Green Machine!</h1>
<form action="/habitatized" method="POST" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="Upload image">
You see an HTML page that includes a basic form that enables you to specify an image file. Although this is a great first step in verifying your app is up and running, it doesn't provide a useful way to interact with the site.
Although not required, you can also run
curl to access your site. But remember that
curl is not automatically installed in the Studio. Here's a refresher on how to install it.
6. Export to Docker
To see the running app from outside the Studio, you could run the
hab svc start command from your workstation, or host. However, Habitat does not yet support the ability to run services natively on every OS. Instead, let's export the package to Docker and run it as a container.
From the Studio, run the
hab pkg export docker command to export your package as a container. Replace the
.hart file you see here with yours.
hab pkg export docker ./results/learn-chef-habitatize-0.1.0-20171007152417-x86_64-linux.hart
The export process installs any additional components needed to prepare the Docker image with a Supervisor, brings in the application package, and then publishes the result to your local Docker image registry.
Now that you've created the Docker image, you no longer need to be in the Studio. Run the
exit command to log out.
When you leave the Studio, Habitat cleans up any intermediate files it created during the build process. All that remains of what was created in the Studio are the contents of the
results directory and the Docker image that you exported.
Run the following
docker images command to verify the image exists (on Windows, you may need to replace
docker images | grep habitatizelearn-chef/habitatize 0.1.0 3e75a0389f82 6 minutes ago 231MBlearn-chef/habitatize 0.1.0-20171007152417 3e75a0389f82 6 minutes ago 231MBlearn-chef/habitatize latest 3e75a0389f82 6 minutes ago 231MB
Every origin name is unique. Here, we use the
learn-chef origin as an example. In the Try Habitat module, you exported the
HAB_ORIGIN environment variable to refer to your origin name to make it easy to run commands that require your origin name.
Before you run the Docker container, ensure that you have the
HAB_ORIGIN enviornment variable set.
Linux and macOS
learn-chef with your origin name.
learn-chef with your origin name.
Windows PowerShell: ~
$env:HAB_ORIGIN = "learn-chef"
Windows PowerShell: ~
Run the following
docker run command to bring up the container.
Linux and macOS
docker run -p 8000:8000 $HAB_ORIGIN/habitatize
Windows PowerShell: ~/habitat-building-with-scaffolding
docker run -p 8000:8000 $env:HAB_ORIGIN/webapp/habitatize
-p argument routes requests to localhost on port 8000 to the application running inside the container on port 8000.
From the output you see similar output as you did when you ran the app in the Studio.
From a browser, navigate to http://localhost:8000 or http://127.0.0.1:8000. You see the site index, which includes a form.
Click the Choose File button and select a GIF or PNG file. (The project comes with an image you can use.) Then click Upload image to see your image transformed into an animated GIF.
You'll see a failure if you upload a JPEG file. That's because the
core/imagemagick package is not compiled with JPEG support. You'll learn in the next module how to extend the application to include JPEG support.
When you are done experimenting with the app, return to your terminal window and enter Ctrl+C to terminate the container.
In this module, you built a Habitat package for a Ruby web application.
You learned how, for common application types, scaffolding can take care of many of the build tasks for you. Because scaffolding can't do everything, you added an explicit dependency to the ImageMagick package.
You ran the app from the Studio and verified you could access it through
wget. To make the app accessible from your workstation, you exported the package to Docker and ran the container from your workstation.