How to Deploy Node.js Applications with Capistrano

Capistrano is a popular tool for web application deployment. This article will show you how to use it with Node.js based projects.

Brought to you by Semaphore, the fastest continuous integration and deployment platform out there.

Test and deploy faster

Introduction

Capistrano is a popular tool for web application deployment, especially in the Ruby ecosystem where it originated. But Capistrano is being used to deploy applications made in any programming language. It is a perfectly fine tool for deploying Node.js projects too. in the Ruby ecosystem.

This article will explain how to set up a deployment script for your Node.js application using Capistrano, and introduce you to its DSL (domain specific language) that makes remote task automation easy and straightforward.

Prerequisites

This article will assume that you are using PM2 to run your Node.js application in production, and that you know how to set up and manage SSH access to your repository, and the remote server where you want to deploy.

Installing Capistrano

Capistrano is a Ruby package, so we will need to install Ruby on our system before installing Capistrano. Use the following command to install it on a Ubuntu based system:

sudo apt-get install ruby-full

The above command will install the ruby command and its package manager named gem. With that command we can install Capistrano with the following:

gem install capistrano

The above command will install Capistrano as a globally available package on our system. The only thing left is to bootstrap Capistrano in our project.

Let's assume that our Node project is located in the ~/project directory. To capify (install Capistrano configuration files) our project we can invoke the following command in our project's root directory:

~/project $ cap install
mkdir -p config/deploy
create config/deploy.rb
create config/deploy/staging.rb
create config/deploy/production.rb
mkdir -p lib/capistrano/tasks
create Capfile
Capified

After this step your project tree should include the following structure:

