Deploying node.js applications with Capistrano

gitops javascript node.js 
2012-04-04
↞ See all posts



I really like Capistrano.

Capistrano is a deployment gem for Ruby which helps you manage migrations, deployments, applications, etc on all of your many servers. It excels at keeping many servers in sync and managing complex multi-application deployments. If you are a ruby-on-rails developer, you probably already use Capistrano.

I have been working on node.js applications lately, and I was missing the simplicity of Capistrano deployments, so I wondered how hard it would be to use Capistrano to manage a node.js application. It turns out that it is fairly easy!

This guide assumes that you already have your server set up (node installed, npm installed, database installed, etc), but you can of course manage this with Capistrano as well. An important note is that you do not need to install ruby on the servers you are deploying to, just your local development environment.

I’m going to assume you also have a version of Ruby installed (ships with all modern OSX versions). Step one is installing Capistrano (gem install capistrano). Gems are like npm packages if you aren’t familiar with ruby. You may need to be root to do this (sudo gem install capistrano), but I recommend using rbenv a great ruby version manager.

To get rbenv up and running, here are the tl;dr steps (I’ll write a longer post on rbenv vs rvm in the future, but this is the "least scary" way to have custom versions of ruby installed at the user-level)

  • Install rbenv for OSX:

  • ( Homebrew: https://github.com/mxcl/homebrew/wiki/installation )

  • Install rbenv brew install rbenv

  • Install ruby-versions brew install ruby-build

  • Install a ruby version

  • Set your global rbenv version

  • Set up some handy bash aliases for running ruby apps:

  • export PATH="$HOME/.rbenv/bin:$PATH

    "

  • eval "$(rbenv init -)"

  • alias r="rbenv exec "

  • alias rb="rbenv exec bundle exec "

  • exec $SHELL

  • Run your apps with rbenv

  • r gem install bundler

  • r bundle install

Now, rather than worrying about being root or messing with your system-level ruby and gems, you can just run r and then your command in ruby user space! I’m using r as shorthand for rbenv exec, as setup in my bash configuration steps above.

OK, we have Capistrano installed, now we need to set up a few files in our node project. Luckily this is as simple as typing r capify . in the project directory’s root. The capify command will set up a few files and folders in your project. You don’t need to worry about Capfile (which is used by the Capistrano command later to initialize itself), but ./config/deploy.rb is where we will be building our custom deployment setps.

The one tricky thing with node.js applications is that by default they expect to be run within a console. This means that it can be hard to daemonize them (running head-less), which is what you want on a production web server. However, the fantastic package Forever does exactly this for us. Forever monitors and logs your application, as well as creating handy start and stop wrappers for your project. Ensure that you have forever installed (npm install forever) and that it is listed in your package.json. Forever can be installed globally (so you have handy access to the ‘forever’ command), but I’ll be using it locally to minimize the chance of conflicts.

So now we have Capistrano and Forever installed. We need to create some new deployment tasks in ./config/deploy.rb to tell Capistrano exactly what we want it to do when we deploy. Here is my skeleton deploy.rb:

1set :application, "MY_APPLICATION" 2set :repository, "git@github.com:PATH_TO_MY_REPO" 3set :scm, :git 4set :use_sudo, false 5set :keep_releases, 5 6set :deploy_via, :remote_cache 7set :main_js, "MAIN_APP.js" 8 9desc "Setup the Demo Env" 10task :demo do 11 set :branch, 'develop' 12 set :domain, 'MY DEMO SERVER' 13 set :user, 'MY SSH USER' 14 set :applicationdir, "/home/#{user}/deploy/#{application}" 15 set :deploy_to, applicationdir 16 ssh_options[:keys] = ["/path/to/my/ssh.pub"] 17 18 server 'MY DEMO SERVER', :app, :web, :db, :primary => true 19end 20 21desc "Setup the Production Env" 22task :production do 23 set :branch, 'master' 24 set :domain, 'MY PROD SERVER' 25 set :user, 'MY SSH USER' 26 set :applicationdir, "/home/#{user}/deploy/#{application}" 27 set :deploy_to, applicationdir 28 29 server 'MY PROD SERVER', :app, :web, :db, :primary => true 30end 31 32namespace :deploy do 33 34 before 'deploy:start', 'deploy:npm_install' 35 before 'deploy:restart', 'deploy:npm_install' 36 # before 'deploy:default', 'deploy:setup' 37 38 after 'deploy:create_symlink', 'deploy:symlink_node_folders' 39 after 'deploy:setup', 'deploy:node_additional_setup' 40 41 desc "START the servers" 42 task :start, :roles => :app, :except => { :no_release => true } do 43 run "cd #{applicationdir}/current/ && node_modules/.bin/forever start #{main_js}" 44 end 45 46 desc "STOP the servers" 47 task :stop, :roles => :app, :except => { :no_release => true } do 48 run "cd #{applicationdir}/current/ && node_modules/.bin/forever stop #{main_js}" 49 end 50 51 desc "RESTART the servers" 52 task :restart, :roles => :app, :except => { :no_release => true } do 53 run "cd #{applicationdir}/current/ && node_modules/.bin/forever restart #{main_js}" 54 end 55 56 task :symlink_node_folders, :roles => :app, :except => { :no_release => true } do 57 run "ln -s #{applicationdir}/shared/node_modules #{applicationdir}/current/node_modules" 58 end 59 60 task :node_additional_setup, :roles => :app, :except => { :no_release => true } do 61 run "mkdir -p #{applicationdir}/shared/node_modules" 62 end 63 64 task :npm_install, :roles => :app, :except => { :no_release => true } do 65 run "cd #{applicationdir}/current/ && npm install" 66 end 67 68 task :npm_update, :roles => :app, :except => { :no_release => true } do 69 run "cd #{applicationdir}/current/ && npm update" 70 end 71 72end 73 74task :tail do 75 resp = capture "cd #{applicationdir}/current/ && node_modules/.bin/forever logs | grep #{main_js}" 76 log = resp.split(" ").last 77 log.gsub!("\e[35m", "") 78 log.gsub!("\e[39m", "") 79 run "tail -f #{log}" 80en

You can see that we have custom start, stop, and restart commands which Capistrano’s normal deployment tasks will use. You can also of course call these tasks directly if you want to restart your servers.


Other than the wrappers for Forever, I also chose to symlink my node_modules directory to a common place. This will allow me to share my packages between deploys (which are all deployed in separate folders, but symlinked to "current") which will make subsequent deployments really fast.

Now you can use Capistrano very simply to check out new code from GitHub, make a new folder to hold the new code, run any npm updates, stop the old server, and start a new one with one simple command: r cap demo deploy. You will note that we have tasks called "demo" and "production". These tasks just set variables (like git branch names and a list of servers) so that subsequent commands can use them. Commands are run in sequence in Capistrano.

I also created one more command called "tail" which is a quick way for me to tail the output of my application which Forever is smart enough to capture and store in a log file for me. To monitor my demo server, it would be "r cap demo tail". This will run until I close it.

For my node.js deployments, I have node.js listen on port 8080 and use haproxy to route that to port 80. Other folks like to use nginx and sockets to route their app to the normal web ports. Either way, it is best to NOT run your node app as root (which is required if you want to listen on port 80). If you do want to do this, you will need to use Capistrano’s try_sudo command to run the start/restart scripts as root.

The power of Capistrano becomes even more obvious when you have many servers. Just keep adding ‘server’ with distinct roles. You will note that tasks you create will only be executed on servers with specific roles, so you can carefully manage your deployment.

There are many grate Capistrano resources out there (including many awesome extensions for controlling EC2 servers, logging, etc) and now you can use them all with your node.js apps!

Originally published at 04 Apr 2012

Hi, I'm Evan

I write about Technology, Software, and Startups. I use my Product Management, Software Engineering, and Leadership skills to build teams that create world-class digital products.

Get in touch