Jekyll, a static site generator, has become a common solution for websites. While GitHub Pages offers free hosting, customization is somewhat limited.
Deploying to Heroku is an option for customization, but is a bit much for a static website. And while we can deploy to GitHub if we pre-compile, we are still limited to what the server can do. Whereas with a VPS from DigitalOcean, for as little as $5 per month, we can customize everything.
Managing a sever by hand is annoying, scary, and a hassle. Instead we can use Chef to manage the server. We’ll be going through the process I used to deploy this website, starting with creating a local Vagrant server.
Vagrant allows us to “create and configure lightweight, reproducible, and portable development environments.” Being able to work offline or without a paid server is nice, but the ability to try changes with no harm is the biggest advantage. And that’s the main reason we’re starting with it.
First we need to download and install Vagrant. And we’ll be using VirtualBox as the virtual machine provider. Other providers are also supported though, such as VMware.
Vagrantfile describes and configures the machine, which uses Ruby and is well documented. We’ll be initializing it with a
hashicorp/precise64 box, a standard Ubuntu 12.04 LTS 64-bit box. Other operating systems are available on vagrantbox.es.
We can simplify the generated file to the bare necessities, the box and the memory available. We’re going to use a 512MB machine in production, so we can match it locally.
To ensure we have everything configured correctly we can boot the server with
vagrant up and SSH into it with
Next we need to setup and configure Chef, which lets us automate and version our infrastructure as code. If you’re not familiar with it I recommend checking out the website. We’ll be using chef-solo, which is easier to use and set up for a single server. And to simplify it further we’ll be using knife-solo.
First we’ll add knife-solo and librarian-chef to a
Gemfile and run
bundle to install them. The librarian-chef library helps automate the management of third-party Chef cookbooks the server depends on.
Next we’ll initialize a directory structure for Chef with knife:
It will generate files and empty directories:
.chef/knife.rb— The configuration settings for knife.
Cheffile— External cookbook definitions.
cookbooks— Directory for vendored cookbooks from the Cheffile.
data_bags— Directory for data bags. See the Data Bag documentation.
environments— Directory for environment definitions. See Environment documentation.
nodes— Directory for node definitions. See Nodes documentation.
roles— Directory for role definitions. See Roles documentation.
site-cookbooks— Directory for your custom Chef cookbooks.
Adding a Cookbook for a Web Server
Now we’re ready to actually start configuring Chef cookbooks. A cookbook generally defines a single scenario for Chef, such as installing and configuring a piece of software. Basically “the meat and potatoes” of Chef and probably what we’ll interact with the most. See the Cookbook documentation if you want to know more about them.
The primary software needed for running a Jekyll website is a web server that can serve static files. We’ll be using nginx, but Apache or practically any other server would work.
There are a ton of existing cookbooks we can use, so we’ll be using the nginx cookbook that is on the cleverly named Chef Supermarket. It offers plenty of customization, but we’ll start with the easiest solution. To use it we’ll add it to the
And then install the cookbook with librarian-chef:
Now we need to create a node to define which cookbooks will run. The knife-solo command will automatically look for a node named after the host we run it on, so we’ll name the node
vagrant and add the default nginx recipe. By default it installs nginx via a package. We’re going to run the latest stable version available instead, so we’ll configure the recipe to compile nginx from the source. The recipe allows compiling from source simply by setting node attributes.
Note that I manually determined the
checksum value by downloading the compressed file from the official website and using
shasum -a 256 [file] locally on OS X.
And we’ll add an SSH host for the Vagrant configuration. Vagrant provides the necessary configuration output by running
vagrant ssh-config --host vagrant. Note that the host option can be whatever you would like, but be sure to replace it in future instructions.
Now we can run Chef on the Vagrant server to install nginx. Using the “bootstrap” command runs the “prepare” and “cook” commands, which installs Chef on the host then uploads and runs the kitchen, which is all the Chef configuration. In the future we only need to run the “cook” command.
Vagrant should now be running an
nginx server, but there’s no way to access it outside of the virtual machine yet. Vagrant can forward ports, so we’ll forward port
80 on the Vagrant server to
8080 locally to make it easier to access.
vagrant reload we’ll be able to access it locally at http://localhost:8080. It will be a “404 Not Found” error for now though, since we haven’t configured a website yet.
Creating a User
Before we add a website we’re going to create a
deploy user, since deploying as root isn’t the best idea. There are some existing cookbooks we could use, but Chef already provides basic user management. Since we don’t need anything beyond a user with our public key we’ll keep it simple.
First we need to define our cookbook name.
Then we can create the simple, default recipe. First we create the user with a configurable name, setting the home directory path and allowing Chef to manage the home directory. Then we create the
.ssh directory and add the
authorized_keys file to it, using a template that will contain our public key.
After we can create the required template with our public key.
And finally we need to add the recipe to our node and define the user name. Since the main purpose for the user is deploying we’ll name it
Then we can cook the server and ensure we can SSH in as the new user.
Adding a Website
The web server is still pretty useless without a website. We’re going to create a custom cookbook that depends on the nginx cookbook, but also adds and enables a website.
We’ll create a new cookbook with only a name for now and depend on the existing cookbook we’re using.
Now we can create the default recipe. Most importantly we need to include the
nginx recipe. Since we’re going to deploy to
/var/www, we’ll create that directory and ensure our
deploy user owns it. We’ll use a template for creating the site and also ensure ownership. And all that’s left is to enable it with
nginx_site, which comes from the third-party nginx cookbook.
All that’s left is to create template file. We’ll exclude any customizations for now, such as compressing and caching assets. Note that we’ll be using the standard Capistrano directory,
current, along with the standard Jekyll compilation directory,
Now we add the recipe to run list for the node and while we’re here we’ll also add a setting to disable the default site.
Now we can cook the server to run the custom recipe.
We’re going to build the site on the server rather than building it locally so we need to install and configure Ruby. We’ll make our own
ruby cookbook that will depend on the
rbenv cookbook to build Ruby.
And we of course need to install the cookbook.
Now we can start creating our custom cookbook, which will just depend on the
rbenv cookbook provides several recipes. We’ll be using the default recipe, to install
rbenv, and the
ruby_build recipe, to install the dependency for compiling and installing Ruby. And we’ll install a configurable version globally, as well as install the
Now we can add the recipe in our run list and configure the version of Ruby we want to install.
Now we can cook the server and ensure it’s installed. Note that compiling Ruby will take a few minutes.
We now how the minimal components needed to deploy a Jekyll website to a Vagrant box. See the jekyll-vps-server contains the complete source, with the part-1 branch being specific to this article.
There’s still plenty to be done. In the next part we’ll set up Capistrano, including deployment of local changes. And in the following part we’ll prepare and deploy a production version. E-mail me if you have any tips, comments, or questions.