Painless deployment workflows with Capistrano 3

Looking into how we can utilise Capistrano 3 as a basic deployment workflow for any and all projects.

By Luke Whitehouse on

DevOpsWorkflow

Tutorial

Automation is at the forefront of deployment workflows these days. SFTP is generally viewed as archaic and unreliable yet still a lot of agencies, freelancers and anyone in between are still using it as part of their day to day workflow. This needs to change.

In this post I will explain how we can utilise Capistrano 3 to do exactly this, to create a workflow that allows us to deploy simultaniously to multiple environments, ensuring we can test our code before it goes live and having full Version Control and Rollback access throughout incase an error does occur.

Then, in future posts we can extend upon this with custom scripts that run after deployment, thus automating any configuration an application may need during launch.

Prerequisites

In order to use Capistrano, you will need the following software installed on your local machine:

You will also need the following software installed on the server(s) you're looking to deploy to:

  • Git
  • SSH access between your local machine and the server(s)

This tutorial will focus on using Git as the Version Control system of choice, however, you can just as easily use Mercurial or Subversion if you wish.

Finally, if you're unsure how to setup SSH access for your server, Digital Ocean have some great tutorials on the matter. However, some Hosting Providers do not allow SSH access for smaller plans so you may need to contact them to find out.

Introducing the project

For this tutorial I'm going to use the example of a completely new project from scratch. Follow along and when you're ready to try this with an existing project then change any reference to repositories to your own.

View the code for this tutorial on Github

OK, I'm going to first of all create a new remote repository and clone that down.

$ git clone git@github.com:Assortment/painless-deployment-workflows-with-Capistrano-3.git

Within its folder lets create an index.html file which will allow us to test whether or not the project has successfully deployed.

$ cd painless-deployment-workflows-with-Capistrano-3
$ touch index.html

along with the following markup in index.html

<!DOCTYPE html>
<html>
<head>
  <title>Painless deployment workflows with Capistrano 3</title>

  <style type="text/css">
    .container {
      width: 85%;
      max-width: 900px;
      margin: 0 auto;
    }
  </style>
</head>
<body>

  <div class="container">
    <h1>Painless deployment workflows with Capsitrano 3</h1>

    <p>If you can see this page then the project is working in your current environment</p>
  </div>

</body>
</html>

Adding Capistrano to your project

To install Capistrano 3, create a blank Gemfile within the root of your project with the following contents

source 'https://rubygems.org'

gem 'capistrano', '~> 3.6'

NOTE: If you're not used to working with gems think of them as npm or bower packages... basically just modules you can install that do stuff.

The first line sets where Bundler should download our gems from, and then second references that we want to download Capistrano version 3.6.*. At this time, the current version of capistrano is 3.6, this may have changed if you're reading this at a later date. Check out the Capistrano website for more info.

Next, we need to install the Capistrano gem, which we can do with Bundler. Afterwards, we'll want to install Capistrano.

$ bundle install
$ bundle exec cap install

We can prefix any gem commands such as cap install with bundle exec to make sure that we use our project's gem versions rather than any versions we may have installed on our computers. Its generally a good idea to always use this prefix moving forward.

We should now have the default template that Capistrano comes with to get us off the ground, lets take a look at the folder structure.

  • Capfile: Sets up our deployment project, providing access to the default tasks Capistrano comes with.
  • config/: The configuration of our project and the details of any environment's (server's) we'll be deploying to.
  • lib/: Where we can create any custom tasks we require that are not provided by default.

Configuring our project

To start with, we need to configure our project from within our config/deploy.rb file.

Delete the contents of this file and replace it with the following

######################################################################
#### Deployment config
######################################################################


##############################
#### Version lock
##############################

lock '3.6.1'


##############################
#### Project info
##############################

set :application, 'painless-deployment-workflows-with-capistrano-3'
set :repo_url, 'git@github.com:Assortment/painless-deployment-workflows-with-Capistrano-3.git'


##############################
#### Deploy info
##############################

set :scm, :git
set :use_sudo, false


##############################
#### Further info
##############################

