We are the Dev Teams of
  • brands
  • ebay_main
  • ebay
  • mobile
<
>
BLOG

Deploying Node applications with Capistrano, GitHub, Nginx and Upstart

by Przemek Matylla
in Tutorials

Deploying JavaScriptI'm a huge fan of the node.js platform and I'm also in love with the Nginx server. Putting those two together makes perfect sense, because they both use event driven design. Nginx is a highly performant web server which gives you a myriad of different features like reverse proxy, load balancing, caching and gzipping, to name a few.

This article explains how you harness this power.

Having our Node application behind a Nginx server, we also need a place to store the source code of our app. Not surprisingly we will use GitHub. The last part of our setup will be a deployment tool called Capistrano.

First thing first.

Well, we will obviously need a server to run our stack of technologies. For the purpose of this article I use a small server with Ubuntu 12.04 ×64. For local development I use OS X 10.8.2.

Prerequisites

I assume you have the following software installed on your remote server (the absolute minimum):

  • Git
  • node.js
  • Nginx

For Node and Nginx, please do check Chris Lea’s PPA archive here as he provides the latest stable (and development) versions of different packages.

User privileges

Running Node process as root user is a very bad idea which will soon end in disaster. Therefore we need to add a less privileged user to our system. Let’s call this guy deploy. While logged in as root execute:

$ useradd deploy
$ mkdir -p /home/deploy/.ssh
$ chmod 700 /home/deploy/.ssh
$ chsh -s /bin/bash deploy

Perfect, we have a new citizen in town. Now let’s setup public key authentication, so we don’t have to use passwords anymore to log in to our machine. The SSH key will also be used with GitHub for the deployment process. On your local machine generate a new SSH key (if you haven’t done this yet):

$ ssh-keygen -t rsa -C "your@email.com"

Now copy your public key to the clipboard:

$ cat ~/.ssh/id_rsa.pub | pbcopy

and add this key to /home/deploy/.ssh/authorized_keys on your remote server. Execute the command below and simply paste your key and save the file:

$ vim /home/deploy/.ssh/authorized_keys

Let’s change the ownership of the deploy directories and fix access permission rights:

$ chmod 400 /home/deploy/.ssh/authorized_keys
$ chown -R deploy:deploy /home/deploy

Now setup a fairly complex password for the deploy user. This password will be used whenever this user want’s to execute a command with sudo:

passwd deploy

Create a log file for our Node application:

$ sudo touch /var/log/node.log
$ sudo chown deploy:deploy /var/log/node.log

I usually use upstart which allows me to run Node as deamon (in the background). The last part of our setup is to enable the ‘deploy’ user to run our application with sudo without a password. To acheive this we need to add deploy user to the sudoers. Execute:

$ visudo

and add those two lines:

deploy ALL=(ALL:ALL) ALL
deploy ALL=(ALL:ALL) NOPASSWD: /sbin/start myapp, /sbin/stop myapp, /sbin/restart myapp

All done! Onwards to the Nginx part.

Configuring Nginx

Nginx server will act as a reverse proxy. Whatever comes to the port 80 (default HTTP port) on our machine will be proxied to the desired port of Node application. Nginx will also be used to serve static files of our application. Edit your default vhost config usually located in /etc/nginx/sites-available/default:

$ sudo vim /etc/nginx/sites-abailable/default

and paste the following lines:

upstream app {
    server 127.0.0.1:3000;
}

