18 Mar 2015 · Software Engineering

    How to Deploy Node.js Applications with Capistrano

    9 min read
    Contents

    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:

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Avatar
    Writen by:
    Chief Architect at Semaphore. A decade of experience in dev productivity, helping close to 50,000 organizations with operational excellence.