set :keep_releases, 3
set :log_level, :debug

Lets break this down section by section.

Version lock

This allows us to ensure that whilst we're using this project Capistrano will always use this exact version (3.6.1). This correlates to the version you have installed in your project.

To find the exact version number go to Gemfile.lock which should now be in the root of your project and locate the line the says capistrano. Whatever the number is there is the number you should use in this lock definition within config/deploy.rb.

Project info

Capistrano uses the :application variable when creating temporary folders (amongst other things), so its best to keep this specific to your project (i.e. Imagine two applications both have the same :application name and then deploy at the same time, on the same server - that could lead to disaster!).

:repo_url refers to the URL of the remote repository, or in lame man's terms that where you're storing your project externally. Personally I use Assortment's Github account for this.

Deployment info

:scm tells Capistrano which Source Control Management tools you're using. Out of the box, Capistrano supports:

  • :git - Git
  • :svn - Subversion
  • :hg - Mercurial

:use_sudo allows you to control whether or not the command Capistrano uses on your server are prefixed with sudo, which allows your SSH user the equivalent to root access (everything on your server). If you do not have these permissions or do not wish to allow Capistrano access to this then say :use_sudo to false, rather than true.

Further config

Capistrano works by storing releases everytime you deploy, and then sets the latest release as 'current release' to show your users. This allows us to very easily rollback to a previous release.

However, each release is an entire copy of your website, so this can be quite taxing on your server's space. The :keep_releases number references how many releases should be stored.

Finally, :log_level controls how much information gets output to our Command Line Interface when we run Capistrano commands. As we are actively developing this project, set this to :debug, if not change this to :info.

Configuring our environments

Now that we've setup our project's defaults, we need to configure our enviornments. Capistrano works in an environment based workflow whereby you set an environment (server) that Capistrano can deploy to and then when you deploy, using a command such as:

$ bundle exec cap ENVIRONMENTNAME deploy