server {
    listen 80;
    server_name yourapp.com;
    access_log /var/log/nginx/yourapp.log;

    location / {
        proxy_pass http://app;

        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location ~* \.(?:jpg|jpeg|gif|png|css|js)$ {
        root /var/www/app/current/public;
    }
}

Reload Nginx to force the new config:

$ sudo service nginx reload

Lastly lets create a directory where our application will live:

$ sudo mkdir -p /var/www/app
$ sudo chown -R deploy:deploy /var/www/app

All done! Onwards to the Upstart part.

Configuring Upstart

Upstart, installed by default on Ubuntu, will take care of running our Node app as a daemon in the background. Think of Upstart as a better alternative to the good old init.d. Writing Upstart scripts is fairly easy. First create a /etc/init/myapp.conf file:

$ sudo vim /etc/init/myapp.conf

and paste the following lines:

description "myapp"
author "You Name <you@domain.com>"

start on (local-filesystems and net-device-up IFACE=eth0)
stop on shutdown

respawn
respawn limit 99 5

script
    export HOME="/home/deploy"
    export NODE_ENV=production"

    cd /var/www/app/current
    exec sudo -u deploy NODE_ENV=production /usr/bin/node app.js >> /var/log/node.log 2>&1
end script

Please check if you have node.js available at this location: /usr/bin/node, if not please alter the Upstart script accordingly (the penultimate line).

Save and exit the editor. From now on you will be able to start, stop and restart your app with these simple commands (won’t work right now since we don’t have our app deployed yet):

$ sudo start myapp
$ sudo stop myapp
$ sudo restart myapp

All done! Onwards to the GitHub part.

Configuring GitHub and new repo

Go into you GitHub’s Account Settings add a new SSH key in the “SSH Keys” section. Give the key a descriptive name, paste the contents of ~/.ssh/id_rsa.pub in the “Key” textarea and hit “Add key”.

Create a new repo on GitHub. During the process of creating a repo, I added a .gitignore file for node.js apps. Once the repo is ready, clone it to our local machine:

$ git clone git@github.com:YOUR_NAME/NAME_OF_REPO.git

Configuring node.js

In fact no special configuration is needed in the node.js app. For the purpose of this article, I will use a very simple Express app. On my local/development machine I execute:

$ cd NAME_OF_REPO && express -c less .

One important thing is to alter the .gitignore file in the root of application. We need to exclude node_modules directory from Git because all node.js (NPM) modules will be installed during the deployment process. Open .gitignore and make sure you have a node_modules entry there (among others you would like to exclude):

lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz

pids
logs
results

npm-debug.log
node_modules

All done! Onwards to the Capistrano part.

Configuring Capistrano

Capistrano is a program designed to make you deployments a breeze. While it is in fact a Ruby Gem, you won’t need Ruby installed on you remote server – Capistrano runs locally on your development machine. First install the Capistrano Gem:

$ gem install capistrano

Go into the root of your application and type:

$ capify .

The above command generates two files in you project. The Capfile in the root directory and deploy.rb in the config directory. The latter one is especially interesting for us because it’s a “deployment recipe”. We will use this file to tell Capistrano how to deploy our application.

Open config/deploy.rb file and paste the following recipe:

set :application, "myapp"
set :scm, "git"
set :repository, "git@github.com:YOUR_NAME/NAME_OF_REPO.git"
set :branch, "master"
set :deploy_to, "/var/www/app"
set :deploy_via, :remote_cache
set :copy_strategy, :checkout
set :keep_releases, 5
set :use_sudo, false
set :copy_compression, :bz2
set :normalize_asset_timestamps, false
set :document_root, "/var/www/app"
set :ssh_options, {:forward_agent => true}
set :user, "deploy"

role :app, "IP_ADDRESS_OF_YOUR_MACHINE"

namespace :deploy do
    task :start, :roles => :app do
        run "sudo restart #{application} || sudo start #{application}"
    end

    task :stop, :roles => :app do
        run "sudo stop #{application}"
    end

    task :restart, :roles => :app do
        start
    end

    task :npm_install, :roles => :app do
        run "cd #{release_path} && npm install"
    end
end

after "deploy:update", "deploy:cleanup"
after "deploy:update_code", "deploy:npm_install"

I encourage you to check out all the options Capistrano offers for the deployment process. Let’s go briefly through most important ones used in the script above:

set :scm, "git"
set :repository, "git@github.com:YOUR_NAME/NAME_OF_REPO.git"
set :branch, "master"

Sets the origin of your application. Capistrano will always fetch new code from this repo during the deployment.

set :deploy_to, "/var/www/app"

Sets destination directory on the remote server. This is where the application lives.

set :keep_releases, 5

Sets the number of historical releases to 5. In other words, last 5 releases of you app will be kept on a server.

set :ssh_options, {:forward_agent => true}

Tells Capistrano to use your public SSH key to log into your remote machine and also from there to fetch the code from GitHub.

set :user, "deploy"

Tells Capistrano what user should perform a deployment process on the remote server.

role :app, "IP_ADDRESS_OF_YOUR_MACHINE"

Tells Capistrano what’s the address of your remote machine. You can add multiple addresses comma-separated.

Let’s commit the whole project and push it to the GitHub. In the root of your app execute:

$ git add .
$ git ci -m "Initial commit"
$ git push

Now we need to make sure everything is correctly prepared for our deployments. Log in to your remote machine as a deploy user (you should be able to do that without password thanks to our SSH keys setup):

$ ssh deploy@some-remote-ip

and execute this command on your server:

$ ssh git@github.com

If you get a prompt

Are you sure you want to continue connecting (yes/no)?

Type yes and move on. This is a very important part. When you do this for the first time Ubuntu will permanently add GitHub to the list of `known_hosts`.

Now lets allow Capistrano to check our config and setup required directories on our remote server. Type this command on your local machine in the root level of your application:

$ cap deploy:setup

You should see something like this:

* 2013-10-29 14:53:32 executing `deploy:setup'
* executing "mkdir -p /var/www/app /var/www/app/releases /var/www/app/shared /var/www/app/shared/system /var/www/app/shared/log /var/www/app/shared/pids"
  servers: ["IP.ADDRESS.HERE"]
  [IP.ADDRESS.HERE] executing command
  command finished in 66ms
* executing "chmod g+w /var/www/app /var/www/app/releases /var/www/app/shared /var/www/app/shared/system /var/www/app/shared/log /var/www/app/shared/pids"
  servers: ["IP.ADDRESS.HERE"]
  [IP.ADDRESS.HERE] executing command
  command finished in 64ms

Pretty neat. Capistrano created all the directories on a remote machine for us and we are ready for our first deployment. Hold tight and execute this command in the root of your application:

$ cap:deploy

If everything was set up correctly (especially user permissions and paths) you should see this as a last line of Capistrano’s output:

** [out :: IP.ADDRESS.HERE] myapp start/running, process 31170

Have fun deploying your apps!

Title graphics: Wikimedia Commons

node.js, nginx, capistrano, upstart, javascript

?>