Introduction
Rails is great, but often it is not the best tool for creating smaller applications. For smaller size apps, especially those that are mainly focused around pure JSON APIs, Sinatra is often a far better option. Unfortunately, the majority of learning resources in the Ruby ecosystem are focused on Rails, and leave out the details for other frameworks. One of the essential web application development processes is application deployment.
This article will try to teach you about Capistrano— a remote server automation tool that lets you execute arbitrarily tasks over SSH. It is very useful for automating your application’s deployment and setting up common repetitive tasks for remote machines.
Prerequisites
This article will assume that you are using the following tools to develop and serve your applications:
- Bundler for dependency management
- Phusion Passenger for serving your application to the web
Installing Capistrano
The first step we need to take is to add Capistrano as our applications dependency. To achieve that we will add the following line to our Gemfile
:
gem "capistrano"
Install the above gem by executing bundle install
in your shell.
To get started with Capistrano we need to “capify” our Sinatra project. In other words we need to let Capistrano create the configuration files and the file structure that it needs. To do this, run the following command in your application’s root directory:
bundle exec cap install
The above will append the following file structure to your application:
├── Capfile
├── config
│ ├── deploy
│ │ ├── production.rb
│ │ └── staging.rb
│ └── deploy.rb
└── lib
└── capistrano
└── tasks
With this we are ready to use Capistrano with your Sinatra app.
Installing Addition Dependencies
We will install two addition dependencies for easier deployment management, the capistrano-bundler and the capistrano-passenger gems. These gems will help us to install our Bundler-defined dependencies on the remote server and to start/restart our Phusion Passenger based application.
Let’s add them to the Gemfile
:
gem "capistrano-bundler"
gem "capistrano-passenger"
Install them by executing bundle install
.
At this point we need to inform Capistrano to include those two dependencies. We will achieve this by editing the Capfile
in our project’s root directory.
The Capfile
is the first file that Capistrano loads and it is responsible for loading all the dependencies that your application needs for deployment. By default it includes deployment commands and loads all the tasks from the lib/tasks
directory.
Luckily, these gems are so common that the Capfile
already includes them, but comments them out by default. Uncomment those two lines, to require the gems. Those lines should look like this:
require "capistrano/bundler"
require "capistrano/passenger"
The next time you execute a Capistrano task, these gems will be available in your Capistrano scripts.
Defining Servers and Roles
By default, Capistrano will create two types of server environments for our application: production and staging. The configuration file for each of those environments can be found in the config/deploy/
directory.
In these files you can define the server you are going to deploy to. For example, we can define a server for our staging environment if we add the following line to the config/deploy/staging.rb
file:
server "staging.sinatra-app.com", :user => "deployer", :roles => %{web app}
In the above command we have defined a server that lives under the staging.sinatra-app.com
domain. We could have used the IP of the server instead, like in the following example:
server "111.222.333.444", :user => "deployer", :roles => %{web app}
The next thing we want to notice is the user
parameter. That option tells Capistrano to use the deployer
Unix user on the remote server. Of course this can also be changed to match the user on your specific deployment machine.
In case we have several machines where we want to deploy simultaneously, we can simply repeat the server
command to add additional servers:
server "staging1.sinatra-app.com", :user => "deployer", :roles => %{web app}
server "staging2.sinatra-app.com", :user => "deployer", :roles => %{web app}
server "staging3.sinatra-app.com", :user => "deployer", :roles => %{web app}
A common option when defining the servers for the production environment is to hardcode the branch to master
, and thus make sure that we only deploy the latest stable build. We can define the branch like this:
server "staging.sinatra-app.com", :user => "deployer", :roles => %{web app}
set :branch, "master"
The last argument that you can see defined in the above examples is the roles
option. With it we can limit the tasks that are run on some of the servers. For example, if we keep our database on a separate machine, we can define that server like this:
server "staging.sinatra-app.com", :user => "deployer", :roles => %{web db}
In the above line we specified that on this server we want to execute only the database specific tasks.
Configuring the Deployment
Capistrano’s main configuration file is the config/deploy.rb
. This is the file where we define the name of our application and a URL to its repository.
Let’s say that our application is called test-application
and is hosted on this Git server git@github.com/tester/test-application.git
. We then need to set the application
and repo_url
options in our config/deploy.rb
file:
set :application, "test-application"
set :repo_url, "git@github.com/tester/test-application.git"
When Capistrano deploys our application, it is actually executing git pull
over an SSH connection. That is why we need to provide the URL to our project’s repository.
The default deploy path is inside the /var/www
directory, but often we want to deploy our applications to the home directory of the remote user. For example, if our user on the remote server is joe
we would set this path to be:
set :deploy_to, "/home/joe/test-application"
The above line will tell Capistrano to deploy the project under the test-application
directory, in the home of the joe
user.
When Capistrano deploys your application, it will create a releases
, current
and shared
directory in the deploy_to
path. The current
path will be a symlink to the latest release in the releases
directory, while the shared
directory contains files that are symlinked into every release.
Managing Secret Files
Often, our application depends on external APIs or a database connection. Both of them usually require a secret password for access. Because we should not keep those passwords checked in to our repository, we will use the linked files mechanism provided with Capistrano.
First we need to define a path of our secret file, relative to our project’s root directory. For example, we could decide to use config/secrets.yml
with the following content:
API_KEY: xyz
With that in place, first we will add that path to our .gitignore
file to prevent it from checking in to our repository:
# .gitignore
config/secrets.yml
Secondly, we need to SSH into our server and store the content of the file in the shared
directory to the config/secrets.yml
path so it will be linked into each release. The full path of our remote secrets file should be test-application/shared/config/secrets.yml
if test-application
is the name of our application.
The next step is to tell Capistrano that this file is linked into each release with the following option in the config/deploy.rb
file:
set :linked_files, ["config/secrets.yml"]
The above line tells Capistrano to include the secret files from the shared
directory into the project’s config
directory on each release.
Setting up SSH Keys
Capistrano needs two SSH key pairs to deploy to your servers.
The first SSH key pair will make sure we are able to enter the remote machine where we want to deploy. It is always a good practice to check your connection before deployment by manually entering the server. For example, if the name of the remote Unix user you are using for deployment is joe
we could try to SSH into the remote server with:
ssh joe@1.2.3.4
Where 1.2.3.4
is the IP of the remote server.
The second SSH key pair is concerned with pulling from our git repository. Each time we deploy, Capistrano will pull a fresh copy of our repository into the deploy path on the remote machine. For this to succeed we need to set up a private key on the remote machine that has access to our repository.
Capistrano also offers an alternative way to pull from our repository. It can use our local keys, forward them in our SSH session to the remote server, and use them to when pulling the project. This way we don’t need to store our SSH keys on the remote server and tighten our security a little bit.
To achieve this, we need to enable the following option in our config/deploy.rb
file:
set :ssh_options, { :forward_agent => true }
Before we deploy, we also need to make sure that our SSH agent is running. To run the agent and put your keys into the agent type the following in your command line:
eval ssh-agent
ssh-add ~/.ssh/id_rsa
To test if we have all the necessary keys we could SSH into the remote machine and try to pull from our repository.
igor@localhost ~ $ ssh joe@1.2.3.4
joe@1.2.3.4 ~ $ git pull git@github.com/tester/test-application.git
If everything works without issues, we are ready to deploy.
Deployment
With the above steps in place we can deploy our application with a simple shell command:
bundle exec cap production deploy
That line will do several things on the remote server:
- It will create the project’s directory and the
releases
,shared
andcurrent
directories. - Install all bundle dependencies with
capistrano-bundle
. - Run/restart Passenger with the
capistrano-passenger
task.
To deploy to a staging server environment, simply:
bundle exec cap staging deploy
Conclusion
Capistrano is an excellent tool for automating your application deployment. There is of course much more to Capistrano than what we covered in this tutorial. Some great resources are:
P.S. Would you like to learn how to build sustainable Rails apps and ship more often? We’ve recently published an ebook covering just that — “Rails Testing Handbook”. Learn more and download a free copy.