You'll use that environment's (in this case - production's) settings that you've specified within your config/deploy/%env%.rb configuration file.

Usually, I'd recommend having at least production and staging environments, however, for the sake of brevity I'll just setup a production environment for now. If this is the case for you too, go ahead and delete your config/deploy/staging.rb file. If not, run through this section again for your staging environment.

FYI: Environment files can override any defaults you've set in config/deploy.rb to make them environment specific.

Moving forward with the config/deploy/production.rb file, delete its contents and add the following

######################################################################
#### Production environment
######################################################################

set :branch, "master"
server "SERVER-IP-HERE", user: "SSH-USER", roles: %w{web app db}
set :deploy_to, '/var/www/html/painless-deployment-workflows-with-capistrano-3'


##############################
#### Further info
##############################

# You can override any default configuration set in config/deploy.rb,
# more information can be found at:
# http://capistranorb.com/documentation/getting-started/configuration/

Lets break this one down too.

Setting the :branch allows you to specific that you only want deployments from a specific branch for this environment. As a common practice at work we:

  • Deploy the master branch to the production environment;
  • and the development branch to the staging Environment.

Next are the server settings for this environment, providing the IP address and SSH user of the server you wish to deploy to. Remember that the credentials you use here will be stored within your Git repository so don't give access to just anyone. Go ahead and change these to your server's credentials.

Finally, once we have our environment configured we need to tell Capistrano where we want it to deploy to. :deploy_to does exactly this, by providing an absolute path where your project will be stored.

In my case, I'll be deploying to a Digital Ocean server so I want all my projects to be deployed to /var/www/html.

set :deploy_to, '/var/www/html/painless-deployment-workflows-with-capistrano-3'

Ready, set, go!

Awesome, thats all the configuration done. Theres one more thing to do, deploy! Within your console, navigate to your newly configured project and run the following:

$ bundle exec cap production deploy

After a little while your project should have successfully deployed. The easiest way to test this is to log into your server, navigate to the :deploy_to destination and see if the project has been set. Did it work? if not, don't worry, just skip to the Troubleshooting section of this post.

If it did, carry on straight below.

Understanding Capistrano's deployment structure

So you've successfully deployed your project and can see all the files on your server. You may have also realised that the folder structure doesn't seem to resemble your project and instead has some new folders like current/, and repo. Weird huh?

I could just tell you that all you need to do is point your vhosts on your server to the current/ directory and be on your way, but then you'd never understand what Capistrano is doing for you or know how to debug when things go wrong. Instead, lets take a close look at how Capistrano functions.

When you deploy to your server you'll have access to:

current/
releases/
repo/
shared/
revisions.log

When Capistrano first deploys to a server it will use the credentials you defined in your config/deploy.rb and/or config/deploy/environment.rb to pull down your git repository into a folder called repo/.

Next, Capistrano will bundle that current version of the repo folder, datetime stamp it and add it to the releases folder. The releases folder houses each release that you're currenly storing (Each release is a separate version of your full project files), whereby the number of releases is based on what you've set your :keep_releases to within config/deploy.rb. In our case there will be 3 releases.

You can rollback to a previous release by running the deploy:rollback command, like so:

$ bundle exec cap ENVIRONMENTNAME deploy:rollback

Afterwards, a current folder will be symlink'ed to the latest release you have.

Think of a symlink (Symbolic Link) as an alias to say, if someone links to /path/to/current they're really going to /path/to/releases/LATEST_DATETIME. Almost like a 301 Redirect, but for files on your server. You can read more about Symbolic links on Wikipedia.

You can see a log of any deployments that have been carried out by opening your revisions.log file. This is especially useful if you have multiple people deploying to a project, as their Git user will be displayed as the person who deployed.

Finally, by default you will have a blank shared folder, however, this can be used to store files that you don't want to be within your git repository. For example, the uploads for your website may be on your local machine so you could setup a task for Capistrano to add them to your shared folder on the server and then symlink within the relevant area of your current folder. This is incredebly useful for configuration files that have password in them, such as a .ENV file or wp-config.php if you use WordPress.

Troubleshooting

Having problems? Here's a list of commonly asked questions when deploying that may be of use. If you cannot find your answer here, please feel free to leave a comment; I'd be more than happy to help.

'/usr/bin/env: git: No such file or directory'

Git installed on your server. Assuming you have permissions to do so and have apt-get installed, SSH into the server and run the following:

$ sudo apt-get install git

If you cannot do this, please contact your server administrator.

'cannot create directory ‘path/to/folder’: Permission denied'

You will need to setup your SSH user's permission to ensure it can create directories in the location you're deploying to, in the example this was /var/www/html. An easy way of doing this is to log onto your server and trying to manually create that folder using the mkdir command without sudo. If not, you may need to chown that directory, like so:

$ sudo chown -R SSHUSER:SSHGROUP /var/www/html

It keeps asking me to provide a passsword for my SSH user

Mixture of potentially two things. A) You don't have your SSH key setup on the server, if this is the case then take a look at this tutorial on Digital Ocean or again contact your server administrator.

'The deploy has failed with an error: getaddrinfo: nodename nor servname provided, or not known'

Sounds like your server details are incorrect. Go back to your environment's config file within config/deploy/<environment-name>.rb and check over your credentials.

To test these run the following ssh SSH-USER@SERVER-IP-HERE you should be able to log in. If you are asked to provide a password refer to the question above.

View the code for this tutorial on Github

Taking it a step further

Hopefully this tutorial has provided you with enough to get going with deployment workflows in general. Capistrano is an awesome tool and can be a great addition to any developer's workflow, give it a try and let me know what you think.

There's a hell of a lot more we can do with Capistrano and quite honestly this post only touches the tip of the iceberg. In future posts we'll explore how you can extend this base project to create your own custom tasks for your applications.

In preparation for that, I'd love to hear what you'd like to utilise Capistrano for. Perhaps for backing up your databases? Maybe even deploying files that are ignored from your Git repository for security reasons? Whatever the case, leave a message and I'll include the best ones in the follow up tutorial.

Follow us on Twitter, Facebook or Github.
© Copyright 2021 Assortment.
Created and maintained by Luke Whitehouse.