|-- Capfile
|-- config
|   |-- deploy
|   |   |-- production.rb
|   |   `-- staging.rb
|   `-- deploy.rb
`-- lib
    `-- capistrano
        `-- tasks

We will also install the capistrano-npm gem that will help Capistrano to install your application's dependencies on the remote machine. Install it with the following command:

gem install capistrano-npm

Defining Servers

Let's first explore the content of the config/deploy/ directory. This is where Capistrano lets us define and configure the various environments for our application. By default, Capistrano creates two types of server environments for our application: production and staging, but we can add additional environments just by creating a new file in the config/deploy/ directory.

If we open some of these files, for example config/staging.rb we can see that there is a server definition in each of them. This is how we tell Capistrano the location of the remote server where we want to deploy our code. Let's analyze the following line:

server "staging.node-test.com", :user => "deployer", :roles => %{web app}

The first argument we pass to the server definition is the location of the server on the internet. It can be a URL like in the above example, or a simple IP address like bellow:

server "1.2.3.4", :user => "deployer", :roles => %{web app}

The next thing we want to configure is the user parameter. When Capistrano deploys our code, it actually creates an SSH session to the remote server using a command similar to the following:

ssh deployer@node-test.com

As you can see in the above command, the user parameter in the configuration
is the name of user on the remote server that Capistrano is using to access the server.

Now the only part left are unexplained are server roles. If your application consists of several servers that work together to create a unified application then we can define a role for each of them in a server definition. For example, if you have one server where you keep your database and another one where you run your Node.js code we would create two server definitions in the configuration file:

server "node-test.com", :user => "deployer", :roles => %{web app}
server "database.node-test.com", :user => "deployer", :roles => %{database}

Capistrano tasks (group of commands that will be executed on the remote server) can specify on which roles they want to be executed. For example a database:backup task should only be executed on servers that have a db in their roles list while a deploy:restart task should only be run on a server with an app role.

Defining the Branch to Deploy

When Capistrano enters your remote server through an SSH session, its next step is to clone a fresh copy of your project. In order to do this, it needs to know the name of the branch that you want to deploy.

For example, if we would like to deploy the master branch to production, we need to have the following line in the config/production.rb file:

set :branch, "master"

Defining the Repository

The config/deploy.rb file is Capistrano's main configuration file. Here we can define the necessary options for deployment that will be shared across every environment.

For example let's say that the name of our project is node-test and it is located in the git@github.com/tester/node-test.git Git repository. This is how this information translates into a format that Capistrano can understand:

# config/deploy.rb

set :application, "node-test"
set :repo_url, "git@github.com/tester/node-test.git"

Deployment Path on the Remote Server

We also need to tell Capistrano where to put your application on the server. The default path is in the /var/www directory, but developers commonly deploy their application in the home directory of the remote user that executes the deployment. To set up deployment to the home directory of a deployer user we need to set the following:

set :deploy_to, "/home/deployer/node-test"

When Capistrano deploys your application, it will create a releases, current and shared directories in the path specified via deploy_to. 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.

SSH Access

Capistrano uses two SSH keys for application deployment.

The first key lets Capistrano make a connection with the remote server, while the second one lets it clone from our repository.

We can check if everything is working properly by trying to enter the remote host from the shell:

ssh deployer@node-test.com

While logged in as the deployer user on the remote server we should also try to pull something from our repository:

git clone git@github.com/tester/node-test.git

Capistrano also offers us a way to forward our SSH key with the following option:

set :ssh_options, { :forward_agent => true }

By setting the above option in our Capistrano configuration, it will be able to use the locally available SSH keys on the remote server and clone our project's repository using that key. When using this option we need to make sure that an SSH agent instance is running before deployment. To start an SSH agent instance we need to execute the following command in our shell:

eval `ssh-agent`

When an SSH agent is running we need to add the SSH key that will be forwarded to our remote server to its registry. For example, if we use the standard SSH key located in ~/.ssh/id_rsa, we can add it with the following command:

ssh-add ~/.ssh/id_rsa

Managing Secret Data

An application often depends on external APIs or a database connection that requires some kind of secret data (password, token, ...) to allow access rights for our application. Storing such information in our repository is considered an anti-pattern because it opens up several security related issues. Hopefully Capistrano lets us store secret data directly on the remote server, without the need to check it into our repository.

Let's start with an example secret data, stored in a JSON format, that our application loads from the config/secrets.json file.

{
  "API_TOKEN": "secret_api_token"
}

To prevent an accidental commit that contains this data, we will put it's path in the .gitignore file:

config/secrets.json

With that in place, we now have to SSH into our server and store the content of the file in the shared directory. Following that, we need to tell Capistrano to symlink our secret file into the current directory. We can do that with the following option in the config/deploy.rb:

set :linked_files, ["config/secrets.json"]

Installing NPM dependencies

Its time to use the capistrano-npm gem to manage our application's dependencies. The only thing we need to do is to load the gem in the Capfile that is located in our project's root directory. Append the following line to the end of the Capfile:

require 'capistrano-npm'

Starting the Application

The only thing missing before we can deploy is to tell Capistrano to start/restart your application with pm2 on each deploy. This will also be the first time where we will write a custom Capistrano task.

Place the following code in the lib/tasks/pm2.cap file:

require 'json'

namespace :pm2 do

  def app_status
    within current_path do
      ps = JSON.parse(capture :pm2, :jlist, fetch(:app_command))
      if ps.empty?
        return nil
      else
        # status: online, errored, stopped
        return ps[0]["pm2_env"]["status"]
      end
    end
  end

  def restart_app
    within current_path do
      execute :pm2, :restart, fetch(:app_command)
    end
  end

  def start_app
    within current_path do
      execute :pm2, :stop, fetch(:app_command)
    end
  end

  desc 'Restart app gracefully'
  task :restart do
    on roles(:app) do
      case app_status
      when nil
        info 'App is not registerd'
        start_app
      when 'stopped'
        info 'App is stopped'
        restart_app
      when 'errored'
        info 'App has errored'
        restart_app
      when 'online'
        info 'App is online'
        restart_app
      end
    end
  end

end

While the above snippet looks a little bit complicated, it is actually really simple. It defines the restart task that will start a PM2 instance of your application if is not already registered in its registry, or restart it otherwise.

To execute this task on every deploy, we will register a restart callback on the after "publishing" event and invoke restart task from the lib/tasks/pm2.cap file with the following snippet in the config/deploy.rb file:

namespace :deploy do

  desc 'Restart application'
  task :restart do
    invoke 'pm2:restart'
  end

  after :publishing, :restart   
end

Deployment

With the above steps in place we can deploy our application with a simple shell command. Call the following command to deploy your application to the production server:

cap production deploy

Conclusion

Capistrano is an excellent tool for automating your Node.js application deployment. There is of course much more to Capistrano than what we covered in this tutorial. Some great resources are:

C8072c254e533750d092e3a3a513c598
Igor Šarčević

Programming and math are his passion. Developer at Rendered Text, actively working on SemaphoreCI. Turns into a Japan obsessed ninja by night.

on this tutorial so far.
User deleted author {{comment.createdAt}}

Edited on {{comment.updatedAt}}

Cancel

Sign In You must be logged in to comment.